├── .changeset ├── README.md └── config.json ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── dependabot.yml ├── logo-dark.svg ├── logo-light.svg ├── pull_request_template.md └── workflows │ ├── autofix.yml │ ├── changesets.yml │ ├── issue-labeled.yml │ ├── lock-issue.yml │ ├── pull-request.yml │ └── verify.yml ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json ├── settings.json └── workspace.code-workspace ├── FUNDING.json ├── LICENSE ├── README.md ├── biome.json ├── docs ├── package.json ├── pages │ ├── api │ │ ├── abis.md │ │ ├── human.md │ │ ├── types.md │ │ ├── utilities.md │ │ └── zod.md │ ├── config.md │ ├── guide │ │ ├── comparisons.md │ │ ├── getting-started.md │ │ └── walkthrough.md │ └── index.mdx ├── public │ ├── .vocs │ │ └── search-index-16b7090f.json │ ├── favicon.png │ ├── favicon.svg │ ├── logo-dark.svg │ ├── logo-light.svg │ ├── og.png │ └── robots.txt ├── sidebar.ts ├── styles.css ├── version.ts └── vocs.config.tsx ├── package.json ├── packages ├── abitype │ ├── CHANGELOG.md │ ├── README.md │ ├── jsr.json │ ├── package.json │ ├── src │ │ ├── abi.test-d.ts │ │ ├── abi.ts │ │ ├── abis │ │ │ ├── human-readable.ts │ │ │ └── json.ts │ │ ├── errors.test.ts │ │ ├── errors.ts │ │ ├── exports │ │ │ ├── abis.test.ts │ │ │ ├── abis.ts │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ ├── zod.test.ts │ │ │ └── zod.ts │ │ ├── human-readable │ │ │ ├── __snapshots__ │ │ │ │ ├── formatAbi.test.ts.snap │ │ │ │ └── parseAbi.test.ts.snap │ │ │ ├── errors │ │ │ │ ├── abiItem.test.ts │ │ │ │ ├── abiItem.ts │ │ │ │ ├── abiParameter.test.ts │ │ │ │ ├── abiParameter.ts │ │ │ │ ├── signature.test.ts │ │ │ │ ├── signature.ts │ │ │ │ ├── splitParameters.test.ts │ │ │ │ ├── splitParameters.ts │ │ │ │ ├── struct.test.ts │ │ │ │ └── struct.ts │ │ │ ├── formatAbi.bench.ts │ │ │ ├── formatAbi.test-d.ts │ │ │ ├── formatAbi.test.ts │ │ │ ├── formatAbi.ts │ │ │ ├── formatAbiItem.bench.ts │ │ │ ├── formatAbiItem.test-d.ts │ │ │ ├── formatAbiItem.test.ts │ │ │ ├── formatAbiItem.ts │ │ │ ├── formatAbiParameter.bench.ts │ │ │ ├── formatAbiParameter.test-d.ts │ │ │ ├── formatAbiParameter.test.ts │ │ │ ├── formatAbiParameter.ts │ │ │ ├── formatAbiParameters.test-d.ts │ │ │ ├── formatAbiParameters.test.ts │ │ │ ├── formatAbiParameters.ts │ │ │ ├── integration.test.ts │ │ │ ├── parseAbi.bench.ts │ │ │ ├── parseAbi.test-d.ts │ │ │ ├── parseAbi.test.ts │ │ │ ├── parseAbi.ts │ │ │ ├── parseAbiItem.bench.ts │ │ │ ├── parseAbiItem.test-d.ts │ │ │ ├── parseAbiItem.test.ts │ │ │ ├── parseAbiItem.ts │ │ │ ├── parseAbiParameter.bench.ts │ │ │ ├── parseAbiParameter.test-d.ts │ │ │ ├── parseAbiParameter.test.ts │ │ │ ├── parseAbiParameter.ts │ │ │ ├── parseAbiParameters.test-d.ts │ │ │ ├── parseAbiParameters.test.ts │ │ │ ├── parseAbiParameters.ts │ │ │ ├── runtime │ │ │ │ ├── cache.ts │ │ │ │ ├── signatures.test.ts │ │ │ │ ├── signatures.ts │ │ │ │ ├── structs.test.ts │ │ │ │ ├── structs.ts │ │ │ │ ├── utils.test.ts │ │ │ │ └── utils.ts │ │ │ └── types │ │ │ │ ├── signatures.test-d.ts │ │ │ │ ├── signatures.ts │ │ │ │ ├── structs.test-d.ts │ │ │ │ ├── structs.ts │ │ │ │ ├── utils.test-d.ts │ │ │ │ └── utils.ts │ │ ├── narrow.test-d.ts │ │ ├── narrow.test.ts │ │ ├── narrow.ts │ │ ├── regex.ts │ │ ├── register.test-d.ts │ │ ├── register.ts │ │ ├── types.test-d.ts │ │ ├── types.ts │ │ ├── utils.bench-d.ts │ │ ├── utils.test-d.ts │ │ ├── utils.ts │ │ ├── version.ts │ │ ├── zod.test-d.ts │ │ ├── zod.test.ts │ │ └── zod.ts │ ├── test │ │ ├── globalSetup.ts │ │ └── setup.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── register-tests │ └── default │ ├── package.json │ ├── src │ ├── address.test-d.ts │ └── register.ts │ └── tsconfig.json ├── playgrounds ├── functions │ ├── package.json │ ├── src │ │ ├── read.test-d.ts │ │ ├── read.ts │ │ ├── reads.test-d.ts │ │ ├── reads.ts │ │ ├── signTypedData.test-d.ts │ │ ├── signTypedData.ts │ │ ├── types.ts │ │ ├── watchEvent.test-d.ts │ │ ├── watchEvent.ts │ │ ├── write.test-d.ts │ │ └── write.ts │ └── tsconfig.json └── performance │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── formatPackageJson.ts ├── generateProxyPackages.ts ├── preconstruct.ts ├── restorePackageJson.ts └── updateVersion.ts ├── tsconfig.base.json ├── tsconfig.json └── vitest.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.1.0/schema.json", 3 | "access": "public", 4 | "baseBranch": "main", 5 | "changelog": ["@changesets/changelog-github", { "repo": "wevm/abitype" }], 6 | "commit": false, 7 | "ignore": ["default-register", "docs", "functions", "performance"], 8 | "updateInternalDependencies": "patch", 9 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { 10 | "onlyUpdatePeerDependentsWhenOutOfRange": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report bugs or issues. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for taking the time to fill out this bug report! The more info you provide, the more we can help you. 8 | 9 | If you are a [Wevm Sponsor](https://github.com/sponsors/wevm?metadata_campaign=gh_issue), your issues are prioritized. 10 | 11 | - type: textarea 12 | attributes: 13 | label: Describe the bug 14 | description: Clear and concise description of the bug. If you intend to submit a PR for this issue, tell us in the description. Thanks! 15 | placeholder: I am doing… What I expect is… What is actually happening… 16 | validations: 17 | required: true 18 | 19 | - type: input 20 | id: reproduction 21 | attributes: 22 | label: Link to Minimal Reproducible Example 23 | description: "Please provide a link that can reproduce the problem: GitHub repository for runtime issues or [TypeScript Playground](https://tsplay.dev/wjQvkW) 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." 24 | placeholder: https://tsplay.dev/wjQvkW 25 | validations: 26 | required: false 27 | 28 | - type: textarea 29 | attributes: 30 | label: Steps To Reproduce 31 | description: Steps or code snippets to reproduce the behavior. 32 | validations: 33 | required: false 34 | 35 | - type: input 36 | attributes: 37 | label: Package Version 38 | description: What version of abitype are you using? 39 | placeholder: x.y.z (do not write `latest`) 40 | validations: 41 | required: true 42 | 43 | - type: input 44 | attributes: 45 | label: TypeScript Version 46 | description: What version of TypeScript are you using? ABIType requires `typescript@>=5.0.4`. 47 | placeholder: x.y.z (do not write `latest`) 48 | validations: 49 | required: true 50 | 51 | - type: checkboxes 52 | attributes: 53 | label: Check existing issues 54 | description: By submitting this issue, you checked there isn't [already an issue](https://github.com/wevm/abitype/issues) for this bug. 55 | options: 56 | - label: I checked there isn't [already an issue](https://github.com/wevm/abitype/issues) for the bug I encountered. 57 | required: true 58 | 59 | - type: textarea 60 | attributes: 61 | label: Anything else? 62 | description: Anything that will give us more context about the issue you are encountering. 63 | validations: 64 | required: false 65 | 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question 4 | url: https://github.com/wevm/abitype/discussions/new?category=q-a 5 | about: Ask questions and discuss with other community members. 6 | - name: Feature Request 7 | url: https://github.com/wevm/abitype/discussions/new?category=ideas 8 | about: Requests features or brainstorm ideas for new functionality. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'monthly' 7 | -------------------------------------------------------------------------------- /.github/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci # needed to securely identify the workflow 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | autofix: 17 | name: autofix 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Install dependencies 24 | uses: wevm/actions/.github/actions/pnpm@main 25 | 26 | - name: Check code 27 | run: pnpm check 28 | 29 | - name: Update package versions 30 | run: pnpm version:update 31 | 32 | - name: Apply fixes 33 | uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c 34 | with: 35 | commit-message: 'ci: apply automated fixes' 36 | -------------------------------------------------------------------------------- /.github/workflows/changesets.yml: -------------------------------------------------------------------------------- 1 | name: Changesets 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | verify: 12 | name: Verify 13 | uses: ./.github/workflows/verify.yml 14 | secrets: inherit 15 | 16 | changesets: 17 | name: Publish 18 | needs: verify 19 | permissions: 20 | contents: write 21 | id-token: write 22 | pull-requests: write 23 | runs-on: ubuntu-latest 24 | timeout-minutes: 5 25 | 26 | steps: 27 | - name: Clone repository 28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 29 | with: 30 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 31 | fetch-depth: 0 32 | 33 | - name: Install dependencies 34 | uses: wevm/actions/.github/actions/pnpm@main 35 | 36 | - name: PR or publish 37 | uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc 38 | with: 39 | title: 'chore: version packages' 40 | commit: 'chore: version packages' 41 | createGithubReleases: ${{ github.ref == 'refs/heads/main' }} 42 | publish: pnpm changeset:publish 43 | version: pnpm changeset:version 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | 48 | - name: Publish prerelease 49 | run: | 50 | git reset --hard origin/main 51 | pnpm clean 52 | pnpm changeset:prepublish 53 | pnpx pkg-pr-new publish --pnpm --compact './packages/abitype' 54 | env: 55 | PKG_PR_NEW: true 56 | 57 | jsr: 58 | name: JSR 59 | needs: verify 60 | runs-on: ubuntu-latest 61 | timeout-minutes: 5 62 | permissions: 63 | contents: read 64 | id-token: write 65 | 66 | steps: 67 | - name: Clone repository 68 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 69 | 70 | - name: Install dependencies 71 | uses: wevm/actions/.github/actions/pnpm@main 72 | 73 | - name: Prepare 74 | run: pnpm version:update && cd packages/abitype && cp ../../LICENSE LICENSE 75 | 76 | - name: Publish to JSR 77 | run: cd packages/abitype && pnpx jsr publish --allow-slow-types --allow-dirty 78 | -------------------------------------------------------------------------------- /.github/workflows/issue-labeled.yml: -------------------------------------------------------------------------------- 1 | name: Issue Labeled 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | issue-labeled: 9 | if: ${{ github.repository_owner == 'wevm' }} 10 | uses: wevm/actions/.github/workflows/issue-labeled.yml@main 11 | with: 12 | needs-reproduction-body: | 13 | Hello @${{ github.event.issue.user.login }}. 14 | 15 | Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) using a [TypeScript Playground](https://www.typescriptlang.org/play) or a separate minimal GitHub repository. 16 | 17 | [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. 18 | 19 | Once a minimal reproduction is added, a team member will confirm it works, then re-open the issue. 20 | -------------------------------------------------------------------------------- /.github/workflows/lock-issue.yml: -------------------------------------------------------------------------------- 1 | name: Lock Issue 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | lock-issue: 9 | if: ${{ github.repository_owner == 'wevm' }} 10 | uses: wevm/actions/.github/workflows/lock-issue.yml@main 11 | with: 12 | issue-comment: | 13 | This issue has been locked since it has been closed for more than 14 days. 14 | 15 | If you found a concrete bug or regression related to it, please open a new [bug report](https://github.com/wevm/abitype/issues/new?template=bug_report.yml) with a reproduction against the latest ABIType version. If you have any questions or comments you can create a new [discussion thread](https://github.com/wevm/abitype/discussions). 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | on: 3 | pull_request: 4 | types: [opened, reopened, synchronize, ready_for_review] 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | verify: 12 | name: Verify 13 | uses: ./.github/workflows/verify.yml 14 | secrets: inherit 15 | 16 | preview: 17 | name: Preview 18 | needs: verify 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Clone repository 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Install dependencies 28 | uses: wevm/actions/.github/actions/pnpm@main 29 | 30 | - name: Publish preview 31 | run: | 32 | pnpm changeset:prepublish 33 | pnpx pkg-pr-new publish --pnpm --compact './packages/abitype' 34 | env: 35 | PKG_PR_NEW: true 36 | 37 | bench: 38 | name: Benchmark 39 | runs-on: ubuntu-latest 40 | timeout-minutes: 5 41 | 42 | steps: 43 | - name: Clone repository 44 | uses: actions/checkout@v4 45 | 46 | - name: Install dependencies 47 | uses: wevm/actions/.github/actions/pnpm@main 48 | 49 | - name: Run benchmarks 50 | run: pnpm bench 51 | 52 | size: 53 | name: Size 54 | runs-on: ubuntu-latest 55 | timeout-minutes: 5 56 | 57 | steps: 58 | - name: Clone repository 59 | uses: actions/checkout@v4 60 | 61 | - name: Install dependencies 62 | uses: wevm/actions/.github/actions/pnpm@main 63 | 64 | - name: Report build size 65 | uses: preactjs/compressed-size-action@v2 66 | with: 67 | pattern: 'packages/**/dist/**' 68 | repo-token: ${{ secrets.GITHUB_TOKEN }} 69 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: Verify 2 | on: 3 | workflow_call: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | check: 8 | name: Check 9 | permissions: 10 | contents: write 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | 14 | steps: 15 | - name: Clone repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Install dependencies 19 | uses: wevm/actions/.github/actions/pnpm@main 20 | 21 | - name: Check repo 22 | run: pnpm check:repo 23 | 24 | build: 25 | name: Build 26 | needs: check 27 | runs-on: ubuntu-latest 28 | timeout-minutes: 5 29 | 30 | steps: 31 | - name: Clone repository 32 | uses: actions/checkout@v4 33 | 34 | - name: Install dependencies 35 | uses: wevm/actions/.github/actions/pnpm@main 36 | 37 | - name: Build 38 | run: pnpm build 39 | 40 | - name: Publint 41 | run: pnpm test:build 42 | 43 | - name: Check for unused files, dependencies, and exports 44 | run: pnpm knip --production 45 | 46 | types: 47 | name: Types 48 | needs: check 49 | runs-on: ubuntu-latest 50 | timeout-minutes: 5 51 | strategy: 52 | matrix: 53 | version: ['5.0.4', '5.1.6', '5.2.2', '5.3.3', '5.4.5', '5.5.2', '5.6', 'latest'] 54 | 55 | steps: 56 | - name: Clone repository 57 | uses: actions/checkout@v4 58 | 59 | - name: Install dependencies 60 | uses: wevm/actions/.github/actions/pnpm@main 61 | 62 | - name: Use `typescript@${{ matrix.version }}` 63 | run: pnpm add -D -w typescript@${{ matrix.version }} 64 | 65 | - name: Link packages 66 | run: pnpm preconstruct 67 | 68 | - name: Bench types 69 | run: pnpm bench:types 70 | 71 | - name: Check types 72 | run: pnpm check:types 73 | 74 | - name: Check types (--exactOptionalPropertyTypes false) 75 | run: pnpm check:types:propertyTypes 76 | 77 | test: 78 | name: Test 79 | runs-on: ubuntu-latest 80 | timeout-minutes: 5 81 | 82 | steps: 83 | - name: Clone repository 84 | uses: actions/checkout@v4 85 | 86 | - name: Install dependencies 87 | uses: wevm/actions/.github/actions/pnpm@main 88 | 89 | - name: Run tests 90 | run: pnpm test:cov 91 | 92 | - name: Upload coverage reports to Codecov 93 | uses: codecov/codecov-action@v5 94 | with: 95 | token: ${{ secrets.CODECOV_TOKEN }} 96 | 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.local 2 | .DS_Store 3 | .attest 4 | .env 5 | .env.*local 6 | .envrc 7 | .pnpm-debug.log* 8 | bench 9 | cache 10 | coverage 11 | dist 12 | node_modules 13 | packages/abitype/abis 14 | packages/abitype/zod 15 | playgrounds/performance/out 16 | tsconfig*.tsbuildinfo 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=false 2 | enable-pre-post-scripts=true 3 | link-workspace-packages=deep 4 | provenance=true 5 | strict-peer-dependencies=false -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "biomejs.biome", 4 | "orta.vscode-twoslash-queries", 5 | "TypeHoles.ts-versions-switcher", 6 | "Vue.volar" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome", 3 | "editor.formatOnSave": true, 4 | "typescript.enablePromptUseWorkspaceTsdk": true, 5 | "typescript.preferences.importModuleSpecifier": "shortest", 6 | "typescript.tsdk": "node_modules/typescript/lib", 7 | "editor.codeActionsOnSave": { 8 | "quickfix.biome": "explicit", 9 | "source.organizeImports.biome": "explicit" 10 | }, 11 | "[javascript][javascriptreact][json][typescript][typescriptreact]": { 12 | "editor.defaultFormatter": "biomejs.biome" 13 | }, 14 | "[vue]": { 15 | "editor.defaultFormatter": "Vue.volar" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "docs", 5 | "path": "../docs" 6 | }, 7 | { 8 | "name": "packages", 9 | "path": "../packages" 10 | }, 11 | { 12 | "name": "playground", 13 | "path": "../playground" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /FUNDING.json: -------------------------------------------------------------------------------- 1 | { 2 | "drips": { 3 | "ethereum": { 4 | "ownedBy": "0xd2135CfB216b74109775236E36d4b433F1DF507B" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present weth, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/abitype/README.md -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "ignore": [ 4 | ".vocs", 5 | "CHANGELOG.md", 6 | "pnpm-lock.yaml", 7 | "trace/**", 8 | "tsconfig.*.json" 9 | ] 10 | }, 11 | "formatter": { 12 | "enabled": true, 13 | "formatWithErrors": false, 14 | "indentStyle": "space", 15 | "indentWidth": 2, 16 | "lineWidth": 80 17 | }, 18 | "linter": { 19 | "enabled": true, 20 | "rules": { 21 | "recommended": true, 22 | "complexity": { 23 | "noBannedTypes": "off" 24 | }, 25 | "correctness": { 26 | "noUnusedVariables": "error" 27 | }, 28 | "performance": { 29 | "noBarrelFile": "error", 30 | "noDelete": "off" 31 | }, 32 | "style": { 33 | "noNonNullAssertion": "off", 34 | "useShorthandArrayType": "error" 35 | }, 36 | "suspicious": { 37 | "noArrayIndexKey": "off", 38 | "noExplicitAny": "off", 39 | "noRedeclare": "off", 40 | "noShadowRestrictedNames": "off" 41 | } 42 | } 43 | }, 44 | "javascript": { 45 | "formatter": { 46 | "quoteStyle": "single", 47 | "trailingCommas": "all", 48 | "semicolons": "asNeeded" 49 | } 50 | }, 51 | "organizeImports": { 52 | "enabled": true 53 | }, 54 | "vcs": { 55 | "enabled": true, 56 | "clientKind": "git", 57 | "useIgnoreFile": true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vocs dev", 7 | "build": "vocs build", 8 | "preview": "vocs preview" 9 | }, 10 | "devDependencies": { 11 | "@ethersproject/abi": "^5.7.0", 12 | "@types/react": "^18.3.3", 13 | "@types/react-dom": "^18.3.0", 14 | "abitype": "workspace:*", 15 | "ethers": "^6.13.1", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1", 18 | "vocs": "1.0.0-alpha.17" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/pages/api/abis.md: -------------------------------------------------------------------------------- 1 | # Test [Entrypoint for test utilities and constants] 2 | 3 | ABIType exports some test utilities and constants to make playing around and testing your code easier via the `'abitype/abis'` entrypoint. 4 | 5 | ### ABIs 6 | 7 | ```ts twoslash 8 | import { 9 | customSolidityErrorsAbi, 10 | ensAbi, 11 | ensRegistryWithFallbackAbi, 12 | erc20Abi, 13 | nestedTupleArrayAbi, 14 | nounsAuctionHouseAbi, 15 | seaportAbi, 16 | wagmiMintExampleAbi, 17 | wethAbi, 18 | writingEditionsFactoryAbi, 19 | eip165Abi, 20 | } from 'abitype/abis' 21 | ``` 22 | 23 | ### Human-Readable ABIs 24 | 25 | ```ts twoslash 26 | import { 27 | customSolidityErrorsHumanReadableAbi, 28 | ensHumanReadableAbi, 29 | ensRegistryWithFallbackHumanReadableAbi, 30 | erc20HumanReadableAbi, 31 | nestedTupleArrayHumanReadableAbi, 32 | nounsAuctionHouseHumanReadableAbi, 33 | seaportHumanReadableAbi, 34 | wagmiMintExampleHumanReadableAbi, 35 | wethHumanReadableAbi, 36 | writingEditionsFactoryHumanReadableAbi, 37 | } from 'abitype/abis' 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/pages/api/types.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Types covering the Contract ABI and EIP-712 Typed Data Specifications.' 3 | --- 4 | 5 | # Types 6 | 7 | Types covering the [Contract ABI](https://docs.soliditylang.org/en/latest/abi-spec.html#json) and [EIP-712 Typed Data](https://eips.ethereum.org/EIPS/eip-712#definition-of-typed-structured-data-%F0%9D%95%8A) Specifications. 8 | 9 | ## `Abi` 10 | 11 | Type matching the [Contract ABI Specification](https://docs.soliditylang.org/en/latest/abi-spec.html#json) 12 | 13 | ```ts twoslash noplayground 14 | import { Abi } from 'abitype' 15 | ``` 16 | 17 | ## `AbiConstructor` 18 | 19 | ABI [Constructor](https://docs.soliditylang.org/en/latest/abi-spec.html#json) type 20 | 21 | ```ts twoslash noplayground 22 | import { AbiConstructor } from 'abitype' 23 | ``` 24 | 25 | ## `AbiError` 26 | 27 | ABI [Error](https://docs.soliditylang.org/en/latest/abi-spec.html#errors) type 28 | 29 | ```ts twoslash noplayground 30 | import { AbiError } from 'abitype' 31 | ``` 32 | 33 | ## `AbiEvent` 34 | 35 | ABI [Event](https://docs.soliditylang.org/en/latest/abi-spec.html#events) type 36 | 37 | ```ts twoslash noplayground 38 | import { AbiEvent } from 'abitype' 39 | ``` 40 | 41 | ## `AbiFallback` 42 | 43 | ABI [Fallback](https://docs.soliditylang.org/en/latest/abi-spec.html#json) type 44 | 45 | ```ts twoslash noplayground 46 | import { AbiFallback } from 'abitype' 47 | ``` 48 | 49 | ## `AbiFunction` 50 | 51 | ABI [Function](https://docs.soliditylang.org/en/latest/abi-spec.html#json) type 52 | 53 | ```ts twoslash noplayground 54 | import { AbiFunction } from 'abitype' 55 | ``` 56 | 57 | ## `AbiInternalType` 58 | 59 | Representation used by Solidity compiler (e.g. `'string'`, `'int256'`, `'struct Foo'`) 60 | 61 | ```ts twoslash noplayground 62 | import { AbiInternalType } from 'abitype' 63 | ``` 64 | 65 | ## `AbiItemType` 66 | 67 | `"type"` name for [`Abi`](#abi) items (e.g. `'type': 'function'` for [`AbiFunction`](#abifunction)) 68 | 69 | ```ts twoslash noplayground 70 | import { AbiInternalType } from 'abitype' 71 | ``` 72 | 73 | ## `AbiParameter` 74 | 75 | `inputs` and `outputs` item for ABI functions, errors, and constructors 76 | 77 | ```ts twoslash noplayground 78 | import { AbiParameter } from 'abitype' 79 | ``` 80 | 81 | ## `AbiEventParameter` 82 | 83 | `inputs` for ABI events 84 | 85 | ```ts twoslash noplayground 86 | import { AbiEventParameter } from 'abitype' 87 | ``` 88 | 89 | ## `AbiParameterKind` 90 | 91 | Kind of ABI parameter: `'inputs' | 'outputs'` 92 | 93 | ```ts twoslash noplayground 94 | import { AbiParameterKind } from 'abitype' 95 | ``` 96 | 97 | ## `AbiReceive` 98 | 99 | ABI [Receive](https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function) type 100 | 101 | ```ts twoslash noplayground 102 | import { AbiReceive } from 'abitype' 103 | ``` 104 | 105 | ## `AbiStateMutability` 106 | 107 | ABI Function behavior 108 | 109 | ```ts twoslash noplayground 110 | import { AbiStateMutability } from 'abitype' 111 | ``` 112 | 113 | ## `AbiType` 114 | 115 | ABI canonical [types](https://docs.soliditylang.org/en/latest/abi-spec.html#json) 116 | 117 | ```ts twoslash noplayground 118 | import { AbiType } from 'abitype' 119 | ``` 120 | 121 | ## Solidity types 122 | 123 | [Solidity types](https://docs.soliditylang.org/en/latest/abi-spec.html#types) as template strings 124 | 125 | ```ts twoslash noplayground 126 | import { 127 | SolidityAddress, 128 | SolidityArray, 129 | SolidityBool, 130 | SolidityBytes, 131 | SolidityFunction, 132 | SolidityInt, 133 | SolidityString, 134 | SolidityTuple, 135 | } from 'abitype' 136 | ``` 137 | 138 | ## `TypedData` 139 | 140 | [EIP-712](https://eips.ethereum.org/EIPS/eip-712#definition-of-typed-structured-data-%F0%9D%95%8A) Typed Data Specification 141 | 142 | ```ts twoslash noplayground 143 | import { TypedData } from 'abitype' 144 | ``` 145 | 146 | ## `TypedDataDomain` 147 | 148 | [EIP-712](https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator) Domain 149 | 150 | ```ts twoslash noplayground 151 | import { TypedDataDomain } from 'abitype' 152 | ``` 153 | 154 | ## `TypedDataParameter` 155 | 156 | Entry in `TypedData` type items 157 | 158 | ```ts twoslash noplayground 159 | import { TypedDataParameter } from 'abitype' 160 | ``` 161 | 162 | ## `TypedDataType` 163 | 164 | Subset of `AbiType` that excludes `tuple` and `function` 165 | 166 | ```ts twoslash noplayground 167 | import { TypedDataType } from 'abitype' 168 | ``` 169 | -------------------------------------------------------------------------------- /docs/pages/api/zod.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "ABIType types as Zod schemas via the `'abitype/zod'` entrypoint." 3 | --- 4 | 5 | # Zod 6 | 7 | ABIType exports the [core types](/api/types) as [Zod](https://github.com/colinhacks/zod) schemas from the `'abitype/zod'` entrypoint. 8 | 9 | ## Install 10 | 11 | Install the Zod peer dependency: 12 | 13 | :::code-group 14 | ```bash [pnpm] 15 | pnpm add zod 16 | ``` 17 | ```bash [bun] 18 | bun add zod 19 | ``` 20 | ```bash [npm] 21 | npm i zod 22 | ``` 23 | ```bash [yarn] 24 | yarn add zod 25 | ``` 26 | ::: 27 | 28 | ## Usage 29 | 30 | Import and use schemas: 31 | 32 | ```ts twoslash 33 | import { Abi } from 'abitype/zod' 34 | 35 | const result = await fetch( 36 | 'https://api.etherscan.io/api?module=contract&action=getabi&address=0x…' 37 | ) 38 | const abi = Abi.parse(result) 39 | ``` 40 | 41 | ## Schemas 42 | 43 | ```ts twoslash 44 | import { 45 | Abi, 46 | AbiConstructor, 47 | AbiEvent, 48 | AbiEventParameter, 49 | AbiError, 50 | AbiFallback, 51 | AbiFunction, 52 | AbiParameter, 53 | Address, 54 | AbiReceive, 55 | AbiStateMutability, 56 | SolidityAddress, 57 | SolidityArray, 58 | SolidityArrayWithoutTuple, 59 | SolidityArrayWithTuple, 60 | SolidityBool, 61 | SolidityBytes, 62 | SolidityFunction, 63 | SolidityInt, 64 | SolidityString, 65 | SolidityTuple, 66 | TypedData, 67 | TypedDataDomain, 68 | TypedDataParameter, 69 | TypedDataType, 70 | } from 'abitype/zod' 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/pages/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'How to configure ABIType in userland or as a library author.' 3 | --- 4 | 5 | # Configuration 6 | 7 | How to configure ABIType in userland or as a library author. 8 | 9 | ## Overview 10 | 11 | ABIType's types are customizable using [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html). Just install `abitype` (make sure versions match) and extend the `Register` interface either directly in your code or in a `d.ts` file (e.g. `abi.d.ts`): 12 | 13 | ```ts twoslash 14 | declare module 'abitype' { 15 | export interface Register { 16 | bigIntType: bigint & { foo: 'bar' } 17 | } 18 | } 19 | 20 | import { ResolvedRegister } from 'abitype' 21 | type Result = ResolvedRegister['bigIntType'] 22 | // ^? 23 | 24 | 25 | 26 | ``` 27 | 28 | :::info[Extending Config from third-party packages] 29 | If you are using ABIType via another package (e.g. [`viem`](https://viem.sh)), you can customize the ABIType's types by targeting the package's `abitype` module: 30 | 31 | ```ts 32 | declare module 'viem/node_modules/abitype' { 33 | export interface Register { 34 | bigIntType: MyCustomBigIntType 35 | } 36 | } 37 | ``` 38 | ::: 39 | 40 | ## Options 41 | 42 | ABIType tries to strike a balance between type exhaustiveness and speed with sensible defaults. In some cases, you might want to tune your configuration (e.g. use a custom `bigint` type). To do this, the following configuration options are available: 43 | 44 | :::warning 45 | When configuring `arrayMaxDepth`, `fixedArrayMinLength`, and `fixedArrayMaxLength`, there are trade-offs. For example, choosing a non-false value for `arrayMaxDepth` and increasing the range between `fixedArrayMinLength` and `fixedArrayMaxLength` will make your types more exhaustive, but will also slow down the compiler for type checking, autocomplete, etc. 46 | ::: 47 | 48 | ### `addressType` 49 | 50 | TypeScript type to use for `address` values. 51 | 52 | - Type `any` 53 | - Default `` `0x${string}` `` 54 | 55 | ```ts twoslash 56 | declare module 'abitype' { 57 | export interface Register { 58 | addressType: `0x${string}` 59 | } 60 | } 61 | ``` 62 | 63 | ### `arrayMaxDepth` 64 | 65 | Maximum depth for nested array types (e.g. `string[][]`). When `false`, there is no maximum array depth. 66 | 67 | - Type `number | false` 68 | - Default `false` 69 | 70 | ```ts twoslash 71 | declare module 'abitype' { 72 | export interface Register { 73 | ArrayMaxDepth: false 74 | } 75 | } 76 | ``` 77 | 78 | ### `bigIntType` 79 | 80 | TypeScript type to use for `int` and `uint` values, where `M > 48`. 81 | 82 | - Type `any` 83 | - Default `bigint` 84 | 85 | ```ts twoslash 86 | declare module 'abitype' { 87 | export interface Register { 88 | bigIntType: bigint 89 | } 90 | } 91 | ``` 92 | 93 | ### `bytesType` 94 | 95 | TypeScript type to use for `bytes` values. 96 | 97 | - Type `{ inputs: any; outputs: any }` 98 | - Default `` { inputs: `0x${string}` | Uint8Array; outputs: `0x${string}` } `` 99 | 100 | ```ts twoslash 101 | declare module 'abitype' { 102 | export interface Register { 103 | bytesType: { 104 | inputs: `0x${string}` 105 | outputs: `0x${string}` 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | ### `fixedArrayMinLength` 112 | 113 | Lower bound for fixed-length arrays. 114 | 115 | - Type `number` 116 | - Default `1` 117 | 118 | ```ts twoslash 119 | declare module 'abitype' { 120 | export interface Register { 121 | FixedArrayMinLength: 1 122 | } 123 | } 124 | ``` 125 | 126 | ### `fixedArrayMaxLength` 127 | 128 | Upper bound for fixed-length arrays. 129 | 130 | - Type `number` 131 | - Default `99` 132 | 133 | ```ts twoslash 134 | declare module 'abitype' { 135 | export interface Register { 136 | FixedArrayMinLength: 99 137 | } 138 | } 139 | ``` 140 | 141 | ### `intType` 142 | 143 | TypeScript type to use for `int` and `uint` values, where `M <= 48`. 144 | 145 | - Type `any` 146 | - Default `number` 147 | 148 | ```ts twoslash 149 | declare module 'abitype' { 150 | export interface Register { 151 | intType: number 152 | } 153 | } 154 | ``` 155 | 156 | ### `strictAbiType` 157 | 158 | When set, validates `AbiParameter`'s `type` against `AbiType`. 159 | 160 | - Type `boolean` 161 | - Default `false` 162 | 163 | ```ts twoslash 164 | declare module 'abitype' { 165 | export interface Register { 166 | strictAbiType: false 167 | } 168 | } 169 | ``` 170 | 171 | :::warning 172 | You probably only want to set this to `true` if parsed types are returning as `unknown` and you want to figure out why. This will slow down type checking significantly. 173 | ::: 174 | -------------------------------------------------------------------------------- /docs/pages/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started [Quickly add ABIType to your TypeScript project] 2 | 3 | This section will help you start using ABIType in your TypeScript project. You can also try ABIType online in a [TypeScript Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgUQB4ygQwMYwIIBGwAYgK4B2uwE5ANCulroSRVTQHKYgCmAzvRYAFTFl4weUPgBUIQqKGAxgANx7SAnmH5wAvnABmUCCDgByTERhaeZgFChIsRHEnYATAAYWew8dMWVjYA9BJ8MPZ21tpwZJTKnNw6ALwMGDj4RHHs5Fy8fAA80TwQBq5QHt5E9GYqwDwA7mYAfHbBwXCdAHoA-HDFsWwJuUl8cKkARHwaIAQQADYTcAA+cBPkSUurE5jz8xANmJQ8W2sEu0fYPADyBqcTACY82KC7fPcwEDC7AMqkYGB5hoJnYojY4NIsOQ+AZJABJchgUgwTTaMapYSiJISKSyeSKZRqVH8Ap2TrtOC9frgyFHGHwxHI4nouBQHiYB40IFwADaAANPKgACQIcIKcgAc10fPoRAlwHIMAAumS0kxMqx4tRyEUbKVypUWDV0tDYVAWjyzAqkTA+GYVa0gA). 4 | 5 | ## Install 6 | 7 | :::code-group 8 | ```bash [pnpm] 9 | pnpm add abitype 10 | ``` 11 | ```bash [bun] 12 | bun add abitype 13 | ``` 14 | ```bash [npm] 15 | npm i abitype 16 | ``` 17 | ```bash [yarn] 18 | yarn add abitype 19 | ``` 20 | ::: 21 | 22 | :::info[TypeScript Version] 23 | ABIType requires `typescript@>=5.0.4`. 24 | ::: 25 | 26 | ## Usage 27 | 28 | Since ABIs can contain deeply nested arrays and objects, you must either assert ABIs to constants using [`const` assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) or use the built-in `narrow` function (works with JavaScript). This allows TypeScript to take the most specific type for expressions and avoid type widening (e.g. no going from `"hello"` to `string`). 29 | 30 | ```ts 31 | const erc20Abi = [...] as const 32 | const erc20Abi = [...] 33 | ``` 34 | 35 | ```ts 36 | import { narrow } from 'abitype' 37 | const erc20Abi = narrow([...]) 38 | ``` 39 | 40 | Once your ABIs are set up correctly, you can use the exported [types](/api/types) and [utilities](/api/utilities) to work with them. You can also import already set-up ABIs from the `abitype/abis` entrypoint to get started quickly. 41 | 42 | ```ts twoslash 43 | import { ExtractAbiFunctionNames } from 'abitype' 44 | import { erc20Abi } from 'abitype/abis' 45 | 46 | type Result = ExtractAbiFunctionNames 47 | // ^? 48 | 49 | 50 | ``` 51 | 52 | ## What's next? 53 | 54 | After setting up your project with ABIType, you are ready to dive in further! Here are some places to start: 55 | 56 | - [Learn about the types](/api/types) and [utilities](/api/utilities) available in ABIType. 57 | - Follow along with a [walkthrough](/guide/walkthrough) on building a type-safe `readContract` function. 58 | - Check out comparisons between features in [ABIType and TypeChain](/guide/comparisons#typechain) as well as [ABIType and ethers.js](/guide/comparisons#ethers-js). 59 | - Make reading and writing ABIs more human with [human-readable ABI support](/api/human). 60 | -------------------------------------------------------------------------------- /docs/pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'ABIType: Strict TypeScript types for Ethereum ABIs' 3 | --- 4 | 5 |

6 | ABIType 7 | ABIType 8 |

9 | 10 | 51 | 52 | Strict TypeScript types for Ethereum ABIs. ABIType provides utilities and type definitions for ABI properties and values, covering the [Contract ABI Specification](https://docs.soliditylang.org/en/latest/abi-spec.html), as well as [EIP-712](https://eips.ethereum.org/EIPS/eip-712) Typed Data. 53 | 54 | ```ts twoslash 55 | import { 56 | AbiParametersToPrimitiveTypes, 57 | ExtractAbiFunction 58 | } from 'abitype' 59 | import { erc20Abi } from 'abitype/abis' 60 | 61 | type TransferInputTypes = AbiParametersToPrimitiveTypes< 62 | // ^? 63 | 64 | 65 | ExtractAbiFunction['inputs'] 66 | > 67 | ``` 68 | 69 | Works great for adding blazing fast [autocomplete](https://twitter.com/awkweb/status/1555678944770367493) and type checking to functions, variables, or your own types. No need to generate types with third-party tools – just use your ABI and let TypeScript do the rest! 70 | 71 | ## TL;DR 72 | 73 | ABIType might be a good option for your project if: 74 | 75 | - You want to [typecheck](/api/types) your ABIs or EIP-712 Typed Data. 76 | - You want to add type inference and autocomplete to your library based on user-provided ABIs or EIP-712 Typed Data, like [Wagmi](https://wagmi.sh) and [Viem](https://viem.sh). 77 | - You need to [convert ABI types](/api/utilities#abiparameterstoprimitivetypes) (e.g. `'string'`) to TypeScript types (e.g. `string`) or other type transformations. 78 | - You need to validate ABIs at [runtime](/api/zod) (e.g. after fetching from external resource). 79 | - You don’t want to set up a build process to generate types (e.g. TypeChain). 80 | 81 | ## Install 82 | 83 | Read the [Getting Started](/guide/getting-started) guide to learn more how to use ABIType. 84 | 85 | :::code-group 86 | 87 | ```bash [pnpm] 88 | pnpm add abitype 89 | ``` 90 | ```bash [bun] 91 | bun add abitype 92 | ``` 93 | ```bash [npm] 94 | npm i abitype 95 | ``` 96 | ```bash [yarn] 97 | yarn add abitype 98 | ``` 99 | 100 | ::: 101 | 102 | ## Sponsor 103 | 104 | If you find ABIType useful, please consider supporting development on [GitHub Sponsors](https://github.com/sponsors/wevm?metadata_campaign=abitype_docs) or sending crypto to `wevm.eth`. Thank you 🙏 105 | 106 | ## Community 107 | 108 | If you have questions or need help, reach out to the community at the [ABIType GitHub Discussions](https://github.com/wevm/abitype/discussions). 109 | 110 | 111 |
112 |
113 | 114 | 115 | Powered by Vercel 116 | 117 | 118 | -------------------------------------------------------------------------------- /docs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wevm/abitype/d153cd678b32783235a6dadd4901262b395eee9a/docs/public/favicon.png -------------------------------------------------------------------------------- /docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/public/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/public/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wevm/abitype/d153cd678b32783235a6dadd4901262b395eee9a/docs/public/og.png -------------------------------------------------------------------------------- /docs/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /docs/sidebar.ts: -------------------------------------------------------------------------------- 1 | import type { Sidebar } from 'vocs' 2 | 3 | export const sidebar = { 4 | '/': [ 5 | { 6 | text: 'Guide', 7 | items: [ 8 | { 9 | text: 'What is ABIType?', 10 | link: '/', 11 | }, 12 | { 13 | text: 'Getting Started', 14 | link: '/guide/getting-started', 15 | }, 16 | { 17 | text: 'Walkthrough', 18 | link: '/guide/walkthrough', 19 | }, 20 | { 21 | text: 'Comparisons', 22 | link: '/guide/comparisons', 23 | }, 24 | ], 25 | }, 26 | { 27 | text: 'API', 28 | items: [ 29 | { 30 | text: 'Types', 31 | link: '/api/types', 32 | }, 33 | { 34 | text: 'Utilities', 35 | link: '/api/utilities', 36 | }, 37 | { 38 | text: 'Human-Readable', 39 | link: '/api/human', 40 | }, 41 | { 42 | text: 'ABIs', 43 | link: '/api/abis', 44 | }, 45 | { 46 | text: 'Zod', 47 | link: '/api/zod', 48 | }, 49 | ], 50 | }, 51 | { 52 | text: 'Config', 53 | items: [ 54 | { 55 | text: 'Reference', 56 | link: '/config', 57 | }, 58 | ], 59 | }, 60 | ], 61 | } satisfies Sidebar 62 | -------------------------------------------------------------------------------- /docs/styles.css: -------------------------------------------------------------------------------- 1 | .dark [data-light] { 2 | display: none; 3 | } 4 | 5 | html:not(.dark) [data-dark] { 6 | display: none; 7 | } 8 | -------------------------------------------------------------------------------- /docs/version.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module' 2 | 3 | const require = createRequire(import.meta.url) 4 | const pkg = require('../packages/abitype/package.json') 5 | 6 | export const version = pkg.version 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "bench": "vitest bench", 6 | "bench:types": "TYPES=true vitest bench-d.ts", 7 | "build": "pnpm run --r --filter \"./packages/**\" build", 8 | "changeset:prepublish": "pnpm version:update && pnpm build && bun scripts/formatPackageJson.ts && bun scripts/generateProxyPackages.ts", 9 | "changeset:publish": "pnpm changeset:prepublish && changeset publish", 10 | "changeset:version": "changeset version && pnpm version:update && pnpm format", 11 | "check": "biome check --write", 12 | "check:repo": "sherif", 13 | "check:types": "pnpm run --r --parallel check:types && tsc --noEmit", 14 | "check:types:propertyTypes": "pnpm run --r --parallel typecheck --exactOptionalPropertyTypes false && tsc --noEmit --exactOptionalPropertyTypes false", 15 | "check:unused": "pnpm clean && knip", 16 | "clean": "pnpm run --r --parallel clean && rm -rf packages/**/*.json.tmp", 17 | "deps": "pnpx taze -r", 18 | "docs:dev": "pnpm -r --filter docs dev", 19 | "format": "biome format --write", 20 | "postinstall": "pnpm preconstruct", 21 | "preconstruct": "bun scripts/preconstruct.ts", 22 | "preinstall": "pnpx only-allow pnpm", 23 | "prepare": "pnpm simple-git-hooks", 24 | "test": "vitest", 25 | "test:build": "pnpm run --r --parallel test:build", 26 | "test:cov": "vitest run --coverage", 27 | "test:update": "vitest --update", 28 | "trace": "tsc --noEmit --generateTrace ./playgrounds/performance/out --incremental false --project playgrounds/performance/tsconfig.json && echo \"Open playgrounds/performance/out/trace.json in https://ui.perfetto.dev\"", 29 | "typeperf": "pnpm run --r --parallel typeperf", 30 | "version:update": "bun scripts/updateVersion.ts" 31 | }, 32 | "devDependencies": { 33 | "@arktype/attest": "0.8.0", 34 | "@biomejs/biome": "1.9.4", 35 | "@changesets/changelog-github": "^0.5.0", 36 | "@changesets/cli": "2.27.10", 37 | "@ethersproject/abi": "^5.7.0", 38 | "@types/bun": "^1.1.10", 39 | "@vitest/coverage-v8": "^1.6.0", 40 | "bun": "^1.1.29", 41 | "ethers": "^6.13.1", 42 | "glob": "^10.4.2", 43 | "knip": "^5.22.3", 44 | "publint": "^0.2.12", 45 | "sherif": "^0.9.0", 46 | "simple-git-hooks": "^2.11.1", 47 | "typescript": "5.7.2", 48 | "vitest": "^1.6.0" 49 | }, 50 | "packageManager": "pnpm@10.11.0", 51 | "engines": { 52 | "node": "22.x" 53 | }, 54 | "simple-git-hooks": { 55 | "pre-commit": "pnpm check" 56 | }, 57 | "knip": { 58 | "ignoreBinaries": ["dev", "only-allow"], 59 | "ignoreWorkspaces": ["packages/register-tests/**", "playgrounds/**"], 60 | "workspaces": { 61 | ".": { 62 | "project": "scripts/*.ts" 63 | }, 64 | "docs": { 65 | "project": ["**/*.ts", "**/*.tsx"] 66 | }, 67 | "packages/abitype": { 68 | "entry": ["src/exports/{abis,index,zod}.ts!"], 69 | "project": ["src/**/*.ts", "test/**/*.ts"] 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/abitype/jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wevm/abitype", 3 | "version": "1.0.8", 4 | "license": "MIT", 5 | "exports": { 6 | ".": "./src/exports/index.ts", 7 | "./abis": "./src/exports/abis.ts", 8 | "./zod": "./src/exports/zod.ts" 9 | }, 10 | "publish": { 11 | "include": [ 12 | "LICENSE", 13 | "README.md", 14 | "CHANGELOG.md", 15 | "src/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "src/**/*.bench.ts", 19 | "src/**/*.bench-d.ts", 20 | "src/**/*.test.ts", 21 | "src/**/*.test-d.ts" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /packages/abitype/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "abitype", 3 | "description": "Strict TypeScript types for Ethereum ABIs", 4 | "version": "1.0.8", 5 | "license": "MIT", 6 | "repository": "wevm/abitype", 7 | "scripts": { 8 | "build": "pnpm run clean && pnpm run build:cjs && pnpm run build:esm+types", 9 | "build:cjs": "tsc --project tsconfig.build.json --module commonjs --moduleResolution node10 --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'", 10 | "build:esm+types": "tsc --project tsconfig.build.json --module nodenext --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types && echo > ./dist/esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'", 11 | "check:types": "tsc --noEmit", 12 | "clean": "rm -rf dist tsconfig.tsbuildinfo abis zod", 13 | "test:build": "publint --strict", 14 | "typeperf": "tsc --noEmit --extendedDiagnostics --composite false --incremental false" 15 | }, 16 | "files": [ 17 | "dist", 18 | "/abis", 19 | "/zod", 20 | "src/**/*.ts", 21 | "!dist/**/*.tsbuildinfo", 22 | "!jsr.json", 23 | "!src/**/*.bench.ts", 24 | "!src/**/*.bench-d.ts", 25 | "!src/**/*.test.ts", 26 | "!src/**/*.test-d.ts" 27 | ], 28 | "sideEffects": false, 29 | "type": "module", 30 | "main": "./dist/cjs/exports/index.js", 31 | "module": "./dist/esm/exports/index.js", 32 | "types": "./dist/types/exports/index.d.ts", 33 | "typings": "./dist/types/exports/index.d.ts", 34 | "exports": { 35 | ".": { 36 | "types": "./dist/types/exports/index.d.ts", 37 | "import": "./dist/esm/exports/index.js", 38 | "default": "./dist/cjs/exports/index.js" 39 | }, 40 | "./abis": { 41 | "types": "./dist/types/exports/abis.d.ts", 42 | "import": "./dist/esm/exports/abis.js", 43 | "default": "./dist/cjs/exports/abis.js" 44 | }, 45 | "./zod": { 46 | "types": "./dist/types/exports/zod.d.ts", 47 | "import": "./dist/esm/exports/zod.js", 48 | "default": "./dist/cjs/exports/zod.js" 49 | }, 50 | "./package.json": "./package.json" 51 | }, 52 | "typesVersions": { 53 | "*": { 54 | "abis": ["./dist/types/exports/abis.d.ts"], 55 | "zod": ["./dist/types/exports/zod.d.ts"] 56 | } 57 | }, 58 | "peerDependencies": { 59 | "typescript": ">=5.0.4", 60 | "zod": "^3 >=3.22.0" 61 | }, 62 | "peerDependenciesMeta": { 63 | "typescript": { 64 | "optional": true 65 | }, 66 | "zod": { 67 | "optional": true 68 | } 69 | }, 70 | "devDependencies": { 71 | "zod": "^3.23.8" 72 | }, 73 | "contributors": ["awkweb.eth ", "jxom.eth "], 74 | "funding": "https://github.com/sponsors/wevm", 75 | "keywords": [ 76 | "abi", 77 | "eth", 78 | "ethereum", 79 | "types", 80 | "typescript", 81 | "viem", 82 | "wagmi", 83 | "web3", 84 | "wevm" 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /packages/abitype/src/errors.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { BaseError } from './errors.js' 4 | 5 | test('BaseError', () => { 6 | expect(new BaseError('An error occurred.')).toMatchInlineSnapshot(` 7 | [AbiTypeError: An error occurred. 8 | 9 | Version: abitype@x.y.z] 10 | `) 11 | 12 | expect( 13 | new BaseError('An error occurred.', { details: 'details' }), 14 | ).toMatchInlineSnapshot(` 15 | [AbiTypeError: An error occurred. 16 | 17 | Details: details 18 | Version: abitype@x.y.z] 19 | `) 20 | 21 | expect(new BaseError('', { details: 'details' })).toMatchInlineSnapshot(` 22 | [AbiTypeError: An error occurred. 23 | 24 | Details: details 25 | Version: abitype@x.y.z] 26 | `) 27 | }) 28 | 29 | test('BaseError (w/ docsPath)', () => { 30 | expect( 31 | new BaseError('An error occurred.', { 32 | details: 'details', 33 | docsPath: '/lol', 34 | }), 35 | ).toMatchInlineSnapshot(` 36 | [AbiTypeError: An error occurred. 37 | 38 | Docs: https://abitype.dev/lol 39 | Details: details 40 | Version: abitype@x.y.z] 41 | `) 42 | expect( 43 | new BaseError('An error occurred.', { 44 | cause: new BaseError('error', { docsPath: '/docs' }), 45 | }), 46 | ).toMatchInlineSnapshot(` 47 | [AbiTypeError: An error occurred. 48 | 49 | Docs: https://abitype.dev/docs 50 | Version: abitype@x.y.z] 51 | `) 52 | expect( 53 | new BaseError('An error occurred.', { 54 | cause: new BaseError('error'), 55 | docsPath: '/lol', 56 | }), 57 | ).toMatchInlineSnapshot(` 58 | [AbiTypeError: An error occurred. 59 | 60 | Docs: https://abitype.dev/lol 61 | Version: abitype@x.y.z] 62 | `) 63 | }) 64 | 65 | test('BaseError (w/ metaMessages)', () => { 66 | expect( 67 | new BaseError('An error occurred.', { 68 | details: 'details', 69 | metaMessages: ['Reason: idk', 'Cause: lol'], 70 | }), 71 | ).toMatchInlineSnapshot(` 72 | [AbiTypeError: An error occurred. 73 | 74 | Reason: idk 75 | Cause: lol 76 | 77 | Details: details 78 | Version: abitype@x.y.z] 79 | `) 80 | }) 81 | 82 | test('inherited BaseError', () => { 83 | const err = new BaseError('An error occurred.', { 84 | details: 'details', 85 | docsPath: '/lol', 86 | }) 87 | expect( 88 | new BaseError('An internal error occurred.', { 89 | cause: err, 90 | }), 91 | ).toMatchInlineSnapshot(` 92 | [AbiTypeError: An internal error occurred. 93 | 94 | Docs: https://abitype.dev/lol 95 | Details: details 96 | Version: abitype@x.y.z] 97 | `) 98 | }) 99 | 100 | test('inherited Error', () => { 101 | const err = new Error('details') 102 | expect( 103 | new BaseError('An internal error occurred.', { 104 | cause: err, 105 | docsPath: '/lol', 106 | }), 107 | ).toMatchInlineSnapshot(` 108 | [AbiTypeError: An internal error occurred. 109 | 110 | Docs: https://abitype.dev/lol 111 | Details: details 112 | Version: abitype@x.y.z] 113 | `) 114 | }) 115 | -------------------------------------------------------------------------------- /packages/abitype/src/errors.ts: -------------------------------------------------------------------------------- 1 | import type { OneOf, Pretty } from './types.js' 2 | import { version } from './version.js' 3 | 4 | type BaseErrorArgs = Pretty< 5 | { 6 | docsPath?: string | undefined 7 | metaMessages?: string[] | undefined 8 | } & OneOf<{ details?: string | undefined } | { cause?: BaseError | Error }> 9 | > 10 | 11 | export class BaseError extends Error { 12 | details: string 13 | docsPath?: string | undefined 14 | metaMessages?: string[] | undefined 15 | shortMessage: string 16 | 17 | override name = 'AbiTypeError' 18 | 19 | constructor(shortMessage: string, args: BaseErrorArgs = {}) { 20 | const details = 21 | args.cause instanceof BaseError 22 | ? args.cause.details 23 | : args.cause?.message 24 | ? args.cause.message 25 | : args.details! 26 | const docsPath = 27 | args.cause instanceof BaseError 28 | ? args.cause.docsPath || args.docsPath 29 | : args.docsPath 30 | const message = [ 31 | shortMessage || 'An error occurred.', 32 | '', 33 | ...(args.metaMessages ? [...args.metaMessages, ''] : []), 34 | ...(docsPath ? [`Docs: https://abitype.dev${docsPath}`] : []), 35 | ...(details ? [`Details: ${details}`] : []), 36 | `Version: abitype@${version}`, 37 | ].join('\n') 38 | 39 | super(message) 40 | 41 | if (args.cause) this.cause = args.cause 42 | this.details = details 43 | this.docsPath = docsPath 44 | this.metaMessages = args.metaMessages 45 | this.shortMessage = shortMessage 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/abitype/src/exports/abis.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | import * as Exports from './abis.js' 4 | 5 | it('exports', () => { 6 | expect(Object.keys(Exports)).toMatchInlineSnapshot(` 7 | [ 8 | "customSolidityErrorsHumanReadableAbi", 9 | "ensHumanReadableAbi", 10 | "ensRegistryWithFallbackHumanReadableAbi", 11 | "erc20HumanReadableAbi", 12 | "nestedTupleArrayHumanReadableAbi", 13 | "nounsAuctionHouseHumanReadableAbi", 14 | "seaportHumanReadableAbi", 15 | "wagmiMintExampleHumanReadableAbi", 16 | "wethHumanReadableAbi", 17 | "writingEditionsFactoryHumanReadableAbi", 18 | "eip165HumanReadableAbi", 19 | "customSolidityErrorsAbi", 20 | "ensAbi", 21 | "ensRegistryWithFallbackAbi", 22 | "erc20Abi", 23 | "nestedTupleArrayAbi", 24 | "nounsAuctionHouseAbi", 25 | "seaportAbi", 26 | "wagmiMintExampleAbi", 27 | "wethAbi", 28 | "writingEditionsFactoryAbi", 29 | "eip165Abi", 30 | ] 31 | `) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/abitype/src/exports/abis.ts: -------------------------------------------------------------------------------- 1 | // biome-ignore lint/performance/noBarrelFile: 2 | export { 3 | customSolidityErrorsHumanReadableAbi, 4 | ensHumanReadableAbi, 5 | ensRegistryWithFallbackHumanReadableAbi, 6 | erc20HumanReadableAbi, 7 | nestedTupleArrayHumanReadableAbi, 8 | nounsAuctionHouseHumanReadableAbi, 9 | seaportHumanReadableAbi, 10 | wagmiMintExampleHumanReadableAbi, 11 | wethHumanReadableAbi, 12 | writingEditionsFactoryHumanReadableAbi, 13 | eip165HumanReadableAbi, 14 | } from '../abis/human-readable.js' 15 | 16 | export { 17 | customSolidityErrorsAbi, 18 | ensAbi, 19 | ensRegistryWithFallbackAbi, 20 | erc20Abi, 21 | nestedTupleArrayAbi, 22 | nounsAuctionHouseAbi, 23 | seaportAbi, 24 | wagmiMintExampleAbi, 25 | wethAbi, 26 | writingEditionsFactoryAbi, 27 | eip165Abi, 28 | } from '../abis/json.js' 29 | -------------------------------------------------------------------------------- /packages/abitype/src/exports/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | import * as Exports from './index.js' 4 | 5 | it('exports', () => { 6 | expect(Object.keys(Exports)).toMatchInlineSnapshot(` 7 | [ 8 | "BaseError", 9 | "narrow", 10 | "formatAbi", 11 | "formatAbiItem", 12 | "formatAbiParameter", 13 | "formatAbiParameters", 14 | "parseAbi", 15 | "parseAbiItem", 16 | "parseAbiParameter", 17 | "parseAbiParameters", 18 | "UnknownTypeError", 19 | "InvalidAbiItemError", 20 | "UnknownSolidityTypeError", 21 | "InvalidAbiTypeParameterError", 22 | "InvalidFunctionModifierError", 23 | "InvalidModifierError", 24 | "SolidityProtectedKeywordError", 25 | "InvalidParameterError", 26 | "InvalidAbiParametersError", 27 | "InvalidAbiParameterError", 28 | "InvalidStructSignatureError", 29 | "InvalidSignatureError", 30 | "UnknownSignatureError", 31 | "InvalidParenthesisError", 32 | "CircularReferenceError", 33 | ] 34 | `) 35 | }) 36 | -------------------------------------------------------------------------------- /packages/abitype/src/exports/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | Abi, 3 | AbiConstructor, 4 | AbiError, 5 | AbiEvent, 6 | AbiEventParameter, 7 | AbiFallback, 8 | AbiFunction, 9 | AbiInternalType, 10 | AbiItemType, 11 | AbiParameter, 12 | AbiParameterKind, 13 | AbiReceive, 14 | AbiStateMutability, 15 | AbiType, 16 | Address, 17 | SolidityAddress, 18 | SolidityArray, 19 | SolidityArrayWithoutTuple, 20 | SolidityArrayWithTuple, 21 | SolidityBool, 22 | SolidityBytes, 23 | SolidityFixedArrayRange, 24 | SolidityFixedArraySizeLookup, 25 | SolidityFunction, 26 | SolidityInt, 27 | SolidityString, 28 | SolidityTuple, 29 | TypedData, 30 | TypedDataDomain, 31 | TypedDataParameter, 32 | TypedDataType, 33 | } from '../abi.js' 34 | 35 | // biome-ignore lint/performance/noBarrelFile: 36 | export { BaseError } from '../errors.js' 37 | 38 | export type { Narrow } from '../narrow.js' 39 | export { narrow } from '../narrow.js' 40 | 41 | export type { 42 | Register, 43 | DefaultRegister, 44 | ResolvedRegister, 45 | } from '../register.js' 46 | 47 | export type { 48 | AbiParameterToPrimitiveType, 49 | AbiParametersToPrimitiveTypes, 50 | AbiTypeToPrimitiveType, 51 | ExtractAbiError, 52 | ExtractAbiErrorNames, 53 | ExtractAbiErrors, 54 | ExtractAbiEvent, 55 | ExtractAbiEventNames, 56 | ExtractAbiEvents, 57 | ExtractAbiFunction, 58 | ExtractAbiFunctionNames, 59 | ExtractAbiFunctions, 60 | IsAbi, 61 | IsTypedData, 62 | TypedDataToPrimitiveTypes, 63 | } from '../utils.js' 64 | 65 | //////////////////////////////////////////////////////////////////////////////////////////////////// 66 | // Human-Readable 67 | 68 | export { 69 | formatAbi, 70 | type FormatAbi, 71 | } from '../human-readable/formatAbi.js' 72 | 73 | export { 74 | formatAbiItem, 75 | type FormatAbiItem, 76 | } from '../human-readable/formatAbiItem.js' 77 | 78 | export { 79 | formatAbiParameter, 80 | type FormatAbiParameter, 81 | } from '../human-readable/formatAbiParameter.js' 82 | 83 | export { 84 | formatAbiParameters, 85 | type FormatAbiParameters, 86 | } from '../human-readable/formatAbiParameters.js' 87 | 88 | export { parseAbi, type ParseAbi } from '../human-readable/parseAbi.js' 89 | 90 | export { 91 | parseAbiItem, 92 | type ParseAbiItem, 93 | } from '../human-readable/parseAbiItem.js' 94 | 95 | export { 96 | parseAbiParameter, 97 | type ParseAbiParameter, 98 | } from '../human-readable/parseAbiParameter.js' 99 | 100 | export { 101 | parseAbiParameters, 102 | type ParseAbiParameters, 103 | } from '../human-readable/parseAbiParameters.js' 104 | 105 | export { 106 | UnknownTypeError, 107 | InvalidAbiItemError, 108 | UnknownSolidityTypeError, 109 | } from '../human-readable/errors/abiItem.js' 110 | 111 | export { 112 | InvalidAbiTypeParameterError, 113 | InvalidFunctionModifierError, 114 | InvalidModifierError, 115 | SolidityProtectedKeywordError, 116 | InvalidParameterError, 117 | InvalidAbiParametersError, 118 | InvalidAbiParameterError, 119 | } from '../human-readable/errors/abiParameter.js' 120 | 121 | export { 122 | InvalidStructSignatureError, 123 | InvalidSignatureError, 124 | UnknownSignatureError, 125 | } from '../human-readable/errors/signature.js' 126 | 127 | export { InvalidParenthesisError } from '../human-readable/errors/splitParameters.js' 128 | 129 | export { CircularReferenceError } from '../human-readable/errors/struct.js' 130 | -------------------------------------------------------------------------------- /packages/abitype/src/exports/zod.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | import * as Exports from './zod.js' 4 | 5 | it('exports', () => { 6 | expect(Object.keys(Exports)).toMatchInlineSnapshot(` 7 | [ 8 | "Address", 9 | "SolidityAddress", 10 | "SolidityBool", 11 | "SolidityBytes", 12 | "SolidityFunction", 13 | "SolidityString", 14 | "SolidityTuple", 15 | "SolidityInt", 16 | "SolidityArrayWithoutTuple", 17 | "SolidityArrayWithTuple", 18 | "SolidityArray", 19 | "AbiParameter", 20 | "AbiEventParameter", 21 | "AbiStateMutability", 22 | "AbiFunction", 23 | "AbiConstructor", 24 | "AbiFallback", 25 | "AbiReceive", 26 | "AbiEvent", 27 | "AbiError", 28 | "AbiItemType", 29 | "Abi", 30 | "TypedDataDomain", 31 | "TypedDataType", 32 | "TypedDataParameter", 33 | "TypedData", 34 | ] 35 | `) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/abitype/src/exports/zod.ts: -------------------------------------------------------------------------------- 1 | // biome-ignore lint/performance/noBarrelFile: 2 | export { 3 | Address, 4 | SolidityAddress, 5 | SolidityBool, 6 | SolidityBytes, 7 | SolidityFunction, 8 | SolidityString, 9 | SolidityTuple, 10 | SolidityInt, 11 | SolidityArrayWithoutTuple, 12 | SolidityArrayWithTuple, 13 | SolidityArray, 14 | AbiParameter, 15 | AbiEventParameter, 16 | AbiStateMutability, 17 | AbiFunction, 18 | AbiConstructor, 19 | AbiFallback, 20 | AbiReceive, 21 | AbiEvent, 22 | AbiError, 23 | AbiItemType, 24 | Abi, 25 | TypedDataDomain, 26 | TypedDataType, 27 | TypedDataParameter, 28 | TypedData, 29 | } from '../zod.js' 30 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/abiItem.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { 4 | InvalidAbiItemError, 5 | UnknownSolidityTypeError, 6 | UnknownTypeError, 7 | } from './abiItem.js' 8 | 9 | test('InvalidAbiItemError', () => { 10 | expect( 11 | new InvalidAbiItemError({ signature: 'address' }), 12 | ).toMatchInlineSnapshot(` 13 | [InvalidAbiItemError: Failed to parse ABI item. 14 | 15 | Docs: https://abitype.dev/api/human#parseabiitem-1 16 | Details: parseAbiItem("address") 17 | Version: abitype@x.y.z] 18 | `) 19 | }) 20 | 21 | test('UnknownTypeError', () => { 22 | expect(new UnknownTypeError({ type: 'Foo' })).toMatchInlineSnapshot(` 23 | [UnknownTypeError: Unknown type. 24 | 25 | Type "Foo" is not a valid ABI type. Perhaps you forgot to include a struct signature? 26 | 27 | Version: abitype@x.y.z] 28 | `) 29 | }) 30 | 31 | test('UnknownSolidityTypeError', () => { 32 | expect(new UnknownSolidityTypeError({ type: 'Foo' })).toMatchInlineSnapshot(` 33 | [UnknownSolidityTypeError: Unknown type. 34 | 35 | Type "Foo" is not a valid ABI type. 36 | 37 | Version: abitype@x.y.z] 38 | `) 39 | }) 40 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/abiItem.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from '../../errors.js' 2 | 3 | export class InvalidAbiItemError extends BaseError { 4 | override name = 'InvalidAbiItemError' 5 | 6 | constructor({ signature }: { signature: string | object }) { 7 | super('Failed to parse ABI item.', { 8 | details: `parseAbiItem(${JSON.stringify(signature, null, 2)})`, 9 | docsPath: '/api/human#parseabiitem-1', 10 | }) 11 | } 12 | } 13 | 14 | export class UnknownTypeError extends BaseError { 15 | override name = 'UnknownTypeError' 16 | 17 | constructor({ type }: { type: string }) { 18 | super('Unknown type.', { 19 | metaMessages: [ 20 | `Type "${type}" is not a valid ABI type. Perhaps you forgot to include a struct signature?`, 21 | ], 22 | }) 23 | } 24 | } 25 | 26 | export class UnknownSolidityTypeError extends BaseError { 27 | override name = 'UnknownSolidityTypeError' 28 | 29 | constructor({ type }: { type: string }) { 30 | super('Unknown type.', { 31 | metaMessages: [`Type "${type}" is not a valid ABI type.`], 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/abiParameter.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { 4 | InvalidAbiParameterError, 5 | InvalidAbiParametersError, 6 | InvalidAbiTypeParameterError, 7 | InvalidFunctionModifierError, 8 | InvalidModifierError, 9 | InvalidParameterError, 10 | SolidityProtectedKeywordError, 11 | } from './abiParameter.js' 12 | 13 | test('InvalidAbiParamterError', () => { 14 | expect( 15 | new InvalidAbiParameterError({ param: 'address owner' }), 16 | ).toMatchInlineSnapshot(` 17 | [InvalidAbiParameterError: Failed to parse ABI parameter. 18 | 19 | Docs: https://abitype.dev/api/human#parseabiparameter-1 20 | Details: parseAbiParameter("address owner") 21 | Version: abitype@x.y.z] 22 | `) 23 | }) 24 | 25 | test('InvalidAbiParamtersError', () => { 26 | expect( 27 | new InvalidAbiParametersError({ params: 'address owner' }), 28 | ).toMatchInlineSnapshot(` 29 | [InvalidAbiParametersError: Failed to parse ABI parameters. 30 | 31 | Docs: https://abitype.dev/api/human#parseabiparameters-1 32 | Details: parseAbiParameters("address owner") 33 | Version: abitype@x.y.z] 34 | `) 35 | }) 36 | 37 | test('InvalidParameterError', () => { 38 | expect( 39 | new InvalidParameterError({ 40 | param: 'address', 41 | }), 42 | ).toMatchInlineSnapshot(` 43 | [InvalidParameterError: Invalid ABI parameter. 44 | 45 | Details: address 46 | Version: abitype@x.y.z] 47 | `) 48 | }) 49 | 50 | test('SolidityProtectedKeywordError', () => { 51 | expect( 52 | new SolidityProtectedKeywordError({ 53 | param: 'address', 54 | name: 'address', 55 | }), 56 | ).toMatchInlineSnapshot(` 57 | [SolidityProtectedKeywordError: Invalid ABI parameter. 58 | 59 | "address" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html 60 | 61 | Details: address 62 | Version: abitype@x.y.z] 63 | `) 64 | }) 65 | 66 | test('InvalidModifierError', () => { 67 | expect( 68 | new InvalidModifierError({ 69 | param: 'address', 70 | modifier: 'calldata', 71 | type: 'event', 72 | }), 73 | ).toMatchInlineSnapshot(` 74 | [InvalidModifierError: Invalid ABI parameter. 75 | 76 | Modifier "calldata" not allowed in "event" type. 77 | 78 | Details: address 79 | Version: abitype@x.y.z] 80 | `) 81 | 82 | expect( 83 | new InvalidModifierError({ 84 | param: 'address', 85 | modifier: 'calldata', 86 | }), 87 | ).toMatchInlineSnapshot(` 88 | [InvalidModifierError: Invalid ABI parameter. 89 | 90 | Modifier "calldata" not allowed. 91 | 92 | Details: address 93 | Version: abitype@x.y.z] 94 | `) 95 | }) 96 | 97 | test('InvalidFunctionModifierError', () => { 98 | expect( 99 | new InvalidFunctionModifierError({ 100 | param: 'address', 101 | modifier: 'calldata', 102 | type: 'function', 103 | }), 104 | ).toMatchInlineSnapshot(` 105 | [InvalidFunctionModifierError: Invalid ABI parameter. 106 | 107 | Modifier "calldata" not allowed in "function" type. 108 | Data location can only be specified for array, struct, or mapping types, but "calldata" was given. 109 | 110 | Details: address 111 | Version: abitype@x.y.z] 112 | `) 113 | }) 114 | 115 | test('InvalidAbiTypeParameterError', () => { 116 | expect( 117 | new InvalidAbiTypeParameterError({ 118 | abiParameter: { type: 'address' }, 119 | }), 120 | ).toMatchInlineSnapshot(` 121 | [InvalidAbiTypeParameterError: Invalid ABI parameter. 122 | 123 | ABI parameter type is invalid. 124 | 125 | Details: { 126 | "type": "address" 127 | } 128 | Version: abitype@x.y.z] 129 | `) 130 | }) 131 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/abiParameter.ts: -------------------------------------------------------------------------------- 1 | import type { AbiItemType, AbiParameter } from '../../abi.js' 2 | import { BaseError } from '../../errors.js' 3 | import type { Modifier } from '../types/signatures.js' 4 | 5 | export class InvalidAbiParameterError extends BaseError { 6 | override name = 'InvalidAbiParameterError' 7 | 8 | constructor({ param }: { param: string | object }) { 9 | super('Failed to parse ABI parameter.', { 10 | details: `parseAbiParameter(${JSON.stringify(param, null, 2)})`, 11 | docsPath: '/api/human#parseabiparameter-1', 12 | }) 13 | } 14 | } 15 | 16 | export class InvalidAbiParametersError extends BaseError { 17 | override name = 'InvalidAbiParametersError' 18 | 19 | constructor({ params }: { params: string | object }) { 20 | super('Failed to parse ABI parameters.', { 21 | details: `parseAbiParameters(${JSON.stringify(params, null, 2)})`, 22 | docsPath: '/api/human#parseabiparameters-1', 23 | }) 24 | } 25 | } 26 | 27 | export class InvalidParameterError extends BaseError { 28 | override name = 'InvalidParameterError' 29 | 30 | constructor({ param }: { param: string }) { 31 | super('Invalid ABI parameter.', { 32 | details: param, 33 | }) 34 | } 35 | } 36 | 37 | export class SolidityProtectedKeywordError extends BaseError { 38 | override name = 'SolidityProtectedKeywordError' 39 | 40 | constructor({ param, name }: { param: string; name: string }) { 41 | super('Invalid ABI parameter.', { 42 | details: param, 43 | metaMessages: [ 44 | `"${name}" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html`, 45 | ], 46 | }) 47 | } 48 | } 49 | 50 | export class InvalidModifierError extends BaseError { 51 | override name = 'InvalidModifierError' 52 | 53 | constructor({ 54 | param, 55 | type, 56 | modifier, 57 | }: { 58 | param: string 59 | type?: AbiItemType | 'struct' | undefined 60 | modifier: Modifier 61 | }) { 62 | super('Invalid ABI parameter.', { 63 | details: param, 64 | metaMessages: [ 65 | `Modifier "${modifier}" not allowed${ 66 | type ? ` in "${type}" type` : '' 67 | }.`, 68 | ], 69 | }) 70 | } 71 | } 72 | 73 | export class InvalidFunctionModifierError extends BaseError { 74 | override name = 'InvalidFunctionModifierError' 75 | 76 | constructor({ 77 | param, 78 | type, 79 | modifier, 80 | }: { 81 | param: string 82 | type?: AbiItemType | 'struct' | undefined 83 | modifier: Modifier 84 | }) { 85 | super('Invalid ABI parameter.', { 86 | details: param, 87 | metaMessages: [ 88 | `Modifier "${modifier}" not allowed${ 89 | type ? ` in "${type}" type` : '' 90 | }.`, 91 | `Data location can only be specified for array, struct, or mapping types, but "${modifier}" was given.`, 92 | ], 93 | }) 94 | } 95 | } 96 | 97 | export class InvalidAbiTypeParameterError extends BaseError { 98 | override name = 'InvalidAbiTypeParameterError' 99 | 100 | constructor({ 101 | abiParameter, 102 | }: { 103 | abiParameter: AbiParameter & { indexed?: boolean | undefined } 104 | }) { 105 | super('Invalid ABI parameter.', { 106 | details: JSON.stringify(abiParameter, null, 2), 107 | metaMessages: ['ABI parameter type is invalid.'], 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/signature.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { 4 | InvalidSignatureError, 5 | InvalidStructSignatureError, 6 | UnknownSignatureError, 7 | } from './signature.js' 8 | 9 | test('InvalidSignatureError', () => { 10 | expect( 11 | new InvalidSignatureError({ 12 | signature: 'function name??()', 13 | type: 'function', 14 | }), 15 | ).toMatchInlineSnapshot(` 16 | [InvalidSignatureError: Invalid function signature. 17 | 18 | Details: function name??() 19 | Version: abitype@x.y.z] 20 | `) 21 | 22 | expect( 23 | new InvalidSignatureError({ 24 | signature: 'function name??()', 25 | type: 'struct', 26 | }), 27 | ).toMatchInlineSnapshot(` 28 | [InvalidSignatureError: Invalid struct signature. 29 | 30 | Details: function name??() 31 | Version: abitype@x.y.z] 32 | `) 33 | 34 | expect( 35 | new InvalidSignatureError({ 36 | signature: 'function name??()', 37 | type: 'error', 38 | }), 39 | ).toMatchInlineSnapshot(` 40 | [InvalidSignatureError: Invalid error signature. 41 | 42 | Details: function name??() 43 | Version: abitype@x.y.z] 44 | `) 45 | 46 | expect( 47 | new InvalidSignatureError({ 48 | signature: 'function name??()', 49 | type: 'event', 50 | }), 51 | ).toMatchInlineSnapshot(` 52 | [InvalidSignatureError: Invalid event signature. 53 | 54 | Details: function name??() 55 | Version: abitype@x.y.z] 56 | `) 57 | 58 | expect( 59 | new InvalidSignatureError({ 60 | signature: 'function name??()', 61 | type: 'constructor', 62 | }), 63 | ).toMatchInlineSnapshot(` 64 | [InvalidSignatureError: Invalid constructor signature. 65 | 66 | Details: function name??() 67 | Version: abitype@x.y.z] 68 | `) 69 | }) 70 | 71 | test('UnknownSignatureError', () => { 72 | expect( 73 | new UnknownSignatureError({ signature: 'invalid' }), 74 | ).toMatchInlineSnapshot(` 75 | [UnknownSignatureError: Unknown signature. 76 | 77 | Details: invalid 78 | Version: abitype@x.y.z] 79 | `) 80 | }) 81 | 82 | test('InvalidStructSignatureError', () => { 83 | expect( 84 | new InvalidStructSignatureError({ signature: 'struct Foo{}' }), 85 | ).toMatchInlineSnapshot(` 86 | [InvalidStructSignatureError: Invalid struct signature. 87 | 88 | No properties exist. 89 | 90 | Details: struct Foo{} 91 | Version: abitype@x.y.z] 92 | `) 93 | }) 94 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/signature.ts: -------------------------------------------------------------------------------- 1 | import type { AbiItemType } from '../../abi.js' 2 | import { BaseError } from '../../errors.js' 3 | 4 | export class InvalidSignatureError extends BaseError { 5 | override name = 'InvalidSignatureError' 6 | 7 | constructor({ 8 | signature, 9 | type, 10 | }: { 11 | signature: string 12 | type: AbiItemType | 'struct' 13 | }) { 14 | super(`Invalid ${type} signature.`, { 15 | details: signature, 16 | }) 17 | } 18 | } 19 | 20 | export class UnknownSignatureError extends BaseError { 21 | override name = 'UnknownSignatureError' 22 | 23 | constructor({ signature }: { signature: string }) { 24 | super('Unknown signature.', { 25 | details: signature, 26 | }) 27 | } 28 | } 29 | 30 | export class InvalidStructSignatureError extends BaseError { 31 | override name = 'InvalidStructSignatureError' 32 | 33 | constructor({ signature }: { signature: string }) { 34 | super('Invalid struct signature.', { 35 | details: signature, 36 | metaMessages: ['No properties exist.'], 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/splitParameters.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { InvalidParenthesisError } from './splitParameters.js' 4 | 5 | test('InvalidParenthesisError', () => { 6 | expect( 7 | new InvalidParenthesisError({ current: '(Foo))', depth: -1 }), 8 | ).toMatchInlineSnapshot(` 9 | [InvalidParenthesisError: Unbalanced parentheses. 10 | 11 | "(Foo))" has too many closing parentheses. 12 | 13 | Details: Depth "-1" 14 | Version: abitype@x.y.z] 15 | `) 16 | 17 | expect( 18 | new InvalidParenthesisError({ current: '((Foo)', depth: 1 }), 19 | ).toMatchInlineSnapshot(` 20 | [InvalidParenthesisError: Unbalanced parentheses. 21 | 22 | "((Foo)" has too many opening parentheses. 23 | 24 | Details: Depth "1" 25 | Version: abitype@x.y.z] 26 | `) 27 | }) 28 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/splitParameters.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from '../../errors.js' 2 | 3 | export class InvalidParenthesisError extends BaseError { 4 | override name = 'InvalidParenthesisError' 5 | 6 | constructor({ current, depth }: { current: string; depth: number }) { 7 | super('Unbalanced parentheses.', { 8 | metaMessages: [ 9 | `"${current.trim()}" has too many ${ 10 | depth > 0 ? 'opening' : 'closing' 11 | } parentheses.`, 12 | ], 13 | details: `Depth "${depth}"`, 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/struct.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { CircularReferenceError } from './struct.js' 4 | 5 | test('CircularReferenceError', () => { 6 | expect(new CircularReferenceError({ type: 'Foo' })).toMatchInlineSnapshot(` 7 | [CircularReferenceError: Circular reference detected. 8 | 9 | Struct "Foo" is a circular reference. 10 | 11 | Version: abitype@x.y.z] 12 | `) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/errors/struct.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from '../../errors.js' 2 | 3 | export class CircularReferenceError extends BaseError { 4 | override name = 'CircularReferenceError' 5 | 6 | constructor({ type }: { type: string }) { 7 | super('Circular reference detected.', { 8 | metaMessages: [`Struct "${type}" is a circular reference.`], 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbi.bench.ts: -------------------------------------------------------------------------------- 1 | import { Interface as InterfaceV5 } from '@ethersproject/abi' 2 | import { Interface } from 'ethers' 3 | import { bench, describe } from 'vitest' 4 | 5 | import { formatAbi } from './formatAbi.js' 6 | 7 | describe('Format ABI', () => { 8 | const abi = [ 9 | { 10 | type: 'function', 11 | name: 'name', 12 | stateMutability: 'nonpayable', 13 | inputs: [ 14 | { 15 | type: 'tuple', 16 | name: 'foo', 17 | components: [ 18 | { type: 'string', name: 'name' }, 19 | { type: 'uint256', name: 'age' }, 20 | ], 21 | }, 22 | { type: 'uint256', name: 'tokenId' }, 23 | ], 24 | outputs: [], 25 | }, 26 | { 27 | type: 'event', 28 | name: 'Foo', 29 | inputs: [{ type: 'address', name: 'bar', indexed: true }], 30 | }, 31 | ] 32 | 33 | bench('abitype', () => { 34 | formatAbi(abi) 35 | }) 36 | 37 | bench('ethers@5', () => { 38 | new InterfaceV5(abi).format('minimal') 39 | }) 40 | 41 | bench('ethers@6', () => { 42 | new Interface(abi).format(true) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbi.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, test } from 'vitest' 2 | 3 | import type { Abi } from '../abi.js' 4 | import { seaportAbi } from '../abis/json.js' 5 | import type { FormatAbi } from './formatAbi.js' 6 | import { formatAbi } from './formatAbi.js' 7 | 8 | test('FormatAbi', () => { 9 | expectTypeOf>().toEqualTypeOf() 10 | 11 | expectTypeOf< 12 | FormatAbi< 13 | readonly [ 14 | { 15 | readonly name: 'foo' 16 | readonly type: 'function' 17 | readonly stateMutability: 'nonpayable' 18 | readonly inputs: readonly [] 19 | readonly outputs: readonly [] 20 | }, 21 | { 22 | readonly name: 'bar' 23 | readonly type: 'function' 24 | readonly stateMutability: 'nonpayable' 25 | readonly inputs: readonly [ 26 | { 27 | readonly type: 'tuple' 28 | readonly components: readonly [ 29 | { 30 | readonly name: 'name' 31 | readonly type: 'string' 32 | }, 33 | ] 34 | }, 35 | { 36 | readonly type: 'bytes32' 37 | }, 38 | ] 39 | readonly outputs: readonly [] 40 | }, 41 | ] 42 | > 43 | >().toEqualTypeOf< 44 | readonly ['function foo()', 'function bar((string name), bytes32)'] 45 | >() 46 | 47 | expectTypeOf< 48 | FormatAbi< 49 | readonly [ 50 | { 51 | readonly name: 'balanceOf' 52 | readonly type: 'function' 53 | readonly stateMutability: 'view' 54 | readonly inputs: readonly [ 55 | { 56 | readonly name: 'owner' 57 | readonly type: 'address' 58 | }, 59 | ] 60 | readonly outputs: readonly [ 61 | { 62 | readonly type: 'uint256' 63 | }, 64 | ] 65 | }, 66 | { 67 | readonly name: 'Transfer' 68 | readonly type: 'event' 69 | readonly inputs: readonly [ 70 | { 71 | readonly name: 'from' 72 | readonly type: 'address' 73 | readonly indexed: true 74 | }, 75 | { 76 | readonly name: 'to' 77 | readonly type: 'address' 78 | readonly indexed: true 79 | }, 80 | { 81 | readonly name: 'amount' 82 | readonly type: 'uint256' 83 | }, 84 | ] 85 | }, 86 | ] 87 | > 88 | >().toEqualTypeOf< 89 | readonly [ 90 | 'function balanceOf(address owner) view returns (uint256)', 91 | 'event Transfer(address indexed from, address indexed to, uint256 amount)', 92 | ] 93 | >() 94 | }) 95 | 96 | test('formatAbi', () => { 97 | expectTypeOf(formatAbi([])).toEqualTypeOf() 98 | 99 | // Array 100 | const res = formatAbi([ 101 | { 102 | name: 'bar', 103 | type: 'function', 104 | stateMutability: 'nonpayable', 105 | inputs: [ 106 | { 107 | type: 'tuple', 108 | components: [ 109 | { 110 | name: 'name', 111 | type: 'string', 112 | }, 113 | ], 114 | }, 115 | { 116 | type: 'bytes32', 117 | }, 118 | ], 119 | outputs: [], 120 | }, 121 | ]) 122 | expectTypeOf().toEqualTypeOf< 123 | readonly ['function bar((string name), bytes32)'] 124 | >() 125 | 126 | const abi2 = [ 127 | { 128 | type: 'function', 129 | name: 'foo', 130 | inputs: [], 131 | outputs: [], 132 | stateMutability: 'view', 133 | }, 134 | ] 135 | expectTypeOf(formatAbi(abi2)).toEqualTypeOf() 136 | 137 | const param = abi2 as Abi 138 | expectTypeOf(formatAbi(param)).toEqualTypeOf() 139 | 140 | const getOrderType = formatAbi(seaportAbi)[10] 141 | expectTypeOf< 142 | typeof getOrderType 143 | >().toEqualTypeOf<'function getOrderHash((address offerer, address zone, (uint8 itemType, address token, uint256 identifierOrCriteria, uint256 startAmount, uint256 endAmount)[] offer, (uint8 itemType, address token, uint256 identifierOrCriteria, uint256 startAmount, uint256 endAmount, address recipient)[] consideration, uint8 orderType, uint256 startTime, uint256 endTime, bytes32 zoneHash, uint256 salt, bytes32 conduitKey, uint256 counter) order) view returns (bytes32 orderHash)'>() 144 | }) 145 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbi.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { customSolidityErrorsAbi, seaportAbi } from '../abis/json.js' 4 | import { formatAbi } from './formatAbi.js' 5 | 6 | test('formatAbi', () => { 7 | const result = formatAbi(seaportAbi) 8 | expect(result).toMatchSnapshot() 9 | 10 | expect(formatAbi(customSolidityErrorsAbi)).toMatchInlineSnapshot(` 11 | [ 12 | "constructor()", 13 | "error ApprovalCallerNotOwnerNorApproved()", 14 | "error ApprovalQueryForNonexistentToken()", 15 | ] 16 | `) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbi.ts: -------------------------------------------------------------------------------- 1 | import type { Abi } from '../abi.js' 2 | import { type FormatAbiItem, formatAbiItem } from './formatAbiItem.js' 3 | 4 | /** 5 | * Parses JSON ABI into human-readable ABI 6 | * 7 | * @param abi - ABI 8 | * @returns Human-readable ABI 9 | */ 10 | export type FormatAbi = Abi extends abi 11 | ? readonly string[] 12 | : abi extends readonly [] 13 | ? never 14 | : abi extends Abi 15 | ? { 16 | [key in keyof abi]: FormatAbiItem 17 | } 18 | : readonly string[] 19 | 20 | /** 21 | * Parses JSON ABI into human-readable ABI 22 | * 23 | * @param abi - ABI 24 | * @returns Human-readable ABI 25 | */ 26 | export function formatAbi( 27 | abi: abi, 28 | ): FormatAbi { 29 | const signatures = [] 30 | const length = abi.length 31 | for (let i = 0; i < length; i++) { 32 | const abiItem = abi[i]! 33 | const signature = formatAbiItem(abiItem as Abi[number]) 34 | signatures.push(signature) 35 | } 36 | return signatures as unknown as FormatAbi 37 | } 38 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiItem.bench.ts: -------------------------------------------------------------------------------- 1 | import { Fragment as FragmentV5 } from '@ethersproject/abi' 2 | import { Fragment } from 'ethers' 3 | import { bench, describe } from 'vitest' 4 | 5 | import { formatAbiItem } from './formatAbiItem.js' 6 | 7 | describe('Format basic ABI function', () => { 8 | const basic = { 9 | type: 'function', 10 | name: 'foo', 11 | inputs: [ 12 | { 13 | type: 'string', 14 | name: 'bar', 15 | }, 16 | { 17 | type: 'string', 18 | name: 'baz', 19 | }, 20 | ], 21 | outputs: [], 22 | stateMutability: 'nonpayable', 23 | } as const 24 | 25 | bench('abitype', () => { 26 | formatAbiItem(basic) 27 | }) 28 | 29 | bench('ethers@6', () => { 30 | Fragment.from(basic).format('minimal') 31 | }) 32 | 33 | bench('ethers@5', () => { 34 | FragmentV5.from(basic).format('minimal') 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiItem.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, test } from 'vitest' 2 | 3 | import type { 4 | Abi, 5 | AbiConstructor, 6 | AbiError, 7 | AbiEvent, 8 | AbiFallback, 9 | AbiFunction, 10 | AbiReceive, 11 | } from '../abi.js' 12 | import type { FormatAbiItem } from './formatAbiItem.js' 13 | import { formatAbiItem } from './formatAbiItem.js' 14 | 15 | test('FormatAbiItem', () => { 16 | expectTypeOf>().toEqualTypeOf() 17 | expectTypeOf>().toEqualTypeOf() 18 | expectTypeOf>().toEqualTypeOf() 19 | expectTypeOf>().toEqualTypeOf() 20 | expectTypeOf>().toEqualTypeOf() 21 | expectTypeOf>().toEqualTypeOf() 22 | expectTypeOf>().toEqualTypeOf() 23 | 24 | expectTypeOf< 25 | FormatAbiItem<{ 26 | readonly name: 'foo' 27 | readonly type: 'function' 28 | readonly stateMutability: 'nonpayable' 29 | readonly inputs: readonly [] 30 | readonly outputs: readonly [] 31 | }> 32 | >().toEqualTypeOf<'function foo()'>() 33 | 34 | expectTypeOf< 35 | FormatAbiItem<{ 36 | readonly name: 'address' 37 | readonly type: 'function' 38 | readonly stateMutability: 'nonpayable' 39 | readonly inputs: readonly [] 40 | readonly outputs: readonly [] 41 | }> 42 | >().toEqualTypeOf<'function [Error: "address" is a protected Solidity keyword.]()'>() 43 | 44 | expectTypeOf< 45 | FormatAbiItem<{ 46 | readonly name: 'Transfer' 47 | readonly type: 'event' 48 | readonly inputs: readonly [ 49 | { 50 | readonly name: 'from' 51 | readonly type: 'address' 52 | readonly indexed: true 53 | }, 54 | { 55 | readonly name: 'to' 56 | readonly type: 'address' 57 | readonly indexed: true 58 | }, 59 | { 60 | readonly name: 'amount' 61 | readonly type: 'uint256' 62 | }, 63 | ] 64 | }> 65 | >().toEqualTypeOf<'event Transfer(address indexed from, address indexed to, uint256 amount)'>() 66 | }) 67 | 68 | test('formatAbiItem', () => { 69 | expectTypeOf( 70 | formatAbiItem({ 71 | name: 'foo', 72 | type: 'function', 73 | stateMutability: 'nonpayable', 74 | inputs: [], 75 | outputs: [], 76 | }), 77 | ).toEqualTypeOf<'function foo()'>() 78 | expectTypeOf( 79 | formatAbiItem({ 80 | name: 'foo', 81 | type: 'function', 82 | stateMutability: 'nonpayable', 83 | inputs: [ 84 | { 85 | type: 'tuple', 86 | components: [ 87 | { 88 | type: 'string', 89 | }, 90 | ], 91 | }, 92 | { 93 | type: 'address', 94 | }, 95 | ], 96 | outputs: [], 97 | }), 98 | ).toEqualTypeOf<'function foo((string), address)'>() 99 | 100 | const abiItem: Abi[number] = { 101 | type: 'function', 102 | name: 'foo', 103 | inputs: [], 104 | outputs: [], 105 | stateMutability: 'nonpayable', 106 | } 107 | expectTypeOf(formatAbiItem(abiItem)).toEqualTypeOf() 108 | 109 | expectTypeOf( 110 | formatAbiItem({ type: 'fallback', stateMutability: 'nonpayable' }), 111 | ).toEqualTypeOf<'fallback() external'>() 112 | expectTypeOf( 113 | formatAbiItem({ type: 'fallback', stateMutability: 'payable' }), 114 | ).toEqualTypeOf<'fallback() external payable'>() 115 | }) 116 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiItem.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { seaportAbi } from '../abis/json.js' 4 | import { formatAbiItem } from './formatAbiItem.js' 5 | 6 | test('default', () => { 7 | const result = formatAbiItem(seaportAbi[1]) 8 | expect(result).toMatchInlineSnapshot( 9 | '"function cancel((address offerer, address zone, (uint8 itemType, address token, uint256 identifierOrCriteria, uint256 startAmount, uint256 endAmount)[] offer, (uint8 itemType, address token, uint256 identifierOrCriteria, uint256 startAmount, uint256 endAmount, address recipient)[] consideration, uint8 orderType, uint256 startTime, uint256 endTime, bytes32 zoneHash, uint256 salt, bytes32 conduitKey, uint256 counter)[] orders) returns (bool cancelled)"', 10 | ) 11 | }) 12 | 13 | test.each([ 14 | { 15 | abiItem: { 16 | type: 'function', 17 | name: 'foo', 18 | inputs: [{ type: 'string' }], 19 | outputs: [], 20 | stateMutability: 'nonpayable', 21 | } as const, 22 | expected: 'function foo(string)', 23 | }, 24 | { 25 | abiItem: { 26 | type: 'event', 27 | name: 'Foo', 28 | inputs: [ 29 | { type: 'address', name: 'from', indexed: true }, 30 | { type: 'address', name: 'to', indexed: true }, 31 | { type: 'uint256', name: 'amount' }, 32 | ], 33 | } as const, 34 | expected: 35 | 'event Foo(address indexed from, address indexed to, uint256 amount)', 36 | }, 37 | { 38 | abiItem: { 39 | type: 'constructor', 40 | stateMutability: 'nonpayable', 41 | inputs: [{ type: 'string' }], 42 | } as const, 43 | expected: 'constructor(string)', 44 | }, 45 | { 46 | abiItem: { 47 | type: 'constructor', 48 | stateMutability: 'payable', 49 | inputs: [{ type: 'string' }], 50 | } as const, 51 | expected: 'constructor(string) payable', 52 | }, 53 | { 54 | abiItem: { 55 | type: 'fallback', 56 | stateMutability: 'nonpayable', 57 | } as const, 58 | expected: 'fallback() external', 59 | }, 60 | { 61 | abiItem: { 62 | type: 'fallback', 63 | stateMutability: 'payable', 64 | } as const, 65 | expected: 'fallback() external payable', 66 | }, 67 | { 68 | abiItem: { 69 | type: 'receive', 70 | stateMutability: 'payable', 71 | } as const, 72 | expected: 'receive() external payable', 73 | }, 74 | { 75 | abiItem: { 76 | type: 'function', 77 | name: 'initWormhole', 78 | inputs: [ 79 | { 80 | type: 'tuple[]', 81 | name: 'configs', 82 | components: [ 83 | { 84 | type: 'uint256', 85 | name: 'chainId', 86 | }, 87 | { 88 | type: 'uint16', 89 | name: 'wormholeChainId', 90 | }, 91 | ], 92 | }, 93 | ], 94 | outputs: [], 95 | stateMutability: 'nonpayable', 96 | } as const, 97 | expected: 98 | 'function initWormhole((uint256 chainId, uint16 wormholeChainId)[] configs)', 99 | }, 100 | ])('formatAbiItem($expected)', ({ abiItem, expected }) => { 101 | expect(formatAbiItem(abiItem)).toEqual(expected) 102 | }) 103 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiItem.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Abi, 3 | AbiConstructor, 4 | AbiError, 5 | AbiEvent, 6 | AbiEventParameter, 7 | AbiFallback, 8 | AbiFunction, 9 | AbiParameter, 10 | AbiReceive, 11 | AbiStateMutability, 12 | } from '../abi.js' 13 | import { 14 | type FormatAbiParameters as FormatAbiParameters_, 15 | formatAbiParameters, 16 | } from './formatAbiParameters.js' 17 | import type { AssertName } from './types/signatures.js' 18 | 19 | /** 20 | * Formats ABI item (e.g. error, event, function) into human-readable ABI item 21 | * 22 | * @param abiItem - ABI item 23 | * @returns Human-readable ABI item 24 | */ 25 | export type FormatAbiItem = 26 | Abi[number] extends abiItem 27 | ? string 28 | : 29 | | (abiItem extends AbiFunction 30 | ? AbiFunction extends abiItem 31 | ? string 32 | : `function ${AssertName}(${FormatAbiParameters< 33 | abiItem['inputs'] 34 | >})${abiItem['stateMutability'] extends Exclude< 35 | AbiStateMutability, 36 | 'nonpayable' 37 | > 38 | ? ` ${abiItem['stateMutability']}` 39 | : ''}${abiItem['outputs']['length'] extends 0 40 | ? '' 41 | : ` returns (${FormatAbiParameters})`}` 42 | : never) 43 | | (abiItem extends AbiEvent 44 | ? AbiEvent extends abiItem 45 | ? string 46 | : `event ${AssertName}(${FormatAbiParameters< 47 | abiItem['inputs'] 48 | >})` 49 | : never) 50 | | (abiItem extends AbiError 51 | ? AbiError extends abiItem 52 | ? string 53 | : `error ${AssertName}(${FormatAbiParameters< 54 | abiItem['inputs'] 55 | >})` 56 | : never) 57 | | (abiItem extends AbiConstructor 58 | ? AbiConstructor extends abiItem 59 | ? string 60 | : `constructor(${FormatAbiParameters< 61 | abiItem['inputs'] 62 | >})${abiItem['stateMutability'] extends 'payable' 63 | ? ' payable' 64 | : ''}` 65 | : never) 66 | | (abiItem extends AbiFallback 67 | ? AbiFallback extends abiItem 68 | ? string 69 | : `fallback() external${abiItem['stateMutability'] extends 'payable' 70 | ? ' payable' 71 | : ''}` 72 | : never) 73 | | (abiItem extends AbiReceive 74 | ? AbiReceive extends abiItem 75 | ? string 76 | : 'receive() external payable' 77 | : never) 78 | 79 | type FormatAbiParameters< 80 | abiParameters extends readonly (AbiParameter | AbiEventParameter)[], 81 | > = abiParameters['length'] extends 0 82 | ? '' 83 | : FormatAbiParameters_< 84 | abiParameters extends readonly [ 85 | AbiParameter | AbiEventParameter, 86 | ...(readonly (AbiParameter | AbiEventParameter)[]), 87 | ] 88 | ? abiParameters 89 | : never 90 | > 91 | 92 | /** 93 | * Formats ABI item (e.g. error, event, function) into human-readable ABI item 94 | * 95 | * @param abiItem - ABI item 96 | * @returns Human-readable ABI item 97 | */ 98 | export function formatAbiItem( 99 | abiItem: abiItem, 100 | ): FormatAbiItem { 101 | type Result = FormatAbiItem 102 | type Params = readonly [ 103 | AbiParameter | AbiEventParameter, 104 | ...(readonly (AbiParameter | AbiEventParameter)[]), 105 | ] 106 | 107 | if (abiItem.type === 'function') 108 | return `function ${abiItem.name}(${formatAbiParameters( 109 | abiItem.inputs as Params, 110 | )})${ 111 | abiItem.stateMutability && abiItem.stateMutability !== 'nonpayable' 112 | ? ` ${abiItem.stateMutability}` 113 | : '' 114 | }${ 115 | abiItem.outputs?.length 116 | ? ` returns (${formatAbiParameters(abiItem.outputs as Params)})` 117 | : '' 118 | }` 119 | if (abiItem.type === 'event') 120 | return `event ${abiItem.name}(${formatAbiParameters( 121 | abiItem.inputs as Params, 122 | )})` 123 | if (abiItem.type === 'error') 124 | return `error ${abiItem.name}(${formatAbiParameters( 125 | abiItem.inputs as Params, 126 | )})` 127 | if (abiItem.type === 'constructor') 128 | return `constructor(${formatAbiParameters(abiItem.inputs as Params)})${ 129 | abiItem.stateMutability === 'payable' ? ' payable' : '' 130 | }` 131 | if (abiItem.type === 'fallback') 132 | return `fallback() external${ 133 | abiItem.stateMutability === 'payable' ? ' payable' : '' 134 | }` as Result 135 | return 'receive() external payable' as Result 136 | } 137 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiParameter.bench.ts: -------------------------------------------------------------------------------- 1 | import { ParamType as ParamTypeV5 } from '@ethersproject/abi' 2 | import { ParamType } from 'ethers' 3 | import { bench, describe } from 'vitest' 4 | 5 | import { formatAbiParameter } from './formatAbiParameter.js' 6 | 7 | describe('Format basic ABI Parameter', () => { 8 | const basic = { type: 'address', name: 'foo' } 9 | 10 | bench('abitype', () => { 11 | formatAbiParameter(basic) 12 | }) 13 | 14 | bench('ethers@6', () => { 15 | ParamType.from(basic).format('minimal') 16 | }) 17 | 18 | bench('ethers@5', () => { 19 | ParamTypeV5.from(basic).format('minimal') 20 | }) 21 | }) 22 | 23 | describe('Format inline tuple ABI Parameter', () => { 24 | const inlineTuple = { 25 | type: 'tuple', 26 | components: [ 27 | { type: 'string', name: 'bar' }, 28 | { type: 'string', name: 'baz' }, 29 | ], 30 | name: 'foo', 31 | } 32 | 33 | bench('abitype', () => { 34 | formatAbiParameter(inlineTuple) 35 | }) 36 | 37 | bench('ethers@6', () => { 38 | ParamType.from(inlineTuple).format('minimal') 39 | }) 40 | 41 | bench('ethers@5', () => { 42 | ParamTypeV5.from(inlineTuple).format('minimal') 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiParameter.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, test } from 'vitest' 2 | 3 | import type { AbiParameter } from '../abi.js' 4 | import type { FormatAbiParameter } from './formatAbiParameter.js' 5 | import { formatAbiParameter } from './formatAbiParameter.js' 6 | 7 | test('FormatAbiParameter', () => { 8 | // string 9 | expectTypeOf< 10 | FormatAbiParameter<{ 11 | readonly type: 'address' 12 | readonly name: 'from' 13 | }> 14 | >().toEqualTypeOf<'address from'>() 15 | expectTypeOf< 16 | FormatAbiParameter<{ 17 | readonly type: 'address' 18 | readonly name: 'from' 19 | readonly indexed: true 20 | }> 21 | >().toEqualTypeOf<'address indexed from'>() 22 | expectTypeOf< 23 | FormatAbiParameter<{ 24 | readonly type: 'address' 25 | readonly name: '' 26 | }> 27 | >().toEqualTypeOf<'address'>() 28 | 29 | expectTypeOf< 30 | FormatAbiParameter<{ 31 | type: 'address' 32 | name: 'address' 33 | }> 34 | >().toEqualTypeOf<'address [Error: "address" is a protected Solidity keyword.]'>() 35 | 36 | expectTypeOf< 37 | FormatAbiParameter<{ 38 | type: 'address' 39 | name: '123' 40 | }> 41 | >().toEqualTypeOf<'address [Error: Identifier "123" cannot be a number string.]'>() 42 | 43 | // Array 44 | expectTypeOf< 45 | FormatAbiParameter<{ 46 | readonly type: 'tuple' 47 | readonly components: readonly [ 48 | { 49 | readonly name: 'name' 50 | readonly type: 'string' 51 | }, 52 | ] 53 | }> 54 | >().toEqualTypeOf<'(string name)'>() 55 | 56 | expectTypeOf< 57 | FormatAbiParameter<{ 58 | readonly type: 'tuple' 59 | readonly components: readonly [ 60 | { 61 | readonly type: 'string' 62 | readonly name: 'bar' 63 | }, 64 | ] 65 | readonly name: 'foo' 66 | }> 67 | >().toEqualTypeOf<'(string bar) foo'>() 68 | 69 | expectTypeOf< 70 | FormatAbiParameter<{ 71 | readonly components: [ 72 | { 73 | readonly components: [ 74 | { 75 | readonly type: 'string' 76 | readonly name: 'foo' 77 | }, 78 | ] 79 | readonly type: 'tuple' 80 | }, 81 | ] 82 | readonly type: 'tuple' 83 | }> 84 | >().toEqualTypeOf<'((string foo))'>() 85 | 86 | expectTypeOf< 87 | FormatAbiParameter<{ 88 | readonly components: [ 89 | { 90 | readonly components: [ 91 | { 92 | readonly components: [ 93 | { 94 | readonly components: [ 95 | { 96 | readonly type: 'string' 97 | }, 98 | ] 99 | readonly type: 'tuple' 100 | }, 101 | ] 102 | readonly type: 'tuple' 103 | }, 104 | ] 105 | readonly type: 'tuple' 106 | }, 107 | ] 108 | readonly type: 'tuple' 109 | }> 110 | >().toEqualTypeOf<'((((string))))'>() 111 | }) 112 | 113 | test('formatAbiParameter', () => { 114 | expectTypeOf( 115 | formatAbiParameter({ 116 | type: 'tuple', 117 | components: [{ type: 'string' }], 118 | }), 119 | ).toEqualTypeOf<'(string)'>() 120 | 121 | const param = { type: 'address' } 122 | const param2: AbiParameter = param 123 | expectTypeOf(formatAbiParameter(param)).toEqualTypeOf() 124 | expectTypeOf(formatAbiParameter(param2)).toEqualTypeOf() 125 | }) 126 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiParameter.test.ts: -------------------------------------------------------------------------------- 1 | import { assertType, expect, expectTypeOf, test } from 'vitest' 2 | 3 | import { formatAbiParameter } from './formatAbiParameter.js' 4 | 5 | test('default', () => { 6 | const result = formatAbiParameter({ type: 'address', name: 'foo' }) 7 | expect(result).toEqual('address foo') 8 | expectTypeOf(result).toEqualTypeOf<'address foo'>() 9 | }) 10 | 11 | test('tuple', () => { 12 | const result = formatAbiParameter({ 13 | type: 'tuple', 14 | components: [ 15 | { type: 'string', name: 'bar' }, 16 | { type: 'string', name: 'baz' }, 17 | ], 18 | name: 'foo', 19 | }) 20 | expect(result).toMatchInlineSnapshot('"(string bar, string baz) foo"') 21 | expectTypeOf(result).toEqualTypeOf<'(string bar, string baz) foo'>() 22 | }) 23 | 24 | test('tuple[][]', () => { 25 | const result = formatAbiParameter({ 26 | type: 'tuple[123][]', 27 | components: [ 28 | { type: 'string', name: 'bar' }, 29 | { type: 'string', name: 'baz' }, 30 | ], 31 | name: 'foo', 32 | }) 33 | expect(result).toMatchInlineSnapshot('"(string bar, string baz)[123][] foo"') 34 | expectTypeOf(result).toEqualTypeOf<'(string bar, string baz)[123][] foo'>() 35 | }) 36 | 37 | test.each([ 38 | { 39 | abiParameter: { type: 'string' }, 40 | expected: 'string', 41 | }, 42 | { 43 | abiParameter: { name: 'foo', type: 'string' }, 44 | expected: 'string foo', 45 | }, 46 | { 47 | abiParameter: { name: 'foo', type: 'string', indexed: true }, 48 | expected: 'string indexed foo', 49 | }, 50 | { 51 | abiParameter: { type: 'tuple', components: [{ type: 'string' }] }, 52 | expected: '(string)', 53 | }, 54 | { 55 | abiParameter: { 56 | type: 'tuple', 57 | components: [{ name: 'foo', type: 'string' }], 58 | }, 59 | expected: '(string foo)', 60 | }, 61 | { 62 | abiParameter: { 63 | type: 'tuple', 64 | name: 'foo', 65 | components: [{ name: 'bar', type: 'string' }], 66 | }, 67 | expected: '(string bar) foo', 68 | }, 69 | { 70 | abiParameter: { 71 | type: 'tuple', 72 | name: 'foo', 73 | components: [ 74 | { name: 'bar', type: 'string' }, 75 | { name: 'baz', type: 'string' }, 76 | ], 77 | }, 78 | expected: '(string bar, string baz) foo', 79 | }, 80 | { 81 | abiParameter: { type: 'string', indexed: false }, 82 | expected: 'string', 83 | }, 84 | { 85 | abiParameter: { type: 'string', indexed: true }, 86 | expected: 'string indexed', 87 | }, 88 | { 89 | abiParameter: { type: 'string', indexed: true, name: 'foo' }, 90 | expected: 'string indexed foo', 91 | }, 92 | ])('formatAbiParameter($abiParameter)', ({ abiParameter, expected }) => { 93 | expect(formatAbiParameter(abiParameter)).toEqual(expected) 94 | }) 95 | 96 | test('nested tuple', () => { 97 | const result = formatAbiParameter({ 98 | components: [ 99 | { 100 | components: [ 101 | { 102 | components: [ 103 | { 104 | components: [ 105 | { 106 | name: 'baz', 107 | type: 'string', 108 | }, 109 | ], 110 | name: 'bar', 111 | type: 'tuple', 112 | }, 113 | ], 114 | name: 'foo', 115 | type: 'tuple[1]', 116 | }, 117 | ], 118 | name: 'boo', 119 | type: 'tuple', 120 | }, 121 | ], 122 | type: 'tuple', 123 | }) 124 | expect(result).toMatchInlineSnapshot('"((((string baz) bar)[1] foo) boo)"') 125 | assertType<'((((string baz) bar)[1] foo) boo)'>(result) 126 | }) 127 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiParameter.ts: -------------------------------------------------------------------------------- 1 | import type { AbiEventParameter, AbiParameter } from '../abi.js' 2 | import { execTyped } from '../regex.js' 3 | import type { IsNarrowable, Join } from '../types.js' 4 | import type { AssertName } from './types/signatures.js' 5 | 6 | /** 7 | * Formats {@link AbiParameter} to human-readable ABI parameter. 8 | * 9 | * @param abiParameter - ABI parameter 10 | * @returns Human-readable ABI parameter 11 | * 12 | * @example 13 | * type Result = FormatAbiParameter<{ type: 'address'; name: 'from'; }> 14 | * // ^? type Result = 'address from' 15 | */ 16 | export type FormatAbiParameter< 17 | abiParameter extends AbiParameter | AbiEventParameter, 18 | > = abiParameter extends { 19 | name?: infer name extends string 20 | type: `tuple${infer array}` 21 | components: infer components extends readonly AbiParameter[] 22 | indexed?: infer indexed extends boolean 23 | } 24 | ? FormatAbiParameter< 25 | { 26 | type: `(${Join< 27 | { 28 | [key in keyof components]: FormatAbiParameter< 29 | { 30 | type: components[key]['type'] 31 | } & (IsNarrowable extends true 32 | ? { name: components[key]['name'] } 33 | : unknown) & 34 | (components[key] extends { components: readonly AbiParameter[] } 35 | ? { components: components[key]['components'] } 36 | : unknown) 37 | > 38 | }, 39 | ', ' 40 | >})${array}` 41 | } & (IsNarrowable extends true ? { name: name } : unknown) & 42 | (IsNarrowable extends true 43 | ? { indexed: indexed } 44 | : unknown) 45 | > 46 | : `${abiParameter['type']}${abiParameter extends { indexed: true } 47 | ? ' indexed' 48 | : ''}${abiParameter['name'] extends infer name extends string 49 | ? name extends '' 50 | ? '' 51 | : ` ${AssertName}` 52 | : ''}` 53 | 54 | // https://regexr.com/7f7rv 55 | const tupleRegex = /^tuple(?(\[(\d*)\])*)$/ 56 | 57 | /** 58 | * Formats {@link AbiParameter} to human-readable ABI parameter. 59 | * 60 | * @param abiParameter - ABI parameter 61 | * @returns Human-readable ABI parameter 62 | * 63 | * @example 64 | * const result = formatAbiParameter({ type: 'address', name: 'from' }) 65 | * // ^? const result: 'address from' 66 | */ 67 | export function formatAbiParameter< 68 | const abiParameter extends AbiParameter | AbiEventParameter, 69 | >(abiParameter: abiParameter): FormatAbiParameter { 70 | type Result = FormatAbiParameter 71 | 72 | let type = abiParameter.type 73 | if (tupleRegex.test(abiParameter.type) && 'components' in abiParameter) { 74 | type = '(' 75 | const length = abiParameter.components.length as number 76 | for (let i = 0; i < length; i++) { 77 | const component = abiParameter.components[i]! 78 | type += formatAbiParameter(component) 79 | if (i < length - 1) type += ', ' 80 | } 81 | const result = execTyped<{ array?: string }>(tupleRegex, abiParameter.type) 82 | type += `)${result?.array ?? ''}` 83 | return formatAbiParameter({ 84 | ...abiParameter, 85 | type, 86 | }) as Result 87 | } 88 | // Add `indexed` to type if in `abiParameter` 89 | if ('indexed' in abiParameter && abiParameter.indexed) 90 | type = `${type} indexed` 91 | // Return human-readable ABI parameter 92 | if (abiParameter.name) return `${type} ${abiParameter.name}` as Result 93 | return type as Result 94 | } 95 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiParameters.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, test } from 'vitest' 2 | 3 | import type { AbiParameter } from '../abi.js' 4 | 5 | import type { FormatAbiParameters } from './formatAbiParameters.js' 6 | import { formatAbiParameters } from './formatAbiParameters.js' 7 | 8 | test('FormatAbiParameters', () => { 9 | // @ts-expect-error must have at least one parameter 10 | expectTypeOf>().toEqualTypeOf() 11 | 12 | // string 13 | expectTypeOf< 14 | FormatAbiParameters< 15 | [ 16 | { 17 | readonly type: 'address' 18 | readonly name: 'from' 19 | }, 20 | ] 21 | > 22 | >().toEqualTypeOf<'address from'>() 23 | expectTypeOf< 24 | FormatAbiParameters< 25 | [ 26 | { 27 | readonly type: 'address' 28 | readonly name: 'from' 29 | readonly indexed: true 30 | }, 31 | ] 32 | > 33 | >().toEqualTypeOf<'address indexed from'>() 34 | 35 | // Array 36 | expectTypeOf< 37 | FormatAbiParameters< 38 | [ 39 | { 40 | readonly type: 'tuple' 41 | readonly components: readonly [ 42 | { 43 | readonly name: 'name' 44 | readonly type: 'string' 45 | }, 46 | ] 47 | }, 48 | ] 49 | > 50 | >().toEqualTypeOf<'(string name)'>() 51 | 52 | expectTypeOf< 53 | FormatAbiParameters< 54 | [ 55 | { 56 | readonly type: 'tuple' 57 | readonly components: readonly [ 58 | { 59 | readonly type: 'string' 60 | readonly name: 'bar' 61 | }, 62 | ] 63 | readonly name: 'foo' 64 | }, 65 | ] 66 | > 67 | >().toEqualTypeOf<'(string bar) foo'>() 68 | }) 69 | 70 | test('formatAbiParameter', () => { 71 | expectTypeOf( 72 | formatAbiParameters([ 73 | { 74 | type: 'tuple', 75 | components: [{ type: 'string' }], 76 | }, 77 | ]), 78 | ).toEqualTypeOf<'(string)'>() 79 | 80 | const param = { type: 'address' } 81 | const param2: AbiParameter = param 82 | 83 | expectTypeOf(formatAbiParameters([param])).toEqualTypeOf() 84 | 85 | expectTypeOf( 86 | formatAbiParameters([param, param]), 87 | ).toEqualTypeOf<`${string}, ${string}`>() 88 | 89 | expectTypeOf( 90 | formatAbiParameters([param2, param2]), 91 | ).toEqualTypeOf<`${string}, ${string}`>() 92 | }) 93 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiParameters.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, expectTypeOf, test } from 'vitest' 2 | 3 | import { formatAbiParameters } from './formatAbiParameters.js' 4 | 5 | test('default', () => { 6 | const result = formatAbiParameters([ 7 | { type: 'address', name: 'foo' }, 8 | { type: 'uint256', name: 'bar' }, 9 | ]) 10 | expect(result).toEqual('address foo, uint256 bar') 11 | expectTypeOf(result).toEqualTypeOf<'address foo, uint256 bar'>() 12 | }) 13 | 14 | test('tuple', () => { 15 | const result = formatAbiParameters([ 16 | { 17 | type: 'tuple', 18 | components: [ 19 | { type: 'string', name: 'bar' }, 20 | { type: 'string', name: 'baz' }, 21 | ], 22 | name: 'foo', 23 | }, 24 | { type: 'uint256', name: 'bar' }, 25 | ]) 26 | expect(result).toMatchInlineSnapshot( 27 | '"(string bar, string baz) foo, uint256 bar"', 28 | ) 29 | expectTypeOf( 30 | result, 31 | ).toEqualTypeOf<'(string bar, string baz) foo, uint256 bar'>() 32 | }) 33 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/formatAbiParameters.ts: -------------------------------------------------------------------------------- 1 | import type { AbiEventParameter, AbiParameter } from '../abi.js' 2 | import type { Join } from '../types.js' 3 | import { 4 | type FormatAbiParameter, 5 | formatAbiParameter, 6 | } from './formatAbiParameter.js' 7 | 8 | /** 9 | * Formats {@link AbiParameter}s to human-readable ABI parameter. 10 | * 11 | * @param abiParameters - ABI parameters 12 | * @returns Human-readable ABI parameters 13 | * 14 | * @example 15 | * type Result = FormatAbiParameters<[ 16 | * // ^? type Result = 'address from, uint256 tokenId' 17 | * { type: 'address'; name: 'from'; }, 18 | * { type: 'uint256'; name: 'tokenId'; }, 19 | * ]> 20 | */ 21 | export type FormatAbiParameters< 22 | abiParameters extends readonly [ 23 | AbiParameter | AbiEventParameter, 24 | ...(readonly (AbiParameter | AbiEventParameter)[]), 25 | ], 26 | > = Join< 27 | { 28 | [key in keyof abiParameters]: FormatAbiParameter 29 | }, 30 | ', ' 31 | > 32 | 33 | /** 34 | * Formats {@link AbiParameter}s to human-readable ABI parameters. 35 | * 36 | * @param abiParameters - ABI parameters 37 | * @returns Human-readable ABI parameters 38 | * 39 | * @example 40 | * const result = formatAbiParameters([ 41 | * // ^? const result: 'address from, uint256 tokenId' 42 | * { type: 'address', name: 'from' }, 43 | * { type: 'uint256', name: 'tokenId' }, 44 | * ]) 45 | */ 46 | export function formatAbiParameters< 47 | const abiParameters extends readonly [ 48 | AbiParameter | AbiEventParameter, 49 | ...(readonly (AbiParameter | AbiEventParameter)[]), 50 | ], 51 | >(abiParameters: abiParameters): FormatAbiParameters { 52 | let params = '' 53 | const length = abiParameters.length 54 | for (let i = 0; i < length; i++) { 55 | const abiParameter = abiParameters[i]! 56 | params += formatAbiParameter(abiParameter) 57 | if (i !== length - 1) params += ', ' 58 | } 59 | return params as FormatAbiParameters 60 | } 61 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/integration.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | import { formatAbiItem } from './formatAbiItem.js' 3 | import { parseAbiItem } from './parseAbiItem.js' 4 | 5 | test.each([ 6 | { 7 | type: 'fallback', 8 | stateMutability: 'payable', 9 | } as const, 10 | { 11 | type: 'fallback', 12 | stateMutability: 'nonpayable', 13 | } as const, 14 | { 15 | type: 'receive', 16 | stateMutability: 'payable', 17 | } as const, 18 | { 19 | type: 'function', 20 | name: 'foo', 21 | inputs: [{ type: 'string' }], 22 | outputs: [], 23 | stateMutability: 'nonpayable', 24 | } as const, 25 | { 26 | type: 'event', 27 | name: 'Foo', 28 | inputs: [ 29 | { type: 'address', name: 'from', indexed: true }, 30 | { type: 'address', name: 'to', indexed: true }, 31 | { type: 'uint256', name: 'amount' }, 32 | ], 33 | } as const, 34 | { 35 | type: 'constructor', 36 | stateMutability: 'nonpayable', 37 | inputs: [{ type: 'string' }], 38 | } as const, 39 | { 40 | type: 'constructor', 41 | stateMutability: 'payable', 42 | inputs: [{ type: 'string' }], 43 | } as const, 44 | { 45 | type: 'function', 46 | name: 'initWormhole', 47 | inputs: [ 48 | { 49 | type: 'tuple[]', 50 | name: 'configs', 51 | components: [ 52 | { 53 | type: 'uint256', 54 | name: 'chainId', 55 | }, 56 | { 57 | type: 'uint16', 58 | name: 'wormholeChainId', 59 | }, 60 | ], 61 | }, 62 | ], 63 | outputs: [], 64 | stateMutability: 'nonpayable', 65 | } as const, 66 | ])('use of parseAbiItem - formatAbiItem should be reversible', (abiItem) => { 67 | expect(parseAbiItem(formatAbiItem(abiItem))).toEqual(abiItem) 68 | }) 69 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbi.bench.ts: -------------------------------------------------------------------------------- 1 | import { Interface as InterfaceV5 } from '@ethersproject/abi' 2 | import { Interface } from 'ethers' 3 | import { bench, describe } from 'vitest' 4 | 5 | import { parseAbi } from './parseAbi.js' 6 | 7 | describe('Parse ABI', () => { 8 | bench('abitype', () => { 9 | parseAbi([ 10 | 'function name((string name, uint256 age) foo, uint256 tokenId)', 11 | 'event Foo(address indexed bar)', 12 | ]) 13 | }) 14 | 15 | bench('abitype (struct)', () => { 16 | parseAbi([ 17 | 'struct Foo { string name; uint256 age }', 18 | 'function name(Foo foo, uint256 tokenId)', 19 | 'event Foo(address indexed bar)', 20 | ]) 21 | }) 22 | 23 | bench('ethers@5', () => { 24 | new InterfaceV5([ 25 | 'function name((string name, uint256 age) foo, uint256 tokenId)', 26 | 'event Foo(address indexed bar)', 27 | ]).fragments 28 | }) 29 | 30 | bench('ethers@6', () => { 31 | new Interface([ 32 | 'function name((string name, uint256 age) foo, uint256 tokenId)', 33 | 'event Foo(address indexed bar)', 34 | ]).fragments 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbi.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, test } from 'vitest' 2 | 3 | import type { Abi } from '../abi.js' 4 | 5 | import { seaportHumanReadableAbi } from '../abis/human-readable.js' 6 | import type { IsAbi } from '../utils.js' 7 | import type { ParseAbi } from './parseAbi.js' 8 | import { parseAbi } from './parseAbi.js' 9 | 10 | test('ParseAbi', () => { 11 | type SeaportAbi = ParseAbi 12 | expectTypeOf>().toEqualTypeOf() 13 | 14 | expectTypeOf>().toEqualTypeOf() 15 | expectTypeOf< 16 | ParseAbi<['struct Foo { string name; }']> 17 | >().toEqualTypeOf() 18 | 19 | expectTypeOf< 20 | ParseAbi< 21 | [ 22 | 'function foo()', 23 | 'function bar(Foo, bytes32)', 24 | 'struct Foo { string name; }', 25 | ] 26 | > 27 | >().toEqualTypeOf< 28 | readonly [ 29 | { 30 | readonly name: 'foo' 31 | readonly type: 'function' 32 | readonly stateMutability: 'nonpayable' 33 | readonly inputs: readonly [] 34 | readonly outputs: readonly [] 35 | }, 36 | { 37 | readonly name: 'bar' 38 | readonly type: 'function' 39 | readonly stateMutability: 'nonpayable' 40 | readonly inputs: readonly [ 41 | { 42 | readonly type: 'tuple' 43 | readonly components: readonly [ 44 | { 45 | readonly name: 'name' 46 | readonly type: 'string' 47 | }, 48 | ] 49 | }, 50 | { 51 | readonly type: 'bytes32' 52 | }, 53 | ] 54 | readonly outputs: readonly [] 55 | }, 56 | ] 57 | >() 58 | 59 | expectTypeOf< 60 | ParseAbi< 61 | [ 62 | 'function balanceOf(address owner) view returns (uint256)', 63 | 'event Transfer(address indexed from, address indexed to, uint256 amount)', 64 | ] 65 | > 66 | >().toEqualTypeOf< 67 | readonly [ 68 | { 69 | readonly name: 'balanceOf' 70 | readonly type: 'function' 71 | readonly stateMutability: 'view' 72 | readonly inputs: readonly [ 73 | { 74 | readonly name: 'owner' 75 | readonly type: 'address' 76 | }, 77 | ] 78 | readonly outputs: readonly [ 79 | { 80 | readonly type: 'uint256' 81 | }, 82 | ] 83 | }, 84 | { 85 | readonly name: 'Transfer' 86 | readonly type: 'event' 87 | readonly inputs: readonly [ 88 | { 89 | readonly name: 'from' 90 | readonly type: 'address' 91 | readonly indexed: true 92 | }, 93 | { 94 | readonly name: 'to' 95 | readonly type: 'address' 96 | readonly indexed: true 97 | }, 98 | { 99 | readonly name: 'amount' 100 | readonly type: 'uint256' 101 | }, 102 | ] 103 | }, 104 | ] 105 | >() 106 | 107 | expectTypeOf>().toEqualTypeOf() 108 | }) 109 | 110 | test('parseAbi', () => { 111 | // @ts-expect-error empty array not allowed 112 | expectTypeOf(parseAbi([])).toEqualTypeOf() 113 | expectTypeOf(parseAbi(['struct Foo { string name; }'])).toEqualTypeOf() 114 | 115 | // Array 116 | const res2 = parseAbi([ 117 | 'function bar(Foo, bytes32)', 118 | 'struct Foo { string name; }', 119 | ]) 120 | expectTypeOf().toEqualTypeOf< 121 | readonly [ 122 | { 123 | readonly name: 'bar' 124 | readonly type: 'function' 125 | readonly stateMutability: 'nonpayable' 126 | readonly inputs: readonly [ 127 | { 128 | readonly type: 'tuple' 129 | readonly components: readonly [ 130 | { 131 | readonly name: 'name' 132 | readonly type: 'string' 133 | }, 134 | ] 135 | }, 136 | { 137 | readonly type: 'bytes32' 138 | }, 139 | ] 140 | readonly outputs: readonly [] 141 | }, 142 | ] 143 | >() 144 | 145 | const abi2 = [ 146 | 'function foo()', 147 | 'function bar(Foo, bytes32)', 148 | 'struct Foo { string name; }', 149 | ] 150 | expectTypeOf(parseAbi(abi2)).toEqualTypeOf() 151 | 152 | // @ts-expect-error invalid signature 153 | expectTypeOf(parseAbi(['function foo ()'])).toEqualTypeOf() 154 | 155 | const param: string[] = abi2 156 | expectTypeOf(parseAbi(param)).toEqualTypeOf() 157 | 158 | const getOrderType = parseAbi(seaportHumanReadableAbi)[10] 159 | expectTypeOf().toEqualTypeOf<{ 160 | readonly name: 'getOrderStatus' 161 | readonly type: 'function' 162 | readonly stateMutability: 'view' 163 | readonly inputs: readonly [ 164 | { 165 | readonly type: 'bytes32' 166 | readonly name: 'orderHash' 167 | }, 168 | ] 169 | readonly outputs: readonly [ 170 | { 171 | readonly type: 'bool' 172 | readonly name: 'isValidated' 173 | }, 174 | { 175 | readonly type: 'bool' 176 | readonly name: 'isCancelled' 177 | }, 178 | { 179 | readonly type: 'uint256' 180 | readonly name: 'totalFilled' 181 | }, 182 | { 183 | readonly type: 'uint256' 184 | readonly name: 'totalSize' 185 | }, 186 | ] 187 | }>() 188 | }) 189 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbi.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { 4 | customSolidityErrorsHumanReadableAbi, 5 | seaportHumanReadableAbi, 6 | } from '../abis/human-readable.js' 7 | import { parseAbi } from './parseAbi.js' 8 | 9 | test('parseAbi', () => { 10 | const result = parseAbi(seaportHumanReadableAbi) 11 | expect(result).toMatchSnapshot() 12 | 13 | expect(parseAbi(customSolidityErrorsHumanReadableAbi)).toMatchInlineSnapshot(` 14 | [ 15 | { 16 | "inputs": [], 17 | "stateMutability": "nonpayable", 18 | "type": "constructor", 19 | }, 20 | { 21 | "inputs": [], 22 | "name": "ApprovalCallerNotOwnerNorApproved", 23 | "type": "error", 24 | }, 25 | { 26 | "inputs": [], 27 | "name": "ApprovalQueryForNonexistentToken", 28 | "type": "error", 29 | }, 30 | ] 31 | `) 32 | }) 33 | 34 | test('busts cache', () => { 35 | const result1 = parseAbi([ 36 | 'function balanceOf(Baz baz)', 37 | 'struct Baz {uint amount; string role;}', 38 | ]) 39 | expect(result1[0].inputs).toMatchInlineSnapshot(` 40 | [ 41 | { 42 | "components": [ 43 | { 44 | "name": "amount", 45 | "type": "uint256", 46 | }, 47 | { 48 | "name": "role", 49 | "type": "string", 50 | }, 51 | ], 52 | "name": "baz", 53 | "type": "tuple", 54 | }, 55 | ] 56 | `) 57 | const result2 = parseAbi([ 58 | 'function balanceOf(Baz baz)', 59 | 'struct Baz {uint price; string role;}', 60 | ]) 61 | expect(result2[0].inputs).toMatchInlineSnapshot(` 62 | [ 63 | { 64 | "components": [ 65 | { 66 | "name": "price", 67 | "type": "uint256", 68 | }, 69 | { 70 | "name": "role", 71 | "type": "string", 72 | }, 73 | ], 74 | "name": "baz", 75 | "type": "tuple", 76 | }, 77 | ] 78 | `) 79 | }) 80 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbi.ts: -------------------------------------------------------------------------------- 1 | import type { Abi } from '../abi.js' 2 | import type { Error, Filter } from '../types.js' 3 | import { isStructSignature } from './runtime/signatures.js' 4 | import { parseStructs } from './runtime/structs.js' 5 | import { parseSignature } from './runtime/utils.js' 6 | import type { Signatures } from './types/signatures.js' 7 | import type { ParseStructs } from './types/structs.js' 8 | import type { ParseSignature } from './types/utils.js' 9 | 10 | /** 11 | * Parses human-readable ABI into JSON {@link Abi} 12 | * 13 | * @param signatures - Human-readable ABI 14 | * @returns Parsed {@link Abi} 15 | * 16 | * @example 17 | * type Result = ParseAbi< 18 | * // ^? type Result = readonly [{ name: "balanceOf"; type: "function"; stateMutability:... 19 | * [ 20 | * 'function balanceOf(address owner) view returns (uint256)', 21 | * 'event Transfer(address indexed from, address indexed to, uint256 amount)', 22 | * ] 23 | * > 24 | */ 25 | export type ParseAbi = 26 | string[] extends signatures 27 | ? Abi // If `T` was not able to be inferred (e.g. just `string[]`), return `Abi` 28 | : signatures extends readonly string[] 29 | ? signatures extends Signatures // Validate signatures 30 | ? ParseStructs extends infer structs 31 | ? { 32 | [key in keyof signatures]: signatures[key] extends string 33 | ? ParseSignature 34 | : never 35 | } extends infer mapped extends readonly unknown[] 36 | ? Filter extends infer result 37 | ? result extends readonly [] 38 | ? never 39 | : result 40 | : never 41 | : never 42 | : never 43 | : never 44 | : never 45 | 46 | /** 47 | * Parses human-readable ABI into JSON {@link Abi} 48 | * 49 | * @param signatures - Human-Readable ABI 50 | * @returns Parsed {@link Abi} 51 | * 52 | * @example 53 | * const abi = parseAbi([ 54 | * // ^? const abi: readonly [{ name: "balanceOf"; type: "function"; stateMutability:... 55 | * 'function balanceOf(address owner) view returns (uint256)', 56 | * 'event Transfer(address indexed from, address indexed to, uint256 amount)', 57 | * ]) 58 | */ 59 | export function parseAbi( 60 | signatures: signatures['length'] extends 0 61 | ? Error<'At least one signature required'> 62 | : Signatures extends signatures 63 | ? signatures 64 | : Signatures, 65 | ): ParseAbi { 66 | const structs = parseStructs(signatures as readonly string[]) 67 | const abi = [] 68 | const length = signatures.length as number 69 | for (let i = 0; i < length; i++) { 70 | const signature = (signatures as readonly string[])[i]! 71 | if (isStructSignature(signature)) continue 72 | abi.push(parseSignature(signature, structs)) 73 | } 74 | return abi as unknown as ParseAbi 75 | } 76 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiItem.bench.ts: -------------------------------------------------------------------------------- 1 | import { Fragment as FragmentV5 } from '@ethersproject/abi' 2 | import { Fragment } from 'ethers' 3 | import { bench, describe } from 'vitest' 4 | 5 | import { parseAbiItem } from './parseAbiItem.js' 6 | 7 | describe('Parse basic ABI function', () => { 8 | const basic = 'function foo(string bar, string baz)' 9 | 10 | bench('abitype', () => { 11 | parseAbiItem(basic) 12 | }) 13 | 14 | bench('ethers@6', () => { 15 | Fragment.from(basic) 16 | }) 17 | 18 | bench('ethers@5', () => { 19 | FragmentV5.from(basic) 20 | }) 21 | }) 22 | 23 | describe('Parse complex ABI function', () => { 24 | const basic = 25 | 'function foo(address boo, (string bar, string baz) foo, string test) public view returns ((string bar, string baz) foo, string test)' 26 | 27 | bench('abitype', () => { 28 | parseAbiItem(basic) 29 | }) 30 | 31 | bench('ethers@6', () => { 32 | Fragment.from(basic) 33 | }) 34 | 35 | bench('ethers@5', () => { 36 | FragmentV5.from(basic) 37 | }) 38 | }) 39 | 40 | describe('Parse ABI event', () => { 41 | const basic = 'event Foo(address indexed boo)' 42 | 43 | bench('abitype', () => { 44 | parseAbiItem(basic) 45 | }) 46 | 47 | bench('ethers@6', () => { 48 | Fragment.from(basic) 49 | }) 50 | 51 | bench('ethers@5', () => { 52 | FragmentV5.from(basic) 53 | }) 54 | }) 55 | 56 | describe('comparison', () => { 57 | bench('abitype', () => { 58 | parseAbiItem( 59 | 'function name((string name, uint256 age) foo, uint256 tokenId)', 60 | ) 61 | }) 62 | 63 | bench('ethers@5', () => { 64 | FragmentV5.from( 65 | 'function name(tuple(string name, uint256 age) foo, uint256 tokenId)', 66 | ) 67 | }) 68 | 69 | bench('ethers@6', () => { 70 | Fragment.from( 71 | 'function name(tuple(string name, uint256 age) foo, uint256 tokenId)', 72 | ) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiItem.ts: -------------------------------------------------------------------------------- 1 | import type { Abi } from '../abi.js' 2 | import type { Narrow } from '../narrow.js' 3 | import type { Error, Filter } from '../types.js' 4 | import { InvalidAbiItemError } from './errors/abiItem.js' 5 | import { isStructSignature } from './runtime/signatures.js' 6 | import { parseStructs } from './runtime/structs.js' 7 | import { parseSignature } from './runtime/utils.js' 8 | import type { Signature, Signatures } from './types/signatures.js' 9 | import type { ParseStructs } from './types/structs.js' 10 | import type { ParseSignature } from './types/utils.js' 11 | 12 | /** 13 | * Parses human-readable ABI item (e.g. error, event, function) into {@link Abi} item 14 | * 15 | * @param signature - Human-readable ABI item 16 | * @returns Parsed {@link Abi} item 17 | * 18 | * @example 19 | * type Result = ParseAbiItem<'function balanceOf(address owner) view returns (uint256)'> 20 | * // ^? type Result = { name: "balanceOf"; type: "function"; stateMutability: "view";... 21 | * 22 | * @example 23 | * type Result = ParseAbiItem< 24 | * // ^? type Result = { name: "foo"; type: "function"; stateMutability: "view"; inputs:... 25 | * ['function foo(Baz bar) view returns (string)', 'struct Baz { string name; }'] 26 | * > 27 | */ 28 | export type ParseAbiItem< 29 | signature extends string | readonly string[] | readonly unknown[], 30 | > = 31 | | (signature extends string 32 | ? string extends signature 33 | ? Abi[number] 34 | : signature extends Signature // Validate signature 35 | ? ParseSignature 36 | : never 37 | : never) 38 | | (signature extends readonly string[] 39 | ? string[] extends signature 40 | ? Abi[number] // Return generic Abi item since type was no inferrable 41 | : signature extends Signatures // Validate signature 42 | ? ParseStructs extends infer structs 43 | ? { 44 | [key in keyof signature]: ParseSignature< 45 | signature[key] extends string ? signature[key] : never, 46 | structs 47 | > 48 | } extends infer mapped extends readonly unknown[] 49 | ? // Filter out `never` since those are structs 50 | Filter[0] extends infer result 51 | ? result extends undefined // convert `undefined` to `never` (e.g. `ParseAbiItem<['struct Foo { string name; }']>`) 52 | ? never 53 | : result 54 | : never 55 | : never 56 | : never 57 | : never 58 | : never) 59 | 60 | /** 61 | * Parses human-readable ABI item (e.g. error, event, function) into {@link Abi} item 62 | * 63 | * @param signature - Human-readable ABI item 64 | * @returns Parsed {@link Abi} item 65 | * 66 | * @example 67 | * const abiItem = parseAbiItem('function balanceOf(address owner) view returns (uint256)') 68 | * // ^? const abiItem: { name: "balanceOf"; type: "function"; stateMutability: "view";... 69 | * 70 | * @example 71 | * const abiItem = parseAbiItem([ 72 | * // ^? const abiItem: { name: "foo"; type: "function"; stateMutability: "view"; inputs:... 73 | * 'function foo(Baz bar) view returns (string)', 74 | * 'struct Baz { string name; }', 75 | * ]) 76 | */ 77 | export function parseAbiItem< 78 | signature extends string | readonly string[] | readonly unknown[], 79 | >( 80 | signature: Narrow & 81 | ( 82 | | (signature extends string 83 | ? string extends signature 84 | ? unknown 85 | : Signature 86 | : never) 87 | | (signature extends readonly string[] 88 | ? signature extends readonly [] // empty array 89 | ? Error<'At least one signature required.'> 90 | : string[] extends signature 91 | ? unknown 92 | : Signatures 93 | : never) 94 | ), 95 | ): ParseAbiItem { 96 | let abiItem: ParseAbiItem | undefined 97 | if (typeof signature === 'string') 98 | abiItem = parseSignature(signature) as ParseAbiItem 99 | else { 100 | const structs = parseStructs(signature as readonly string[]) 101 | const length = signature.length as number 102 | for (let i = 0; i < length; i++) { 103 | const signature_ = (signature as readonly string[])[i]! 104 | if (isStructSignature(signature_)) continue 105 | abiItem = parseSignature(signature_, structs) as ParseAbiItem 106 | break 107 | } 108 | } 109 | 110 | if (!abiItem) throw new InvalidAbiItemError({ signature }) 111 | return abiItem as ParseAbiItem 112 | } 113 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiParameter.bench.ts: -------------------------------------------------------------------------------- 1 | import { ParamType as ParamTypeV5 } from '@ethersproject/abi' 2 | import { ParamType } from 'ethers' 3 | import { bench, describe } from 'vitest' 4 | 5 | import { parseAbiParameter } from './parseAbiParameter.js' 6 | 7 | describe('Parse basic ABI Parameter', () => { 8 | const basic = 'string foo' 9 | 10 | bench('abitype', () => { 11 | parseAbiParameter(basic) 12 | }) 13 | 14 | bench('ethers@6', () => { 15 | ParamType.from(basic) 16 | }) 17 | 18 | bench('ethers@5', () => { 19 | ParamTypeV5.from(basic) 20 | }) 21 | }) 22 | 23 | describe('Parse inline tuple ABI Parameter', () => { 24 | const inlineTuple = '(string bar, string baz) foo' 25 | 26 | bench('abitype', () => { 27 | parseAbiParameter(inlineTuple) 28 | }) 29 | 30 | bench('ethers@6', () => { 31 | ParamType.from(inlineTuple) 32 | }) 33 | 34 | bench('ethers@5', () => { 35 | ParamTypeV5.from(inlineTuple) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiParameter.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, test } from 'vitest' 2 | 3 | import type { AbiParameter } from '../abi.js' 4 | import type { ParseAbiParameter } from './parseAbiParameter.js' 5 | import { parseAbiParameter } from './parseAbiParameter.js' 6 | 7 | test('ParseAbiParameter', () => { 8 | expectTypeOf>().toEqualTypeOf() 9 | expectTypeOf>().toEqualTypeOf() 10 | expectTypeOf< 11 | ParseAbiParameter<['struct Foo { string name; }']> 12 | >().toEqualTypeOf() 13 | 14 | // string 15 | expectTypeOf>().toEqualTypeOf<{ 16 | readonly type: 'address' 17 | readonly name: 'from' 18 | }>() 19 | expectTypeOf>().toEqualTypeOf<{ 20 | readonly type: 'address' 21 | readonly name: 'from' 22 | readonly indexed: true 23 | }>() 24 | expectTypeOf>().toEqualTypeOf<{ 25 | readonly type: 'address' 26 | readonly name: 'foo' 27 | }>() 28 | 29 | // Array 30 | expectTypeOf< 31 | ParseAbiParameter<['Foo', 'struct Foo { string name; }']> 32 | >().toEqualTypeOf<{ 33 | readonly type: 'tuple' 34 | readonly components: readonly [ 35 | { 36 | readonly name: 'name' 37 | readonly type: 'string' 38 | }, 39 | ] 40 | }>() 41 | 42 | expectTypeOf>().toEqualTypeOf<{ 43 | readonly type: 'tuple' 44 | readonly components: readonly [ 45 | { 46 | readonly type: 'string' 47 | readonly name: 'bar' 48 | }, 49 | ] 50 | readonly name: 'foo' 51 | }>() 52 | }) 53 | 54 | test('parseAbiParameter', () => { 55 | // @ts-expect-error empty array not allowed 56 | expectTypeOf(parseAbiParameter([])).toEqualTypeOf() 57 | expectTypeOf( 58 | parseAbiParameter(['struct Foo { string name; }']), 59 | ).toEqualTypeOf() 60 | 61 | expectTypeOf(parseAbiParameter('(string)')).toEqualTypeOf<{ 62 | readonly type: 'tuple' 63 | readonly components: readonly [{ readonly type: 'string' }] 64 | }>() 65 | 66 | const param: string = 'address' 67 | expectTypeOf(parseAbiParameter(param)).toEqualTypeOf() 68 | }) 69 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiParameter.test.ts: -------------------------------------------------------------------------------- 1 | import { assertType, expect, test } from 'vitest' 2 | 3 | import { parseAbiParameter } from './parseAbiParameter.js' 4 | 5 | test('parseAbiParameter', () => { 6 | // @ts-expect-error invalid signature type 7 | expect(() => parseAbiParameter('')).toThrowErrorMatchingInlineSnapshot( 8 | ` 9 | [InvalidParameterError: Invalid ABI parameter. 10 | 11 | Version: abitype@x.y.z] 12 | `, 13 | ) 14 | // @ts-expect-error invalid signature type 15 | expect(() => parseAbiParameter([])).toThrowErrorMatchingInlineSnapshot( 16 | ` 17 | [InvalidAbiParameterError: Failed to parse ABI parameter. 18 | 19 | Docs: https://abitype.dev/api/human#parseabiparameter-1 20 | Details: parseAbiParameter([]) 21 | Version: abitype@x.y.z] 22 | `, 23 | ) 24 | expect(() => 25 | parseAbiParameter(['struct Foo { string name; }']), 26 | ).toThrowErrorMatchingInlineSnapshot( 27 | ` 28 | [InvalidAbiParameterError: Failed to parse ABI parameter. 29 | 30 | Docs: https://abitype.dev/api/human#parseabiparameter-1 31 | Details: parseAbiParameter([ 32 | "struct Foo { string name; }" 33 | ]) 34 | Version: abitype@x.y.z] 35 | `, 36 | ) 37 | 38 | expect(() => 39 | parseAbiParameter(['struct Foo { string memory bar; }', 'Foo indexed foo']), 40 | ).toThrowErrorMatchingInlineSnapshot( 41 | ` 42 | [InvalidModifierError: Invalid ABI parameter. 43 | 44 | Modifier "memory" not allowed in "struct" type. 45 | 46 | Details: string memory bar 47 | Version: abitype@x.y.z] 48 | `, 49 | ) 50 | 51 | expect([parseAbiParameter('address from')]).toMatchInlineSnapshot(` 52 | [ 53 | { 54 | "name": "from", 55 | "type": "address", 56 | }, 57 | ] 58 | `) 59 | }) 60 | 61 | test.each([ 62 | { signature: 'string', expected: { type: 'string' } }, 63 | { signature: 'string foo', expected: { name: 'foo', type: 'string' } }, 64 | { 65 | signature: 'string indexed foo', 66 | expected: { name: 'foo', type: 'string', indexed: true }, 67 | }, 68 | { 69 | signature: 'string calldata foo', 70 | expected: { name: 'foo', type: 'string' }, 71 | }, 72 | { 73 | signature: '(string)', 74 | expected: { type: 'tuple', components: [{ type: 'string' }] }, 75 | }, 76 | { 77 | signature: '(string foo)', 78 | expected: { type: 'tuple', components: [{ name: 'foo', type: 'string' }] }, 79 | }, 80 | { 81 | signature: '(string bar) foo', 82 | expected: { 83 | type: 'tuple', 84 | name: 'foo', 85 | components: [{ name: 'bar', type: 'string' }], 86 | }, 87 | }, 88 | { 89 | signature: '(string bar, string baz) foo', 90 | expected: { 91 | type: 'tuple', 92 | name: 'foo', 93 | components: [ 94 | { name: 'bar', type: 'string' }, 95 | { name: 'baz', type: 'string' }, 96 | ], 97 | }, 98 | }, 99 | { signature: 'string[]', expected: { type: 'string[]' } }, 100 | ])('parseAbiParameter($signature)', ({ signature, expected }) => { 101 | expect(parseAbiParameter(signature)).toEqual(expected) 102 | }) 103 | 104 | test.each([ 105 | { 106 | signatures: ['struct Foo { string bar; }', 'Foo'], 107 | expected: { type: 'tuple', components: [{ name: 'bar', type: 'string' }] }, 108 | }, 109 | { 110 | signatures: ['struct Foo { string bar; }', 'Foo foo'], 111 | expected: { 112 | type: 'tuple', 113 | name: 'foo', 114 | components: [{ name: 'bar', type: 'string' }], 115 | }, 116 | }, 117 | { 118 | signatures: ['struct Foo { string bar; }', 'Foo indexed foo'], 119 | expected: { 120 | type: 'tuple', 121 | name: 'foo', 122 | indexed: true, 123 | components: [{ name: 'bar', type: 'string' }], 124 | }, 125 | }, 126 | ])('parseAbiParameter($signatures)', ({ signatures, expected }) => { 127 | expect(parseAbiParameter(signatures)).toEqual(expected) 128 | }) 129 | 130 | test('nested tuple', () => { 131 | const result = parseAbiParameter('((((string baz) bar)[1] foo) boo)') 132 | expect(result).toMatchInlineSnapshot(` 133 | { 134 | "components": [ 135 | { 136 | "components": [ 137 | { 138 | "components": [ 139 | { 140 | "components": [ 141 | { 142 | "name": "baz", 143 | "type": "string", 144 | }, 145 | ], 146 | "name": "bar", 147 | "type": "tuple", 148 | }, 149 | ], 150 | "name": "foo", 151 | "type": "tuple[1]", 152 | }, 153 | ], 154 | "name": "boo", 155 | "type": "tuple", 156 | }, 157 | ], 158 | "type": "tuple", 159 | } 160 | `) 161 | assertType<{ 162 | type: 'tuple' 163 | components: readonly [ 164 | { 165 | type: 'tuple' 166 | components: readonly [ 167 | { 168 | type: 'tuple[1]' 169 | components: readonly [ 170 | { 171 | type: 'tuple' 172 | components: readonly [ 173 | { 174 | type: 'string' 175 | name: 'baz' 176 | }, 177 | ] 178 | name: 'bar' 179 | }, 180 | ] 181 | name: 'foo' 182 | }, 183 | ] 184 | name: 'boo' 185 | }, 186 | ] 187 | }>(result) 188 | }) 189 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiParameter.ts: -------------------------------------------------------------------------------- 1 | import type { AbiParameter } from '../abi.js' 2 | import type { Narrow } from '../narrow.js' 3 | import type { Error, Filter } from '../types.js' 4 | import { InvalidAbiParameterError } from './errors/abiParameter.js' 5 | import { isStructSignature, modifiers } from './runtime/signatures.js' 6 | import { parseStructs } from './runtime/structs.js' 7 | import { parseAbiParameter as parseAbiParameter_ } from './runtime/utils.js' 8 | import type { IsStructSignature, Modifier } from './types/signatures.js' 9 | import type { ParseStructs } from './types/structs.js' 10 | import type { ParseAbiParameter as ParseAbiParameter_ } from './types/utils.js' 11 | 12 | /** 13 | * Parses human-readable ABI parameter into {@link AbiParameter} 14 | * 15 | * @param param - Human-readable ABI parameter 16 | * @returns Parsed {@link AbiParameter} 17 | * 18 | * @example 19 | * type Result = ParseAbiParameter<'address from'> 20 | * // ^? type Result = { type: "address"; name: "from"; } 21 | * 22 | * @example 23 | * type Result = ParseAbiParameter< 24 | * // ^? type Result = { type: "tuple"; components: [{ type: "string"; name:... 25 | * ['Baz bar', 'struct Baz { string name; }'] 26 | * > 27 | */ 28 | export type ParseAbiParameter< 29 | param extends string | readonly string[] | readonly unknown[], 30 | > = 31 | | (param extends string 32 | ? param extends '' 33 | ? never 34 | : string extends param 35 | ? AbiParameter 36 | : ParseAbiParameter_ 37 | : never) 38 | | (param extends readonly string[] 39 | ? string[] extends param 40 | ? AbiParameter // Return generic AbiParameter item since type was no inferrable 41 | : ParseStructs extends infer structs 42 | ? { 43 | [key in keyof param]: param[key] extends string 44 | ? IsStructSignature extends true 45 | ? never 46 | : ParseAbiParameter_< 47 | param[key], 48 | { modifier: Modifier; structs: structs } 49 | > 50 | : never 51 | } extends infer mapped extends readonly unknown[] 52 | ? Filter[0] extends infer result 53 | ? result extends undefined 54 | ? never 55 | : result 56 | : never 57 | : never 58 | : never 59 | : never) 60 | 61 | /** 62 | * Parses human-readable ABI parameter into {@link AbiParameter} 63 | * 64 | * @param param - Human-readable ABI parameter 65 | * @returns Parsed {@link AbiParameter} 66 | * 67 | * @example 68 | * const abiParameter = parseAbiParameter('address from') 69 | * // ^? const abiParameter: { type: "address"; name: "from"; } 70 | * 71 | * @example 72 | * const abiParameter = parseAbiParameter([ 73 | * // ^? const abiParameter: { type: "tuple"; components: [{ type: "string"; name:... 74 | * 'Baz bar', 75 | * 'struct Baz { string name; }', 76 | * ]) 77 | */ 78 | export function parseAbiParameter< 79 | param extends string | readonly string[] | readonly unknown[], 80 | >( 81 | param: Narrow & 82 | ( 83 | | (param extends string 84 | ? param extends '' 85 | ? Error<'Empty string is not allowed.'> 86 | : unknown 87 | : never) 88 | | (param extends readonly string[] 89 | ? param extends readonly [] // empty array 90 | ? Error<'At least one parameter required.'> 91 | : string[] extends param 92 | ? unknown 93 | : unknown // TODO: Validate param string 94 | : never) 95 | ), 96 | ): ParseAbiParameter { 97 | let abiParameter: AbiParameter | undefined 98 | if (typeof param === 'string') 99 | abiParameter = parseAbiParameter_(param, { 100 | modifiers, 101 | }) as ParseAbiParameter 102 | else { 103 | const structs = parseStructs(param as readonly string[]) 104 | const length = param.length as number 105 | for (let i = 0; i < length; i++) { 106 | const signature = (param as readonly string[])[i]! 107 | if (isStructSignature(signature)) continue 108 | abiParameter = parseAbiParameter_(signature, { modifiers, structs }) 109 | break 110 | } 111 | } 112 | 113 | if (!abiParameter) throw new InvalidAbiParameterError({ param }) 114 | 115 | return abiParameter as ParseAbiParameter 116 | } 117 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiParameters.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, test } from 'vitest' 2 | 3 | import type { AbiParameter } from '../abi.js' 4 | import type { ParseAbiParameters } from './parseAbiParameters.js' 5 | import { parseAbiParameters } from './parseAbiParameters.js' 6 | 7 | test('ParseAbiParameters', () => { 8 | expectTypeOf>().toEqualTypeOf() 9 | expectTypeOf>().toEqualTypeOf() 10 | expectTypeOf< 11 | ParseAbiParameters<['struct Foo { string name; }']> 12 | >().toEqualTypeOf() 13 | 14 | // string 15 | expectTypeOf< 16 | ParseAbiParameters<'address from, address to, uint256 amount'> 17 | >().toEqualTypeOf< 18 | readonly [ 19 | { 20 | readonly type: 'address' 21 | readonly name: 'from' 22 | }, 23 | { 24 | readonly type: 'address' 25 | readonly name: 'to' 26 | }, 27 | { 28 | readonly type: 'uint256' 29 | readonly name: 'amount' 30 | }, 31 | ] 32 | >() 33 | expectTypeOf< 34 | ParseAbiParameters<'address indexed from, address indexed to, uint256 indexed amount'> 35 | >().toEqualTypeOf< 36 | readonly [ 37 | { 38 | readonly type: 'address' 39 | readonly name: 'from' 40 | readonly indexed: true 41 | }, 42 | { 43 | readonly type: 'address' 44 | readonly name: 'to' 45 | readonly indexed: true 46 | }, 47 | { 48 | readonly type: 'uint256' 49 | readonly name: 'amount' 50 | readonly indexed: true 51 | }, 52 | ] 53 | >() 54 | expectTypeOf< 55 | ParseAbiParameters<'address calldata foo, address memory bar, uint256 storage baz'> 56 | >().toEqualTypeOf< 57 | readonly [ 58 | { 59 | readonly type: 'address' 60 | readonly name: 'foo' 61 | }, 62 | { 63 | readonly type: 'address' 64 | readonly name: 'bar' 65 | }, 66 | { 67 | readonly type: 'uint256' 68 | readonly name: 'baz' 69 | }, 70 | ] 71 | >() 72 | 73 | // Array 74 | expectTypeOf< 75 | ParseAbiParameters<['Foo, bytes32', 'struct Foo { string name; }']> 76 | >().toEqualTypeOf< 77 | readonly [ 78 | { 79 | readonly type: 'tuple' 80 | readonly components: readonly [ 81 | { 82 | readonly name: 'name' 83 | readonly type: 'string' 84 | }, 85 | ] 86 | }, 87 | { readonly type: 'bytes32' }, 88 | ] 89 | >() 90 | }) 91 | 92 | test('parseAbiParameters', () => { 93 | // @ts-expect-error empty array not allowed 94 | expectTypeOf(parseAbiParameters([])).toEqualTypeOf() 95 | expectTypeOf( 96 | parseAbiParameters(['struct Foo { string name; }']), 97 | ).toEqualTypeOf() 98 | 99 | expectTypeOf(parseAbiParameters('(string)')).toEqualTypeOf< 100 | readonly [ 101 | { 102 | readonly type: 'tuple' 103 | readonly components: readonly [{ readonly type: 'string' }] 104 | }, 105 | ] 106 | >() 107 | 108 | const param: string = 'address, string' 109 | expectTypeOf(parseAbiParameters(param)).toEqualTypeOf< 110 | readonly AbiParameter[] 111 | >() 112 | 113 | expectTypeOf(parseAbiParameters(['(uint256 a),(uint256 b)'])).toEqualTypeOf< 114 | readonly [ 115 | { 116 | readonly type: 'tuple' 117 | readonly components: readonly [ 118 | { 119 | readonly type: 'uint256' 120 | readonly name: 'a' 121 | }, 122 | ] 123 | }, 124 | { 125 | readonly type: 'tuple' 126 | readonly components: readonly [ 127 | { 128 | readonly type: 'uint256' 129 | readonly name: 'b' 130 | }, 131 | ] 132 | }, 133 | ] 134 | >() 135 | 136 | expectTypeOf( 137 | parseAbiParameters(['(uint256 a)', '(uint256 b)']), 138 | ).toEqualTypeOf< 139 | readonly [ 140 | { 141 | readonly type: 'tuple' 142 | readonly components: readonly [ 143 | { 144 | readonly type: 'uint256' 145 | readonly name: 'a' 146 | }, 147 | ] 148 | }, 149 | { 150 | readonly type: 'tuple' 151 | readonly components: readonly [ 152 | { 153 | readonly type: 'uint256' 154 | readonly name: 'b' 155 | }, 156 | ] 157 | }, 158 | ] 159 | >() 160 | }) 161 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/parseAbiParameters.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { parseAbiParameters } from './parseAbiParameters.js' 4 | 5 | test('parseAbiParameters', () => { 6 | // @ts-expect-error invalid signature type 7 | expect(() => parseAbiParameters('')).toThrowErrorMatchingInlineSnapshot( 8 | ` 9 | [InvalidAbiParametersError: Failed to parse ABI parameters. 10 | 11 | Docs: https://abitype.dev/api/human#parseabiparameters-1 12 | Details: parseAbiParameters("") 13 | Version: abitype@x.y.z] 14 | `, 15 | ) 16 | // @ts-expect-error invalid signature type 17 | expect(() => parseAbiParameters([])).toThrowErrorMatchingInlineSnapshot( 18 | ` 19 | [InvalidAbiParametersError: Failed to parse ABI parameters. 20 | 21 | Docs: https://abitype.dev/api/human#parseabiparameters-1 22 | Details: parseAbiParameters([]) 23 | Version: abitype@x.y.z] 24 | `, 25 | ) 26 | expect(() => 27 | parseAbiParameters(['struct Foo { string name; }']), 28 | ).toThrowErrorMatchingInlineSnapshot( 29 | ` 30 | [InvalidAbiParametersError: Failed to parse ABI parameters. 31 | 32 | Docs: https://abitype.dev/api/human#parseabiparameters-1 33 | Details: parseAbiParameters([ 34 | "struct Foo { string name; }" 35 | ]) 36 | Version: abitype@x.y.z] 37 | `, 38 | ) 39 | 40 | expect(parseAbiParameters('address from')).toMatchInlineSnapshot(` 41 | [ 42 | { 43 | "name": "from", 44 | "type": "address", 45 | }, 46 | ] 47 | `) 48 | }) 49 | 50 | test.each([ 51 | { 52 | signatures: 'string, string', 53 | expected: [{ type: 'string' }, { type: 'string' }], 54 | }, 55 | { 56 | signatures: 'string foo, string bar', 57 | expected: [ 58 | { type: 'string', name: 'foo' }, 59 | { type: 'string', name: 'bar' }, 60 | ], 61 | }, 62 | ])('parseAbiParameters($signatures)', ({ signatures, expected }) => { 63 | expect(parseAbiParameters(signatures)).toEqual(expected) 64 | }) 65 | 66 | test.each([ 67 | { 68 | signatures: ['struct Foo { string bar; }', 'Foo, string'], 69 | expected: [ 70 | { type: 'tuple', components: [{ name: 'bar', type: 'string' }] }, 71 | { type: 'string' }, 72 | ], 73 | }, 74 | { 75 | signatures: ['string foo, string bar'], 76 | expected: [ 77 | { name: 'foo', type: 'string' }, 78 | { name: 'bar', type: 'string' }, 79 | ], 80 | }, 81 | ])('parseAbiParameters($signatures)', ({ signatures, expected }) => { 82 | expect(parseAbiParameters(signatures)).toEqual(expected) 83 | }) 84 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/runtime/cache.ts: -------------------------------------------------------------------------------- 1 | import type { AbiItemType, AbiParameter } from '../../abi.js' 2 | import type { StructLookup } from '../types/structs.js' 3 | 4 | /** 5 | * Gets {@link parameterCache} cache key namespaced by {@link type}. This prevents parameters from being accessible to types that don't allow them (e.g. `string indexed foo` not allowed outside of `type: 'event'`). 6 | * @param param ABI parameter string 7 | * @param type ABI parameter type 8 | * @returns Cache key for {@link parameterCache} 9 | */ 10 | export function getParameterCacheKey( 11 | param: string, 12 | type?: AbiItemType | 'struct', 13 | structs?: StructLookup, 14 | ) { 15 | let structKey = '' 16 | if (structs) 17 | for (const struct of Object.entries(structs)) { 18 | if (!struct) continue 19 | let propertyKey = '' 20 | for (const property of struct[1]) { 21 | propertyKey += `[${property.type}${property.name ? `:${property.name}` : ''}]` 22 | } 23 | structKey += `(${struct[0]}{${propertyKey}})` 24 | } 25 | if (type) return `${type}:${param}${structKey}` 26 | return param 27 | } 28 | 29 | /** 30 | * Basic cache seeded with common ABI parameter strings. 31 | * 32 | * **Note: When seeding more parameters, make sure you benchmark performance. The current number is the ideal balance between performance and having an already existing cache.** 33 | */ 34 | export const parameterCache = new Map< 35 | string, 36 | AbiParameter & { indexed?: boolean } 37 | >([ 38 | // Unnamed 39 | ['address', { type: 'address' }], 40 | ['bool', { type: 'bool' }], 41 | ['bytes', { type: 'bytes' }], 42 | ['bytes32', { type: 'bytes32' }], 43 | ['int', { type: 'int256' }], 44 | ['int256', { type: 'int256' }], 45 | ['string', { type: 'string' }], 46 | ['uint', { type: 'uint256' }], 47 | ['uint8', { type: 'uint8' }], 48 | ['uint16', { type: 'uint16' }], 49 | ['uint24', { type: 'uint24' }], 50 | ['uint32', { type: 'uint32' }], 51 | ['uint64', { type: 'uint64' }], 52 | ['uint96', { type: 'uint96' }], 53 | ['uint112', { type: 'uint112' }], 54 | ['uint160', { type: 'uint160' }], 55 | ['uint192', { type: 'uint192' }], 56 | ['uint256', { type: 'uint256' }], 57 | 58 | // Named 59 | ['address owner', { type: 'address', name: 'owner' }], 60 | ['address to', { type: 'address', name: 'to' }], 61 | ['bool approved', { type: 'bool', name: 'approved' }], 62 | ['bytes _data', { type: 'bytes', name: '_data' }], 63 | ['bytes data', { type: 'bytes', name: 'data' }], 64 | ['bytes signature', { type: 'bytes', name: 'signature' }], 65 | ['bytes32 hash', { type: 'bytes32', name: 'hash' }], 66 | ['bytes32 r', { type: 'bytes32', name: 'r' }], 67 | ['bytes32 root', { type: 'bytes32', name: 'root' }], 68 | ['bytes32 s', { type: 'bytes32', name: 's' }], 69 | ['string name', { type: 'string', name: 'name' }], 70 | ['string symbol', { type: 'string', name: 'symbol' }], 71 | ['string tokenURI', { type: 'string', name: 'tokenURI' }], 72 | ['uint tokenId', { type: 'uint256', name: 'tokenId' }], 73 | ['uint8 v', { type: 'uint8', name: 'v' }], 74 | ['uint256 balance', { type: 'uint256', name: 'balance' }], 75 | ['uint256 tokenId', { type: 'uint256', name: 'tokenId' }], 76 | ['uint256 value', { type: 'uint256', name: 'value' }], 77 | 78 | // Indexed 79 | [ 80 | 'event:address indexed from', 81 | { type: 'address', name: 'from', indexed: true }, 82 | ], 83 | ['event:address indexed to', { type: 'address', name: 'to', indexed: true }], 84 | [ 85 | 'event:uint indexed tokenId', 86 | { type: 'uint256', name: 'tokenId', indexed: true }, 87 | ], 88 | [ 89 | 'event:uint256 indexed tokenId', 90 | { type: 'uint256', name: 'tokenId', indexed: true }, 91 | ], 92 | ]) 93 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/runtime/signatures.ts: -------------------------------------------------------------------------------- 1 | import type { AbiStateMutability } from '../../abi.js' 2 | import { execTyped } from '../../regex.js' 3 | import type { 4 | EventModifier, 5 | FunctionModifier, 6 | Modifier, 7 | } from '../types/signatures.js' 8 | 9 | // https://regexr.com/7gmok 10 | const errorSignatureRegex = 11 | /^error (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/ 12 | export function isErrorSignature(signature: string) { 13 | return errorSignatureRegex.test(signature) 14 | } 15 | export function execErrorSignature(signature: string) { 16 | return execTyped<{ name: string; parameters: string }>( 17 | errorSignatureRegex, 18 | signature, 19 | ) 20 | } 21 | 22 | // https://regexr.com/7gmoq 23 | const eventSignatureRegex = 24 | /^event (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/ 25 | export function isEventSignature(signature: string) { 26 | return eventSignatureRegex.test(signature) 27 | } 28 | export function execEventSignature(signature: string) { 29 | return execTyped<{ name: string; parameters: string }>( 30 | eventSignatureRegex, 31 | signature, 32 | ) 33 | } 34 | 35 | // https://regexr.com/7gmot 36 | const functionSignatureRegex = 37 | /^function (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)(?: (?external|public{1}))?(?: (?pure|view|nonpayable|payable{1}))?(?: returns\s?\((?.*?)\))?$/ 38 | export function isFunctionSignature(signature: string) { 39 | return functionSignatureRegex.test(signature) 40 | } 41 | export function execFunctionSignature(signature: string) { 42 | return execTyped<{ 43 | name: string 44 | parameters: string 45 | stateMutability?: AbiStateMutability 46 | returns?: string 47 | }>(functionSignatureRegex, signature) 48 | } 49 | 50 | // https://regexr.com/7gmp3 51 | const structSignatureRegex = 52 | /^struct (?[a-zA-Z$_][a-zA-Z0-9$_]*) \{(?.*?)\}$/ 53 | export function isStructSignature(signature: string) { 54 | return structSignatureRegex.test(signature) 55 | } 56 | export function execStructSignature(signature: string) { 57 | return execTyped<{ name: string; properties: string }>( 58 | structSignatureRegex, 59 | signature, 60 | ) 61 | } 62 | 63 | // https://regexr.com/78u01 64 | const constructorSignatureRegex = 65 | /^constructor\((?.*?)\)(?:\s(?payable{1}))?$/ 66 | export function isConstructorSignature(signature: string) { 67 | return constructorSignatureRegex.test(signature) 68 | } 69 | export function execConstructorSignature(signature: string) { 70 | return execTyped<{ 71 | parameters: string 72 | stateMutability?: Extract 73 | }>(constructorSignatureRegex, signature) 74 | } 75 | 76 | // https://regexr.com/7srtn 77 | const fallbackSignatureRegex = 78 | /^fallback\(\) external(?:\s(?payable{1}))?$/ 79 | export function isFallbackSignature(signature: string) { 80 | return fallbackSignatureRegex.test(signature) 81 | } 82 | export function execFallbackSignature(signature: string) { 83 | return execTyped<{ 84 | parameters: string 85 | stateMutability?: Extract 86 | }>(fallbackSignatureRegex, signature) 87 | } 88 | 89 | // https://regexr.com/78u1k 90 | const receiveSignatureRegex = /^receive\(\) external payable$/ 91 | export function isReceiveSignature(signature: string) { 92 | return receiveSignatureRegex.test(signature) 93 | } 94 | 95 | export const modifiers = new Set([ 96 | 'memory', 97 | 'indexed', 98 | 'storage', 99 | 'calldata', 100 | ]) 101 | export const eventModifiers = new Set(['indexed']) 102 | export const functionModifiers = new Set([ 103 | 'calldata', 104 | 'memory', 105 | 'storage', 106 | ]) 107 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/runtime/structs.ts: -------------------------------------------------------------------------------- 1 | import type { AbiParameter } from '../../abi.js' 2 | import { execTyped, isTupleRegex } from '../../regex.js' 3 | import { UnknownTypeError } from '../errors/abiItem.js' 4 | import { InvalidAbiTypeParameterError } from '../errors/abiParameter.js' 5 | import { 6 | InvalidSignatureError, 7 | InvalidStructSignatureError, 8 | } from '../errors/signature.js' 9 | import { CircularReferenceError } from '../errors/struct.js' 10 | import type { StructLookup } from '../types/structs.js' 11 | import { execStructSignature, isStructSignature } from './signatures.js' 12 | import { isSolidityType, parseAbiParameter } from './utils.js' 13 | 14 | export function parseStructs(signatures: readonly string[]) { 15 | // Create "shallow" version of each struct (and filter out non-structs or invalid structs) 16 | const shallowStructs: StructLookup = {} 17 | const signaturesLength = signatures.length 18 | for (let i = 0; i < signaturesLength; i++) { 19 | const signature = signatures[i]! 20 | if (!isStructSignature(signature)) continue 21 | 22 | const match = execStructSignature(signature) 23 | if (!match) throw new InvalidSignatureError({ signature, type: 'struct' }) 24 | 25 | const properties = match.properties.split(';') 26 | 27 | const components: AbiParameter[] = [] 28 | const propertiesLength = properties.length 29 | for (let k = 0; k < propertiesLength; k++) { 30 | const property = properties[k]! 31 | const trimmed = property.trim() 32 | if (!trimmed) continue 33 | const abiParameter = parseAbiParameter(trimmed, { 34 | type: 'struct', 35 | }) 36 | components.push(abiParameter) 37 | } 38 | 39 | if (!components.length) throw new InvalidStructSignatureError({ signature }) 40 | shallowStructs[match.name] = components 41 | } 42 | 43 | // Resolve nested structs inside each parameter 44 | const resolvedStructs: StructLookup = {} 45 | const entries = Object.entries(shallowStructs) 46 | const entriesLength = entries.length 47 | for (let i = 0; i < entriesLength; i++) { 48 | const [name, parameters] = entries[i]! 49 | resolvedStructs[name] = resolveStructs(parameters, shallowStructs) 50 | } 51 | 52 | return resolvedStructs 53 | } 54 | 55 | const typeWithoutTupleRegex = 56 | /^(?[a-zA-Z$_][a-zA-Z0-9$_]*)(?(?:\[\d*?\])+?)?$/ 57 | 58 | function resolveStructs( 59 | abiParameters: readonly (AbiParameter & { indexed?: true })[], 60 | structs: StructLookup, 61 | ancestors = new Set(), 62 | ) { 63 | const components: AbiParameter[] = [] 64 | const length = abiParameters.length 65 | for (let i = 0; i < length; i++) { 66 | const abiParameter = abiParameters[i]! 67 | const isTuple = isTupleRegex.test(abiParameter.type) 68 | if (isTuple) components.push(abiParameter) 69 | else { 70 | const match = execTyped<{ array?: string; type: string }>( 71 | typeWithoutTupleRegex, 72 | abiParameter.type, 73 | ) 74 | if (!match?.type) throw new InvalidAbiTypeParameterError({ abiParameter }) 75 | 76 | const { array, type } = match 77 | if (type in structs) { 78 | if (ancestors.has(type)) throw new CircularReferenceError({ type }) 79 | 80 | components.push({ 81 | ...abiParameter, 82 | type: `tuple${array ?? ''}`, 83 | components: resolveStructs( 84 | structs[type] ?? [], 85 | structs, 86 | new Set([...ancestors, type]), 87 | ), 88 | }) 89 | } else { 90 | if (isSolidityType(type)) components.push(abiParameter) 91 | else throw new UnknownTypeError({ type }) 92 | } 93 | } 94 | } 95 | 96 | return components 97 | } 98 | -------------------------------------------------------------------------------- /packages/abitype/src/human-readable/types/structs.ts: -------------------------------------------------------------------------------- 1 | import type { AbiParameter } from '../../abi.js' 2 | import type { Error, Trim } from '../../types.js' 3 | import type { StructSignature } from './signatures.js' 4 | import type { ParseAbiParameter } from './utils.js' 5 | 6 | export type StructLookup = Record 7 | 8 | export type ParseStructs = 9 | // Create "shallow" version of each struct (and filter out non-structs or invalid structs) 10 | { 11 | [signature in signatures[number] as ParseStruct extends infer struct extends 12 | { 13 | name: string 14 | } 15 | ? struct['name'] 16 | : never]: ParseStruct['components'] 17 | } extends infer structs extends Record< 18 | string, 19 | readonly (AbiParameter & { type: string })[] 20 | > 21 | ? // Resolve nested structs inside each struct 22 | { 23 | [structName in keyof structs]: ResolveStructs< 24 | structs[structName], 25 | structs 26 | > 27 | } 28 | : never 29 | 30 | export type ParseStruct< 31 | signature extends string, 32 | structs extends StructLookup | unknown = unknown, 33 | > = signature extends StructSignature 34 | ? { 35 | readonly name: Trim 36 | readonly components: ParseStructProperties 37 | } 38 | : never 39 | 40 | export type ResolveStructs< 41 | abiParameters extends readonly (AbiParameter & { type: string })[], 42 | structs extends Record, 43 | keyReferences extends { [_: string]: unknown } | unknown = unknown, 44 | > = readonly [ 45 | ...{ 46 | [key in keyof abiParameters]: abiParameters[key]['type'] extends `${infer head extends 47 | string & keyof structs}[${infer tail}]` // Struct arrays (e.g. `type: 'Struct[]'`, `type: 'Struct[10]'`, `type: 'Struct[][]'`) 48 | ? head extends keyof keyReferences 49 | ? Error<`Circular reference detected. Struct "${abiParameters[key]['type']}" is a circular reference.`> 50 | : { 51 | readonly name: abiParameters[key]['name'] 52 | readonly type: `tuple[${tail}]` 53 | readonly components: ResolveStructs< 54 | structs[head], 55 | structs, 56 | keyReferences & { [_ in head]: true } 57 | > 58 | } 59 | : // Basic struct (e.g. `type: 'Struct'`) 60 | abiParameters[key]['type'] extends keyof structs 61 | ? abiParameters[key]['type'] extends keyof keyReferences 62 | ? Error<`Circular reference detected. Struct "${abiParameters[key]['type']}" is a circular reference.`> 63 | : { 64 | readonly name: abiParameters[key]['name'] 65 | readonly type: 'tuple' 66 | readonly components: ResolveStructs< 67 | structs[abiParameters[key]['type']], 68 | structs, 69 | keyReferences & { [_ in abiParameters[key]['type']]: true } 70 | > 71 | } 72 | : abiParameters[key] 73 | }, 74 | ] 75 | 76 | export type ParseStructProperties< 77 | signature extends string, 78 | structs extends StructLookup | unknown = unknown, 79 | result extends any[] = [], 80 | > = Trim extends `${infer head};${infer tail}` 81 | ? ParseStructProperties< 82 | tail, 83 | structs, 84 | [...result, ParseAbiParameter] 85 | > 86 | : result 87 | -------------------------------------------------------------------------------- /packages/abitype/src/narrow.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, expectTypeOf, test } from 'vitest' 2 | 3 | import type { Narrow } from './narrow.js' 4 | import { narrow } from './narrow.js' 5 | 6 | test('Narrow', () => { 7 | expectTypeOf>().toEqualTypeOf<['foo', 'bar', 1]>() 8 | expectTypeOf>().toEqualTypeOf() 9 | }) 10 | 11 | test('narrow', () => { 12 | const asConst = narrow(['foo', 'bar', 1]) 13 | // ^? 14 | type Result = typeof asConst 15 | expectTypeOf().toEqualTypeOf<['foo', 'bar', 1]>() 16 | 17 | assertType<'foo'>(narrow('foo')) 18 | assertType<1>(narrow(1)) 19 | assertType(narrow(true)) 20 | assertType<{ foo: 'bar' }>(narrow({ foo: 'bar' })) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/abitype/src/narrow.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { narrow } from './narrow.js' 4 | 5 | test('narrow', () => { 6 | expect(narrow('foo')).toMatchInlineSnapshot('"foo"') 7 | }) 8 | -------------------------------------------------------------------------------- /packages/abitype/src/narrow.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Infers embedded primitive type of any type 3 | * 4 | * @param T - Type to infer 5 | * @returns Embedded type of {@link type} 6 | * 7 | * @example 8 | * type Result = Narrow<['foo', 'bar', 1]> 9 | */ 10 | // s/o https://twitter.com/hd_nvim/status/1578567206190780417 11 | export type Narrow = 12 | | (unknown extends type ? unknown : never) 13 | | (type extends Function ? type : never) 14 | | (type extends bigint | boolean | number | string ? type : never) 15 | | (type extends [] ? [] : never) 16 | | { [K in keyof type]: Narrow } 17 | 18 | /** 19 | * Infers embedded primitive type of any type 20 | * Same as `as const` but without setting the object as readonly and without needing the user to use it. 21 | * 22 | * @param value - Value to infer 23 | * @returns Value with embedded type inferred 24 | * 25 | * @example 26 | * const result = narrow(['foo', 'bar', 1]) 27 | */ 28 | export function narrow(value: Narrow) { 29 | return value 30 | } 31 | -------------------------------------------------------------------------------- /packages/abitype/src/regex.ts: -------------------------------------------------------------------------------- 1 | // TODO: This looks cool. Need to check the performance of `new RegExp` versus defined inline though. 2 | // https://twitter.com/GabrielVergnaud/status/1622906834343366657 3 | export function execTyped(regex: RegExp, string: string) { 4 | const match = regex.exec(string) 5 | return match?.groups as type | undefined 6 | } 7 | 8 | // `bytes`: binary type of `M` bytes, `0 < M <= 32` 9 | // https://regexr.com/6va55 10 | export const bytesRegex = /^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/ 11 | 12 | // `(u)int`: (un)signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0` 13 | // https://regexr.com/6v8hp 14 | export const integerRegex = 15 | /^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/ 16 | 17 | export const isTupleRegex = /^\(.+?\).*?$/ 18 | -------------------------------------------------------------------------------- /packages/abitype/src/register.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, test } from 'vitest' 2 | 3 | import type { ResolvedRegister } from './register.js' 4 | 5 | test('ResolvedRegister', () => { 6 | assertType(false) 7 | assertType(1) 8 | assertType(99) 9 | 10 | type AddressType = ResolvedRegister['addressType'] 11 | assertType('0x0000000000000000000000000000000000000000') 12 | 13 | type BytesType = ResolvedRegister['bytesType'] 14 | assertType({ 15 | inputs: '0xfoobarbaz', 16 | outputs: '0xfoobarbaz', 17 | }) 18 | 19 | type IntType = ResolvedRegister['intType'] 20 | assertType(123) 21 | 22 | type BigIntType = ResolvedRegister['bigIntType'] 23 | assertType(123n) 24 | 25 | type StrictAbiType = ResolvedRegister['strictAbiType'] 26 | assertType(false) 27 | }) 28 | -------------------------------------------------------------------------------- /packages/abitype/src/types.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, expectTypeOf, test } from 'vitest' 2 | 3 | import type { 4 | Error, 5 | Filter, 6 | IsNarrowable, 7 | IsNever, 8 | IsUnknown, 9 | Join, 10 | Merge, 11 | OneOf, 12 | Range, 13 | Trim, 14 | Tuple, 15 | } from './types.js' 16 | 17 | test('Error', () => { 18 | expectTypeOf>().toEqualTypeOf< 19 | ['Error: Custom error message'] 20 | >() 21 | expectTypeOf< 22 | Error<['Custom error message', 'Another custom message']> 23 | >().toEqualTypeOf< 24 | ['Error: Custom error message', 'Error: Another custom message'] 25 | >() 26 | }) 27 | 28 | test('Filter', () => { 29 | expectTypeOf>().toEqualTypeOf< 30 | readonly [1, 'foo', 'baz'] 31 | >() 32 | }) 33 | 34 | test('IsNarrowable', () => { 35 | expectTypeOf>().toEqualTypeOf() 36 | expectTypeOf>().toEqualTypeOf() 37 | }) 38 | 39 | test('IsNever', () => { 40 | expectTypeOf>().toEqualTypeOf() 41 | 42 | expectTypeOf>().toEqualTypeOf() 43 | expectTypeOf>().toEqualTypeOf() 44 | expectTypeOf>().toEqualTypeOf() 45 | expectTypeOf>().toEqualTypeOf() 46 | expectTypeOf>().toEqualTypeOf() 47 | expectTypeOf>().toEqualTypeOf() 48 | expectTypeOf>().toEqualTypeOf() 49 | expectTypeOf>().toEqualTypeOf() 50 | }) 51 | 52 | test('IsUnknown', () => { 53 | expectTypeOf>().toEqualTypeOf() 54 | expectTypeOf>().toEqualTypeOf() 55 | }) 56 | 57 | test('Join', () => { 58 | assertType>('foo') 59 | assertType>('foo,bar') 60 | assertType>('foo,bar,baz') 61 | }) 62 | 63 | test('Merge', () => { 64 | assertType>({ foo: 123, bar: 'abc' }) 65 | assertType>({ 66 | foo: 'xyz', 67 | bar: 'abc', 68 | }) 69 | }) 70 | 71 | test('OneOf', () => { 72 | assertType>({ foo: false }) 73 | assertType>({ bar: false }) 74 | }) 75 | 76 | test('Range', () => { 77 | assertType>([0, 1, 2]) 78 | assertType>([10, 11, 12]) 79 | assertType>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 80 | assertType>([1]) 81 | assertType>([]) 82 | // @ts-expect-error Only positive ranges work 83 | assertType>([-2, -1, 0]) 84 | }) 85 | 86 | test('Trim', () => { 87 | assertType>('foo bar baz') 88 | assertType>('foo bar baz') 89 | assertType>( 90 | 'foo bar baz', 91 | ) 92 | }) 93 | 94 | test('Tuple', () => { 95 | expectTypeOf>().toEqualTypeOf() 96 | expectTypeOf>().toEqualTypeOf< 97 | readonly [string | number, string | number] 98 | >() 99 | }) 100 | -------------------------------------------------------------------------------- /packages/abitype/src/version.ts: -------------------------------------------------------------------------------- 1 | export const version = '1.0.8' 2 | -------------------------------------------------------------------------------- /packages/abitype/test/globalSetup.ts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'node:path' 2 | import { fileURLToPath } from 'node:url' 3 | import { setup } from '@arktype/attest' 4 | 5 | const __filename = fileURLToPath(import.meta.url) 6 | const __dirname = dirname(__filename) 7 | 8 | export default function () { 9 | return setup({ 10 | benchErrorOnThresholdExceeded: true, 11 | tsconfig: resolve(__dirname, '../tsconfig.json'), 12 | formatter: 'pnpm biome format --write', 13 | }) 14 | } 15 | 16 | // biome-ignore lint/performance/noBarrelFile: 17 | export { teardown } from '@arktype/attest' 18 | -------------------------------------------------------------------------------- /packages/abitype/test/setup.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest' 2 | 3 | vi.mock('../src/version.ts', () => { 4 | return { version: 'x.y.z' } 5 | }) 6 | -------------------------------------------------------------------------------- /packages/abitype/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": [ 5 | "src/**/*.bench.ts", 6 | "src/**/*.bench-d.ts", 7 | "src/**/*.test.ts", 8 | "src/**/*.test-d.ts" 9 | ], 10 | "compilerOptions": { 11 | "sourceMap": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/abitype/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This configuration is used for local development and type checking. 3 | "extends": "./tsconfig.build.json", 4 | "include": ["src/**/*.ts", "test/**/*.ts"], 5 | "exclude": [] 6 | } 7 | -------------------------------------------------------------------------------- /packages/register-tests/default/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "default-register", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "check:types": "tsc --noEmit" 7 | }, 8 | "dependencies": { 9 | "abitype": "workspace:*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/register-tests/default/src/address.test-d.ts: -------------------------------------------------------------------------------- 1 | import type { Address } from 'abitype' 2 | import { expectTypeOf, test } from 'vitest' 3 | 4 | test('default', async () => { 5 | expectTypeOf
().toEqualTypeOf< 6 | `0x${string}` & { _tag: 'addressType' } 7 | >() 8 | }) 9 | -------------------------------------------------------------------------------- /packages/register-tests/default/src/register.ts: -------------------------------------------------------------------------------- 1 | declare module 'abitype' { 2 | interface Register { 3 | addressType: `0x${string}` & { _tag: 'addressType' } 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/register-tests/default/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": [] 5 | } 6 | -------------------------------------------------------------------------------- /playgrounds/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "check:types": "tsc --noEmit" 7 | }, 8 | "dependencies": { 9 | "abitype": "workspace:*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/functions/src/read.ts: -------------------------------------------------------------------------------- 1 | import type { Abi } from 'abitype' 2 | import type { wagmiMintExampleAbi } from 'abitype/abis' 3 | 4 | import type { 5 | ContractParameters, 6 | ContractReturnType, 7 | DeepPartial, 8 | } from './types.js' 9 | 10 | export declare function read< 11 | const abi extends Abi | readonly unknown[], // `readonly unknown[]` allows for non-const asserted types 12 | functionName extends string, 13 | const args extends readonly unknown[] | undefined, 14 | >( 15 | parameters: ReadParameters, 16 | ): ReadReturnType 17 | 18 | export declare function readWagmiMintExample< 19 | functionName extends string, 20 | const args extends readonly unknown[] | undefined, 21 | >( 22 | parameters: Omit< 23 | ReadParameters, 24 | 'abi' 25 | >, 26 | ): ReadReturnType 27 | 28 | export declare function useRead< 29 | const abi extends Abi | readonly unknown[], // `readonly unknown[]` allows for non-const asserted types 30 | const functionName extends string, 31 | const args extends readonly unknown[] | undefined, 32 | >( 33 | parameters: DeepPartial, 1>, 34 | ): ReadReturnType 35 | 36 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 37 | 38 | export type ReadParameters< 39 | abi extends Abi | readonly unknown[], 40 | functionName extends string, 41 | args extends readonly unknown[] | undefined = readonly unknown[] | undefined, 42 | > = { abi: abi } & ContractParameters 43 | 44 | export type ReadReturnType< 45 | abi extends Abi | readonly unknown[], 46 | functionName extends string, 47 | args extends readonly unknown[] | undefined, 48 | > = ContractReturnType 49 | -------------------------------------------------------------------------------- /playgrounds/functions/src/signTypedData.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ResolvedRegister, 3 | TypedData, 4 | TypedDataDomain, 5 | TypedDataToPrimitiveTypes, 6 | } from 'abitype' 7 | 8 | export declare function signTypedData< 9 | const typedData extends TypedData | Record, // `Record` allows for non-const asserted types 10 | primaryType extends keyof typedData, 11 | >( 12 | parameters: SignTypedDataParameters, 13 | ): SignTypedDataReturnType 14 | 15 | export type SignTypedDataParameters< 16 | typedData extends TypedData | Record, 17 | primaryType extends keyof typedData, 18 | /// 19 | schema extends Record = typedData extends TypedData 20 | ? TypedDataToPrimitiveTypes 21 | : { [_: string]: any }, 22 | message extends schema[keyof schema] = schema[primaryType extends keyof schema 23 | ? primaryType 24 | : keyof schema], 25 | > = { 26 | domain: TypedDataDomain 27 | primaryType: 28 | | primaryType // infer value 29 | | keyof typedData // show all values 30 | types: typedData 31 | message: { [_: string]: any } extends message // Check if message was inferred 32 | ? Record 33 | : message 34 | } 35 | 36 | export type SignTypedDataReturnType = ResolvedRegister['bytesType']['outputs'] 37 | -------------------------------------------------------------------------------- /playgrounds/functions/src/watchEvent.test-d.ts: -------------------------------------------------------------------------------- 1 | import type { Abi, ResolvedRegister } from 'abitype' 2 | import { wagmiMintExampleAbi, writingEditionsFactoryAbi } from 'abitype/abis' 3 | import { assertType, test } from 'vitest' 4 | 5 | import { watchEvent } from './watchEvent.js' 6 | 7 | test('args', () => { 8 | test('zero', () => { 9 | watchEvent({ 10 | abi: [ 11 | { 12 | name: 'Foo', 13 | type: 'event', 14 | inputs: [], 15 | anonymous: false, 16 | }, 17 | { 18 | name: 'Bar', 19 | type: 'event', 20 | inputs: [{ name: 'baz', type: 'uint256', indexed: false }], 21 | anonymous: false, 22 | }, 23 | ], 24 | eventName: 'Foo', 25 | onEmit(...args) { 26 | assertType<[]>(args) 27 | }, 28 | }) 29 | }) 30 | 31 | test('one', () => { 32 | watchEvent({ 33 | abi: writingEditionsFactoryAbi, 34 | eventName: 'FactoryGuardSet', 35 | onEmit(guard) { 36 | assertType(guard) 37 | }, 38 | }) 39 | }) 40 | 41 | test('two or more', () => { 42 | watchEvent({ 43 | abi: wagmiMintExampleAbi, 44 | eventName: 'Transfer', 45 | onEmit(from, to, tokenId) { 46 | assertType(from) 47 | assertType(to) 48 | assertType(tokenId) 49 | }, 50 | }) 51 | }) 52 | }) 53 | 54 | test('behavior', () => { 55 | test('works without const assertion', () => { 56 | const abi = [ 57 | { 58 | name: 'Foo', 59 | type: 'event', 60 | inputs: [ 61 | { 62 | indexed: true, 63 | name: 'name', 64 | type: 'address', 65 | }, 66 | ], 67 | anonymous: false, 68 | }, 69 | ] 70 | watchEvent({ 71 | abi, 72 | eventName: 'Foo', 73 | onEmit(name) { 74 | assertType(name) 75 | }, 76 | }) 77 | }) 78 | 79 | test('declared as Abi type', () => { 80 | const abi: Abi = [ 81 | { 82 | name: 'Foo', 83 | type: 'event', 84 | inputs: [ 85 | { 86 | indexed: true, 87 | name: 'name', 88 | type: 'address', 89 | }, 90 | ], 91 | anonymous: false, 92 | }, 93 | ] 94 | watchEvent({ 95 | abi, 96 | eventName: 'Foo', 97 | onEmit(name) { 98 | assertType(name) 99 | }, 100 | }) 101 | }) 102 | 103 | test('defined inline', () => { 104 | watchEvent({ 105 | abi: [ 106 | { 107 | name: 'Foo', 108 | type: 'event', 109 | inputs: [ 110 | { 111 | indexed: true, 112 | name: 'name', 113 | type: 'address', 114 | }, 115 | ], 116 | anonymous: false, 117 | }, 118 | ], 119 | eventName: 'Foo', 120 | onEmit(name) { 121 | assertType(name) 122 | }, 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /playgrounds/functions/src/watchEvent.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Abi, 3 | AbiEvent, 4 | AbiParametersToPrimitiveTypes, 5 | ExtractAbiEvent, 6 | ExtractAbiEventNames, 7 | } from 'abitype' 8 | 9 | export declare function watchEvent< 10 | const abi extends Abi | readonly unknown[], // `readonly unknown[]` allows for non-const asserted types 11 | eventName extends string, 12 | >(parameters: WatchEventParameters): WatchEventReturnType 13 | 14 | export type WatchEventParameters< 15 | abi extends Abi | readonly unknown[], 16 | eventName extends string, 17 | /// 18 | eventNames extends string = abi extends Abi 19 | ? ExtractAbiEventNames 20 | : string, 21 | abiEvent extends AbiEvent = abi extends Abi 22 | ? ExtractAbiEvent 23 | : AbiEvent, 24 | primitiveTypes = AbiParametersToPrimitiveTypes, 25 | > = { 26 | abi: abi 27 | eventName: 28 | | eventNames // show all values 29 | | (eventName extends eventNames ? eventName : never) // infer value (if valid) 30 | | (Abi extends abi ? string : never) // fallback if `abi` is declared as `Abi` 31 | onEmit: Abi extends abi 32 | ? (...args: unknown[]) => void // `abi` declared as `Abi` 33 | : abi extends Abi 34 | ? ( 35 | // `abi` was inferrable 36 | ...args: primitiveTypes extends readonly unknown[] 37 | ? primitiveTypes 38 | : unknown[] 39 | ) => void 40 | : (...args: unknown[]) => void // fallback 41 | } 42 | 43 | export type WatchEventReturnType = () => void 44 | -------------------------------------------------------------------------------- /playgrounds/functions/src/write.ts: -------------------------------------------------------------------------------- 1 | import type { Abi } from 'abitype' 2 | 3 | import type { ContractParameters, ContractReturnType } from './types.js' 4 | 5 | export declare function write< 6 | const abi extends Abi | readonly unknown[], // `readonly unknown[]` allows for non-const asserted types 7 | functionName extends string, 8 | const args extends readonly unknown[] | undefined, 9 | >( 10 | parameters: WriteParameters, 11 | ): WriteReturnType 12 | 13 | export type WriteParameters< 14 | abi extends Abi | readonly unknown[], 15 | functionName extends string, 16 | args extends readonly unknown[] | undefined = readonly unknown[] | undefined, 17 | > = { abi: abi } & ContractParameters< 18 | abi, 19 | functionName, 20 | 'nonpayable' | 'payable', 21 | args 22 | > 23 | 24 | export type WriteReturnType< 25 | abi extends Abi | readonly unknown[], 26 | functionName extends string, 27 | args extends readonly unknown[] | undefined, 28 | > = ContractReturnType 29 | -------------------------------------------------------------------------------- /playgrounds/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": [] 5 | } 6 | -------------------------------------------------------------------------------- /playgrounds/performance/index.ts: -------------------------------------------------------------------------------- 1 | import { parseAbi } from 'abitype' 2 | import { seaportHumanReadableAbi } from 'abitype/abis' 3 | 4 | // open `out/trace.json` in https://ui.perfetto.dev 5 | const result = parseAbi(seaportHumanReadableAbi) 6 | result[0] 7 | -------------------------------------------------------------------------------- /playgrounds/performance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "performance", 3 | "private": true, 4 | "type": "module", 5 | "devDependencies": { 6 | "abitype": "workspace:*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /playgrounds/performance/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "moduleResolution": "NodeNext", 5 | "baseUrl": ".", 6 | "paths": { 7 | "abitype": ["../../packages/abitype/src"], 8 | "abitype/*": ["../../packages/abitype/src/*"] 9 | } 10 | }, 11 | "include": ["index.ts"], 12 | "exclude": [] 13 | } 14 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - docs 3 | - packages/* 4 | - '!packages/register-tests' 5 | - packages/register-tests/* 6 | - playgrounds/* 7 | onlyBuiltDependencies: 8 | - bun 9 | -------------------------------------------------------------------------------- /scripts/formatPackageJson.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { glob } from 'glob' 3 | 4 | // Generates package.json files to be published to NPM with only the necessary fields. 5 | 6 | console.log('Formatting package.json files.') 7 | 8 | // Get all package.json files 9 | const packagePaths = await glob('packages/**/package.json', { 10 | ignore: ['**/dist/**', '**/node_modules/**'], 11 | }) 12 | 13 | let count = 0 14 | for (const packagePath of packagePaths) { 15 | type Package = Record & { 16 | name?: string | undefined 17 | private?: boolean | undefined 18 | } 19 | const file = Bun.file(packagePath) 20 | const packageJson = (await file.json()) as Package 21 | 22 | // Skip private packages 23 | if (packageJson.private) continue 24 | 25 | count += 1 26 | console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) 27 | 28 | await Bun.write( 29 | `${packagePath}.tmp`, 30 | `${JSON.stringify(packageJson, undefined, 2)}\n`, 31 | ) 32 | 33 | const { devDependencies: _dD, scripts: _s, type: _t, ...rest } = packageJson 34 | await Bun.write(packagePath, `${JSON.stringify(rest, undefined, 2)}\n`) 35 | } 36 | 37 | console.log(`Done. Formatted ${count} ${count === 1 ? 'file' : 'files'}.`) 38 | -------------------------------------------------------------------------------- /scripts/generateProxyPackages.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | import path from 'node:path' 3 | import { glob } from 'glob' 4 | 5 | // Generates proxy packages for package.json#exports. 6 | 7 | console.log('Generating proxy packages.') 8 | 9 | // Get all package.json files 10 | const packagePaths = await glob('packages/**/package.json', { 11 | ignore: ['**/dist/**', '**/node_modules/**'], 12 | }) 13 | 14 | let count = 0 15 | for (const packagePath of packagePaths) { 16 | type Package = Record & { 17 | name?: string | undefined 18 | private?: boolean | undefined 19 | exports?: 20 | | Record 21 | | undefined 22 | } 23 | const file = Bun.file(packagePath) 24 | const packageJson = (await file.json()) as Package 25 | 26 | // Skip private packages 27 | if (packageJson.private) continue 28 | if (!packageJson.exports) continue 29 | 30 | count += 1 31 | console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) 32 | 33 | const dir = path.resolve(path.dirname(packagePath)) 34 | 35 | for (const [key, exports] of Object.entries(packageJson.exports)) { 36 | // Skip `package.json` export 37 | if (/package\.json$/.test(key)) continue 38 | if (key === '.') continue 39 | if (typeof exports === 'string') continue 40 | if (!exports.default) continue 41 | 42 | const proxyDir = path.resolve(dir, key) 43 | await fs.mkdir(proxyDir, { recursive: true }) 44 | 45 | const types = path.relative(key, exports.types) 46 | const main = path.relative(key, exports.default) 47 | await Bun.write( 48 | `${proxyDir}/package.json`, 49 | `${JSON.stringify({ type: 'module', types, main }, undefined, 2)}\n`, 50 | ) 51 | } 52 | } 53 | 54 | console.log( 55 | `Done. Generated proxy packages for ${count} ${ 56 | count === 1 ? 'package' : 'packages' 57 | }.`, 58 | ) 59 | -------------------------------------------------------------------------------- /scripts/preconstruct.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | import path from 'node:path' 3 | import { glob } from 'glob' 4 | 5 | // Symlinks package sources to dist for local development 6 | 7 | console.log('Setting up packages for development.') 8 | 9 | // Get all package.json files 10 | const packagePaths = await glob('**/package.json', { 11 | ignore: ['**/dist/**', '**/node_modules/**'], 12 | }) 13 | 14 | let count = 0 15 | for (const packagePath of packagePaths) { 16 | type Package = { 17 | bin?: Record | undefined 18 | exports?: 19 | | Record 20 | | undefined 21 | name?: string | undefined 22 | private?: boolean | undefined 23 | } 24 | const file = Bun.file(packagePath) 25 | const packageJson = (await file.json()) as Package 26 | 27 | // Skip private packages 28 | if (packageJson.private) continue 29 | if (!packageJson.exports) continue 30 | if (packageJson.bin) continue 31 | 32 | count += 1 33 | console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) 34 | 35 | const dir = path.resolve(path.dirname(packagePath)) 36 | 37 | // Empty dist directory 38 | const dist = path.resolve(dir, 'dist') 39 | let files: string[] = [] 40 | try { 41 | files = await fs.readdir(dist) 42 | } catch { 43 | await fs.mkdir(dist) 44 | } 45 | 46 | const promises = [] 47 | for (const file of files) { 48 | promises.push( 49 | fs.rm(path.join(dist, file), { recursive: true, force: true }), 50 | ) 51 | } 52 | await Promise.all(promises) 53 | 54 | // Link exports to dist locations 55 | for (const [key, exports] of Object.entries(packageJson.exports)) { 56 | // Skip `package.json` exports 57 | if (/package\.json$/.test(key)) continue 58 | if (typeof exports === 'string') continue 59 | 60 | // Link exports to dist locations 61 | for (const [type, value] of Object.entries(exports) as [ 62 | type: 'types' | 'default', 63 | value: string, 64 | ][]) { 65 | const srcDir = path.resolve( 66 | dir, 67 | path 68 | .dirname(value) 69 | .replace(`dist/${type === 'default' ? 'esm' : type}`, 'src'), 70 | ) 71 | let srcFileName: string 72 | if (key === '.') srcFileName = 'index.ts' 73 | else srcFileName = path.basename(`${key}.ts`) 74 | const srcFilePath = path.resolve(srcDir, srcFileName) 75 | 76 | const distDir = path.resolve(dir, path.dirname(value)) 77 | const distFileName = path.basename(value) 78 | const distFilePath = path.resolve(distDir, distFileName) 79 | 80 | await fs.mkdir(distDir, { recursive: true }) 81 | 82 | // Symlink src to dist file 83 | await fs.symlink(srcFilePath, distFilePath, 'file') 84 | } 85 | } 86 | } 87 | 88 | console.log(`Done. Set up ${count} ${count === 1 ? 'package' : 'packages'}.`) 89 | -------------------------------------------------------------------------------- /scripts/restorePackageJson.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | import path from 'node:path' 3 | import { glob } from 'glob' 4 | 5 | // Restores package.json files from package.json.tmp files. 6 | 7 | console.log('Restoring package.json files.') 8 | 9 | // Get all package.json files 10 | const packagePaths = await glob('packages/**/package.json.tmp', { 11 | ignore: ['**/dist/**', '**/node_modules/**'], 12 | }) 13 | 14 | let count = 0 15 | for (const packagePath of packagePaths) { 16 | type Package = { name?: string | undefined } & Record 17 | const file = Bun.file(packagePath) 18 | const packageJson = (await file.json()) as Package 19 | 20 | count += 1 21 | console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) 22 | 23 | await Bun.write( 24 | packagePath.replace('.tmp', ''), 25 | `${JSON.stringify(packageJson, undefined, 2)}\n`, 26 | ) 27 | await fs.rm(packagePath) 28 | } 29 | 30 | console.log(`Done. Restored ${count} ${count === 1 ? 'file' : 'files'}.`) 31 | -------------------------------------------------------------------------------- /scripts/updateVersion.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'node:child_process' 2 | import path from 'node:path' 3 | import { glob } from 'glob' 4 | 5 | // Updates package version.ts files (so you can use the version in code without importing package.json). 6 | 7 | console.log('Updating version files.') 8 | 9 | // Get all package.json files 10 | const packagePaths = await glob('**/package.json', { 11 | ignore: ['**/dist/**', '**/node_modules/**'], 12 | }) 13 | 14 | let count = 0 15 | for (const packagePath of packagePaths) { 16 | type Package = { 17 | name?: string | undefined 18 | private?: boolean | undefined 19 | version?: string | undefined 20 | } 21 | const file = Bun.file(packagePath) 22 | const packageJson = (await file.json()) as Package 23 | 24 | // Skip private packages 25 | if (packageJson.private) continue 26 | 27 | const version = (() => { 28 | if (Bun.env.PKG_PR_NEW) { 29 | const gitHash = execSync('git rev-parse --short HEAD').toString().trim() 30 | const branch = execSync('git branch --show-current') 31 | .toString() 32 | .trim() 33 | .replace(/[^a-zA-Z0-9]/g, '_') 34 | return `0.0.0-${branch}.${gitHash}` 35 | } 36 | return packageJson.version 37 | })() 38 | 39 | count += 1 40 | console.log(`${packageJson.name} — ${version}`) 41 | 42 | const versionFilePath = path.resolve( 43 | path.dirname(packagePath), 44 | 'src', 45 | 'version.ts', 46 | ) 47 | await Bun.write(versionFilePath, `export const version = '${version}'\n`) 48 | 49 | const jsrFilePath = path.resolve(path.dirname(packagePath), 'jsr.json') 50 | const jsrJson = await Bun.file(jsrFilePath).json() 51 | jsrJson.version = version 52 | await Bun.write(jsrFilePath, JSON.stringify(jsrJson, null, 2)) 53 | 54 | if (Bun.env.PKG_PR_NEW) { 55 | packageJson.version = version 56 | await Bun.write(packagePath, JSON.stringify(packageJson, null, 2)) 57 | } 58 | } 59 | 60 | console.log( 61 | `Done. Updated version file for ${count} ${ 62 | count === 1 ? 'package' : 'packages' 63 | }.`, 64 | ) 65 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config. 3 | "include": [], 4 | "compilerOptions": { 5 | // Incremental builds 6 | // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes. 7 | "incremental": true, 8 | 9 | // Type checking 10 | "strict": true, 11 | "useDefineForClassFields": true, // Not enabled by default in `strict` mode unless we bump `target` to ES2022. 12 | "noFallthroughCasesInSwitch": true, // Not enabled by default in `strict` mode. 13 | "noImplicitReturns": true, // Not enabled by default in `strict` mode. 14 | "useUnknownInCatchVariables": true, // TODO: This would normally be enabled in `strict` mode but would require some adjustments to the codebase. 15 | "noImplicitOverride": true, // Not enabled by default in `strict` mode. 16 | "noUnusedLocals": true, // Not enabled by default in `strict` mode. 17 | "noUnusedParameters": true, // Not enabled by default in `strict` mode. 18 | "exactOptionalPropertyTypes": true, 19 | "noUncheckedIndexedAccess": true, 20 | 21 | // JavaScript support 22 | "allowJs": false, 23 | "checkJs": false, 24 | 25 | // Interop constraints 26 | "forceConsistentCasingInFileNames": true, 27 | "verbatimModuleSyntax": true, 28 | 29 | // Language and environment 30 | "moduleResolution": "NodeNext", 31 | "module": "NodeNext", 32 | "target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping. 33 | "lib": [ 34 | "ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances. 35 | "DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped. 36 | ], 37 | 38 | // Skip type checking for node modules 39 | "skipLibCheck": true, 40 | "noErrorTruncation": true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This configuration is used for local development and type checking. 3 | "extends": "./tsconfig.base.json", 4 | "include": ["scripts/**/*.ts", "vitest.workspace.ts"], 5 | "exclude": [], 6 | "compilerOptions": { 7 | "types": ["@types/bun"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | benchmark: { 6 | outputFile: 'bench/report.json', 7 | reporters: ['verbose'], 8 | }, 9 | coverage: { 10 | exclude: [ 11 | '**/dist/**', 12 | '**/test/**', 13 | '**/*.bench.ts', 14 | '**/*.bench-d.ts', 15 | '**/*.test.ts', 16 | '**/*.test-d.ts', 17 | 'src/version.ts', 18 | ], 19 | reporter: process.env.CI ? ['lcov'] : ['text', 'json', 'html'], 20 | }, 21 | environment: 'node', 22 | globalSetup: process.env.TYPES ? ['./test/globalSetup.ts'] : undefined, 23 | include: [ 24 | ...(process.env.TYPES ? ['**/*.bench-d.ts'] : []), 25 | '**/*.test.ts', 26 | ], 27 | root: './packages/abitype', 28 | setupFiles: ['./test/setup.ts'], 29 | }, 30 | }) 31 | --------------------------------------------------------------------------------