diff --git a/.changeset/angry-squids-rescue.md b/.changeset/angry-squids-rescue.md new file mode 100644 index 0000000000..0a8626dbc2 --- /dev/null +++ b/.changeset/angry-squids-rescue.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +Added experimental named tuple support for contract-related actions and utilities. diff --git a/.github/actions/setup-wagmi/action.yml b/.github/actions/setup-wagmi/action.yml index ed075e95ec..299e23c6dd 100644 --- a/.github/actions/setup-wagmi/action.yml +++ b/.github/actions/setup-wagmi/action.yml @@ -33,11 +33,6 @@ runs: run: pnpm install --no-frozen-lockfile working-directory: ./wagmi - - name: Build packages - shell: bash - run: pnpm build - working-directory: ./wagmi - - name: Link packages shell: bash run: pnpm preconstruct diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 04a7959472..a54b0d9318 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -227,7 +227,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - typescript-version: ['5.7.2', '5.8.2', '5.9.2', 'latest'] + typescript-version: ['5.7.2', '5.8.3', '5.9.2', 'latest'] steps: - name: Clone repository diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4328e4a137..0640e55fc5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -581,8 +581,8 @@ importers: specifier: 1.6.0 version: 1.6.0 abitype: - specifier: 1.1.0 - version: 1.1.0(typescript@5.9.3)(zod@3.25.76) + specifier: 1.2.3 + version: 1.2.3(typescript@5.9.3)(zod@3.25.76) isows: specifier: 1.0.7 version: 1.0.7(ws@8.18.3) @@ -3329,6 +3329,17 @@ packages: zod: optional: true + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -3585,9 +3596,6 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - caniuse-lite@1.0.30001701: - resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==} - caniuse-lite@1.0.30001751: resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} @@ -4049,10 +4057,6 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - detect-libc@2.1.1: resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} engines: {node: '>=8'} @@ -8466,7 +8470,7 @@ snapshots: extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.4.0 - semver: 7.6.3 + semver: 7.7.2 tar-fs: 3.1.1 unbzip2-stream: 1.4.3 yargs: 17.7.2 @@ -10003,7 +10007,17 @@ snapshots: typescript: 5.9.3 zod: 3.23.8 - abitype@1.1.0(typescript@5.9.3)(zod@3.25.76): + abitype@1.2.3(typescript@5.6.2)(zod@3.25.76): + optionalDependencies: + typescript: 5.6.2 + zod: 3.25.76 + + abitype@1.2.3(typescript@5.9.3)(zod@3.23.8): + optionalDependencies: + typescript: 5.9.3 + zod: 3.23.8 + + abitype@1.2.3(typescript@5.9.3)(zod@3.25.76): optionalDependencies: typescript: 5.9.3 zod: 3.25.76 @@ -10202,14 +10216,14 @@ snapshots: browserslist@4.24.2: dependencies: - caniuse-lite: 1.0.30001701 + caniuse-lite: 1.0.30001751 electron-to-chromium: 1.5.50 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001701 + caniuse-lite: 1.0.30001751 electron-to-chromium: 1.5.107 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.4) @@ -10265,8 +10279,6 @@ snapshots: cac@6.7.14: {} - caniuse-lite@1.0.30001701: {} - caniuse-lite@1.0.30001751: {} ccount@2.0.1: {} @@ -10692,9 +10704,6 @@ snapshots: detect-indent@6.1.0: {} - detect-libc@2.0.3: - optional: true - detect-libc@2.1.1: {} detect-node-es@1.1.0: {} @@ -12493,7 +12502,7 @@ snapshots: node-gyp-build-optional-packages@5.2.2: dependencies: - detect-libc: 2.0.3 + detect-libc: 2.1.1 optional: true node-releases@2.0.18: {} @@ -12590,7 +12599,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.6.2)(zod@3.25.76) + abitype: 1.2.3(typescript@5.6.2)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.6.2 @@ -12605,7 +12614,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.9.3)(zod@3.23.8) + abitype: 1.2.3(typescript@5.9.3)(zod@3.23.8) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 @@ -12620,7 +12629,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.9.3)(zod@3.25.76) + abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 @@ -13999,7 +14008,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.6.2)(zod@3.25.76) + abitype: 1.2.3(typescript@5.6.2)(zod@3.25.76) isows: 1.0.7(ws@8.18.3) ox: 0.9.6(typescript@5.6.2)(zod@3.25.76) ws: 8.18.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6293fba57c..804a5ce82f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -11,6 +11,7 @@ catalog: minimumReleaseAge: 1440 minimumReleaseAgeExclude: - '@vitest/*' + - abitype - glob - hono - vitest diff --git a/src/actions/getContract.ts b/src/actions/getContract.ts index 6d2092356f..8314a7ed40 100644 --- a/src/actions/getContract.ts +++ b/src/actions/getContract.ts @@ -825,7 +825,7 @@ type GetReadFunction< ? ExtractAbiFunction : AbiFunction, // - _args = AbiParametersToPrimitiveTypes, + _args = AbiParametersToPrimitiveTypes, _options = Prettify< UnionOmit< ReadContractParameters, @@ -859,7 +859,7 @@ type GetEstimateFunction< ? ExtractAbiFunction : AbiFunction, // - _args = AbiParametersToPrimitiveTypes, + _args = AbiParametersToPrimitiveTypes, _options = Prettify< UnionOmit< EstimateContractGasParameters, @@ -909,7 +909,7 @@ type GetSimulateFunction< ? ExtractAbiFunction : AbiFunction, // - _args = AbiParametersToPrimitiveTypes, + _args = AbiParametersToPrimitiveTypes, > = narrowable extends true ? < chainOverride extends Chain | undefined = undefined, @@ -1003,7 +1003,7 @@ type GetWriteFunction< ? ExtractAbiFunction : AbiFunction, // - _args = AbiParametersToPrimitiveTypes, + _args = AbiParametersToPrimitiveTypes, // For making `options` parameter required if `account` or `chain` is undefined _isOptionsRequired = Or<[IsUndefined, IsUndefined]>, > = narrowable extends true diff --git a/src/actions/public/multicall.test-d.ts b/src/actions/public/multicall.test-d.ts index 853ba839ee..5430adcb4c 100644 --- a/src/actions/public/multicall.test-d.ts +++ b/src/actions/public/multicall.test-d.ts @@ -250,7 +250,7 @@ test('many contracts of differing types', async () => { address: '0x', abi: parseAbi([ 'function foo() view returns (int8)', - 'function foo(address) view returns (string)', + 'function foo(address account) view returns (string)', 'function foo(address, address) view returns ((address foo, address bar))', 'function bar() view returns (int8)', ]), diff --git a/src/actions/public/readContract.bench-d.ts b/src/actions/public/readContract.bench-d.ts index 9b00631339..895ff7d8de 100644 --- a/src/actions/public/readContract.bench-d.ts +++ b/src/actions/public/readContract.bench-d.ts @@ -1,20 +1,43 @@ import { attest } from '@ark/attest' +import { parseAbi } from 'abitype' +import { erc20Abi } from 'abitype/abis' import { test } from 'vitest' - -import { usdcContractConfig } from '~test/abis.js' import { mainnet } from '../../chains/index.js' import { createClient } from '../../clients/createClient.js' import { http } from '../../clients/transports/http.js' -import { readContract } from './readContract.js' +import { type ReadContractParameters, readContract } from './readContract.js' const client = createClient({ chain: mainnet, transport: http() }) test('return type', () => { const res = readContract(client, { - ...usdcContractConfig, + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + abi: erc20Abi, functionName: 'balanceOf', args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], }) - attest.instantiations([53192, 'instantiations']) + attest.instantiations([63834, 'instantiations']) attest>(res) }) + +const abi = parseAbi([ + 'function foo() view returns (int8)', + 'function foo(address account) view returns (string)', + 'function foo(address sender, address account) view returns ((address foo, address bar))', + 'function bar() view returns (int8)', +]) +test('overloads', () => { + type Result = ReadContractParameters + const res = {} as Result + attest.instantiations([14699, 'instantiations']) + attest< + | readonly [] + | readonly [account: `0x${string}`] + | readonly [sender: `0x${string}`, account: `0x${string}`] + | undefined + >(res.args) + attest(res.args).type.toString.snap(` | readonly [] + | readonly [account: \`0x\${string}\`] + | readonly [sender: \`0x\${string}\`, account: \`0x\${string}\`] + | undefined`) +}) diff --git a/src/actions/public/readContract.test-d.ts b/src/actions/public/readContract.test-d.ts index df13ff0353..a6bfe539b2 100644 --- a/src/actions/public/readContract.test-d.ts +++ b/src/actions/public/readContract.test-d.ts @@ -207,7 +207,7 @@ test('behavior', () => { name: 'bar', type: 'function', stateMutability: 'view', - inputs: [{ type: 'address', name: '' }], + inputs: [{ type: 'address', name: 'account' }], outputs: [{ type: 'address', name: '' }], }, ], @@ -231,8 +231,8 @@ test('behavior', () => { test('overloads', async () => { const abi = parseAbi([ 'function foo() view returns (int8)', - 'function foo(address) view returns (string)', - 'function foo(address, address) view returns ((address foo, address bar))', + 'function foo(address tokenId) view returns (string)', + 'function foo(address spender, address account) view returns ((address foo, address bar))', 'function bar() view returns (int8)', ]) diff --git a/src/actions/public/simulateContract.ts b/src/actions/public/simulateContract.ts index 6bc19f24a2..ca8fda32a7 100644 --- a/src/actions/public/simulateContract.ts +++ b/src/actions/public/simulateContract.ts @@ -83,6 +83,8 @@ export type SimulateContractParameters< accountOverride extends Account | Address | null | undefined = undefined, /// derivedChain extends Chain | undefined = DeriveChain, + callParameters extends + CallParameters = CallParameters, > = { account?: accountOverride | null | undefined chain?: chainOverride | undefined @@ -95,7 +97,7 @@ export type SimulateContractParameters< args > & UnionOmit< - CallParameters, + callParameters, | 'account' | 'batch' | 'code' @@ -109,9 +111,7 @@ export type SimulateContractParameters< abi, 'nonpayable' | 'payable', functionName, - CallParameters extends CallParameters - ? CallParameters['value'] - : CallParameters['value'], + callParameters['value'], args > diff --git a/src/actions/public/verifyHash.ts b/src/actions/public/verifyHash.ts index f09d94f313..9c405fee9b 100644 --- a/src/actions/public/verifyHash.ts +++ b/src/actions/public/verifyHash.ts @@ -223,13 +223,13 @@ export async function verifyErc8010( args: [ [ ...(initData - ? [ + ? ([ { allowFailure: true, target: to ?? address, callData: initData, }, - ] + ] as const) : []), { allowFailure: true, diff --git a/src/actions/wallet/writeContract.bench-d.ts b/src/actions/wallet/writeContract.bench-d.ts new file mode 100644 index 0000000000..cf40d8e6ab --- /dev/null +++ b/src/actions/wallet/writeContract.bench-d.ts @@ -0,0 +1,37 @@ +import { attest } from '@ark/attest' +import { parseAbi } from 'abitype' +import type { erc20Abi } from 'abitype/abis' +import { test } from 'vitest' +import type { WriteContractParameters } from './writeContract.js' + +test('default', () => { + type Result = WriteContractParameters + const res = {} as Result + attest.instantiations([9217, 'instantiations']) + attest(res.args) + attest(res.args).type.toString.snap( + `readonly [spender: \`0x\${string}\`, amount: bigint]`, + ) +}) + +const abi = parseAbi([ + 'function foo() returns (int8)', + 'function foo(address account) returns (string)', + 'function foo(address sender, address account) returns ((address foo, address bar))', + 'function bar() returns (int8)', +]) +test('overloads', () => { + type Result = WriteContractParameters + const res = {} as Result + attest.instantiations([14699, 'instantiations']) + attest< + | readonly [] + | readonly [account: `0x${string}`] + | readonly [sender: `0x${string}`, account: `0x${string}`] + | undefined + >(res.args) + attest(res.args).type.toString.snap(` | readonly [] + | readonly [account: \`0x\${string}\`] + | readonly [sender: \`0x\${string}\`, account: \`0x\${string}\`] + | undefined`) +}) diff --git a/src/actions/wallet/writeContract.test-d.ts b/src/actions/wallet/writeContract.test-d.ts index 33cf9063c3..fe1f712d79 100644 --- a/src/actions/wallet/writeContract.test-d.ts +++ b/src/actions/wallet/writeContract.test-d.ts @@ -1,5 +1,5 @@ import { type Address, parseAbi } from 'abitype' -import { seaportAbi } from 'abitype/abis' +import { erc20Abi, seaportAbi } from 'abitype/abis' import { assertType, expectTypeOf, test } from 'vitest' import { baycContractConfig, wagmiContractConfig } from '~test/abis.js' import { anvilMainnet } from '~test/anvil.js' @@ -13,6 +13,12 @@ import { type WriteContractParameters, writeContract } from './writeContract.js' const clientWithAccount = anvilMainnet.getClient({ account: true, }) +writeContract(clientWithAccount, { + address: '0x', + abi: erc20Abi, + functionName: 'approve', + args: ['0x', 123n], +}) test('WriteContractParameters', async () => { type Result = WriteContractParameters @@ -88,8 +94,8 @@ test('infers args', () => { transport: custom(window.ethereum!), }) const abi = parseAbi([ - 'function foo(address) payable returns (int8)', - 'function bar(address, uint256) returns (int8)', + 'function foo(address account) payable returns (int8)', + 'function bar(address account, uint256 amount) returns (int8)', ]) type Result1 = WriteContractParameters @@ -275,8 +281,8 @@ test('overloads', async () => { }) const abi = parseAbi([ 'function foo() returns (int8)', - 'function foo(address) returns (string)', - 'function foo(address, address) returns ((address foo, address bar))', + 'function foo(address account) returns (string)', + 'function foo(address sender, address account) returns ((address foo, address bar))', 'function bar() returns (int8)', ]) diff --git a/src/package.json b/src/package.json index 6f131ba198..3705231501 100644 --- a/src/package.json +++ b/src/package.json @@ -200,7 +200,7 @@ "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", - "abitype": "1.1.0", + "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.9.6", "ws": "8.18.3" diff --git a/src/types/contract.ts b/src/types/contract.ts index e95a465749..06f95dd92e 100644 --- a/src/types/contract.ts +++ b/src/types/contract.ts @@ -71,7 +71,8 @@ export type ContractFunctionArgs< functionName, mutability >['inputs'], - 'inputs' + 'inputs', + true > extends infer args ? [args] extends [never] ? readonly unknown[] @@ -85,7 +86,8 @@ export type ContractConstructorArgs< (abi extends Abi ? abi : Abi)[number], { type: 'constructor' } >['inputs'], - 'inputs' + 'inputs', + true > extends infer args ? [args] extends [never] ? readonly unknown[] @@ -97,7 +99,8 @@ export type ContractErrorArgs< errorName extends ContractErrorName = ContractErrorName, > = AbiParametersToPrimitiveTypes< ExtractAbiError['inputs'], - 'inputs' + 'inputs', + true > extends infer args ? [args] extends [never] ? readonly unknown[] @@ -179,8 +182,9 @@ type CheckArgs< /// targetArgs extends AbiParametersToPrimitiveTypes< abiFunction['inputs'], - 'inputs' - > = AbiParametersToPrimitiveTypes, + 'inputs', + true + > = AbiParametersToPrimitiveTypes, > = (readonly [] extends args ? readonly [] : args) extends targetArgs // fallback to `readonly []` if `args` has no value (e.g. `args` property not provided) ? abiFunction : never @@ -201,6 +205,11 @@ export type ContractFunctionParameters< /// allFunctionNames = ContractFunctionName, allArgs = ContractFunctionArgs, + abiFunction = ExtractAbiFunction< + abi extends Abi ? abi : Abi, + functionName, + mutability + >, // when `args` is inferred to `readonly []` ("inputs": []) or `never` (`abi` declared as `Abi` or not inferrable), allow `args` to be optional. // important that both branches return same structural type > = { @@ -208,11 +217,28 @@ export type ContractFunctionParameters< functionName: | allFunctionNames // show all options | (functionName extends allFunctionNames ? functionName : never) // infer value - args?: (abi extends Abi ? UnionWiden : never) | allArgs | undefined -} & (readonly [] extends allArgs ? {} : { args: Widen }) & +} & (readonly [] extends allArgs + ? { + args?: + | allArgs + | (abi extends Abi + ? Abi extends abi + ? never + : UnionWiden extends true ? args : allArgs> + : never) + | undefined + } + : { + args: args + }) & (deployless extends true - ? { address?: undefined; code: Hex } - : { address: Address }) + ? { + address?: undefined + code: Hex + } + : { + address: Address + }) export type ContractFunctionReturnType< abi extends Abi | readonly unknown[] = Abi, @@ -235,7 +261,9 @@ export type ContractFunctionReturnType< mutability, functionName, args - >['outputs'] + >['outputs'], + 'outputs', + true > extends infer types ? types extends readonly [] ? void @@ -265,7 +293,8 @@ export type AbiItemArgs< name extends AbiItemName = AbiItemName, > = AbiParametersToPrimitiveTypes< ExtractAbiItem['inputs'], - 'inputs' + 'inputs', + true > extends infer args ? [args] extends [never] ? readonly unknown[] @@ -290,7 +319,8 @@ export type ExtractAbiItemForArgs< : args ) extends AbiParametersToPrimitiveTypes< abiItems[k]['inputs'], - 'inputs' + 'inputs', + true > ? abiItems[k] : never diff --git a/src/utils/abi/decodeAbiParameters.ts b/src/utils/abi/decodeAbiParameters.ts index 91334a5916..5a705c1866 100644 --- a/src/utils/abi/decodeAbiParameters.ts +++ b/src/utils/abi/decodeAbiParameters.ts @@ -1,4 +1,8 @@ -import type { AbiParameter, AbiParametersToPrimitiveTypes } from 'abitype' +import type { + AbiParameter, + AbiParameterKind, + AbiParametersToPrimitiveTypes, +} from 'abitype' import { AbiDecodingDataSizeTooSmallError, AbiDecodingZeroDataError, @@ -36,7 +40,9 @@ import { getArrayComponents } from './encodeAbiParameters.js' export type DecodeAbiParametersReturnType< params extends readonly AbiParameter[] = readonly AbiParameter[], > = AbiParametersToPrimitiveTypes< - params extends readonly AbiParameter[] ? params : AbiParameter[] + params extends readonly AbiParameter[] ? params : AbiParameter[], + AbiParameterKind, + true > export type DecodeAbiParametersErrorType = @@ -76,7 +82,7 @@ export function decodeAbiParameters< consumed += consumed_ values.push(data) } - return values as DecodeAbiParametersReturnType + return values as never } type DecodeParameterErrorType = diff --git a/src/utils/abi/decodeEventLog.ts b/src/utils/abi/decodeEventLog.ts index 7fbfe750d2..f22bc3aa96 100644 --- a/src/utils/abi/decodeEventLog.ts +++ b/src/utils/abi/decodeEventLog.ts @@ -152,7 +152,10 @@ export function decodeEventLog< if (nonIndexedInputs.length > 0) { if (data && data !== '0x') { try { - const decodedData = decodeAbiParameters(nonIndexedInputs, data) + const decodedData = decodeAbiParameters( + nonIndexedInputs, + data, + ) as unknown[] if (decodedData) { if (isUnnamed) for (let i = 0; i < inputs.length; i++) diff --git a/src/utils/abi/encodeAbiParameters.ts b/src/utils/abi/encodeAbiParameters.ts index 4ce7d78107..8fb5bd55e6 100644 --- a/src/utils/abi/encodeAbiParameters.ts +++ b/src/utils/abi/encodeAbiParameters.ts @@ -1,5 +1,6 @@ import type { AbiParameter, + AbiParameterKind, AbiParametersToPrimitiveTypes, AbiParameterToPrimitiveType, } from 'abitype' @@ -87,7 +88,7 @@ export function encodeAbiParameters< >( params: params, values: params extends readonly AbiParameter[] - ? AbiParametersToPrimitiveTypes + ? AbiParametersToPrimitiveTypes : never, ): EncodeAbiParametersReturnType { if (params.length !== values.length)