├── .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 |
12 |
--------------------------------------------------------------------------------
/.github/logo-light.svg:
--------------------------------------------------------------------------------
1 |
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 |
7 |
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 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/docs/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wevm/abitype/d153cd678b32783235a6dadd4901262b395eee9a/docs/public/favicon.png
--------------------------------------------------------------------------------
/docs/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
17 |
18 |
--------------------------------------------------------------------------------
/docs/public/logo-dark.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/docs/public/logo-light.svg:
--------------------------------------------------------------------------------
1 |
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