diff --git a/.changeset/new-hotels-sell.md b/.changeset/new-hotels-sell.md new file mode 100644 index 0000000000..86c50fcc72 --- /dev/null +++ b/.changeset/new-hotels-sell.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Fixed #2560: infer the correct payable `value` type on function overloads by matching function against `args`. diff --git a/src/actions/public/simulateContract.test-d.ts b/src/actions/public/simulateContract.test-d.ts index 3e651b7e69..0844f2eee0 100644 --- a/src/actions/public/simulateContract.test-d.ts +++ b/src/actions/public/simulateContract.test-d.ts @@ -266,3 +266,37 @@ test('chain formatters', async () => { `0x${string}` | undefined >() }) + +test('https://github.com/wevm/viem/issues/2531', async () => { + const abi = parseAbi([ + 'function safeTransferFrom(address, address, uint256)', + 'function safeTransferFrom(address, address, uint256, bytes) payable', + ]) + + const res1 = await simulateContract(client, { + address: '0x', + abi, + functionName: 'safeTransferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error + value: 123n, + }) + assertType(res1.result) + expectTypeOf(res1.request.abi).toEqualTypeOf( + parseAbi(['function safeTransferFrom(address, address, uint256)']), + ) + + const res2 = await simulateContract(client, { + address: '0x', + abi, + functionName: 'safeTransferFrom', + args: ['0x', '0x', 123n, '0x'], + value: 123n, + }) + assertType(res2.result) + expectTypeOf(res2.request.abi).toEqualTypeOf( + parseAbi([ + 'function safeTransferFrom(address, address, uint256, bytes) payable', + ]), + ) +}) diff --git a/src/actions/public/simulateContract.ts b/src/actions/public/simulateContract.ts index a6e3e0e9dd..46c755d2e1 100644 --- a/src/actions/public/simulateContract.ts +++ b/src/actions/public/simulateContract.ts @@ -1,4 +1,4 @@ -import type { Abi, Address } from 'abitype' +import type { Abi, AbiFunction, AbiStateMutability, Address } from 'abitype' import { type ParseAccountErrorType, @@ -16,10 +16,15 @@ import type { ContractFunctionParameters, ContractFunctionReturnType, ExtractAbiFunctionForArgs, - GetValue, } from '../../types/contract.js' import type { Hex } from '../../types/misc.js' -import type { Prettify, UnionEvaluate, UnionOmit } from '../../types/utils.js' +import type { + IsNarrowable, + NoInfer, + Prettify, + UnionEvaluate, + UnionOmit, +} from '../../types/utils.js' import { type DecodeFunctionResultErrorType, decodeFunctionResult, @@ -34,9 +39,35 @@ import { } from '../../utils/errors/getContractError.js' import type { WriteContractParameters } from '../wallet/writeContract.js' +import type { TransactionRequest } from '../../types/transaction.js' import { getAction } from '../../utils/getAction.js' import { type CallErrorType, type CallParameters, call } from './call.js' +export type GetMutabilityAwareValue< + abi extends Abi | readonly unknown[], + mutability extends AbiStateMutability = AbiStateMutability, + functionName extends ContractFunctionName< + abi, + mutability + > = ContractFunctionName, + valueType = TransactionRequest['value'], + args extends ContractFunctionArgs< + abi, + mutability, + functionName + > = ContractFunctionArgs, + abiFunction extends AbiFunction = abi extends Abi + ? ExtractAbiFunctionForArgs + : AbiFunction, + _Narrowable extends boolean = IsNarrowable, +> = _Narrowable extends true + ? abiFunction['stateMutability'] extends 'payable' + ? { value?: NoInfer | undefined } + : abiFunction['payable'] extends true + ? { value?: NoInfer | undefined } + : { value?: undefined } + : { value?: NoInfer | undefined } + export type SimulateContractParameters< abi extends Abi | readonly unknown[] = Abi, functionName extends ContractFunctionName< @@ -75,12 +106,14 @@ export type SimulateContractParameters< | 'factoryData' | 'value' > & - GetValue< + GetMutabilityAwareValue< abi, + 'nonpayable' | 'payable', functionName, CallParameters extends CallParameters ? CallParameters['value'] - : CallParameters['value'] + : CallParameters['value'], + args > export type SimulateContractReturnType< diff --git a/src/actions/wallet/writeContract.ts b/src/actions/wallet/writeContract.ts index 5231f770cc..44765980cc 100644 --- a/src/actions/wallet/writeContract.ts +++ b/src/actions/wallet/writeContract.ts @@ -14,7 +14,6 @@ import type { ContractFunctionArgs, ContractFunctionName, ContractFunctionParameters, - GetValue, } from '../../types/contract.js' import type { Hex } from '../../types/misc.js' import type { Prettify, UnionEvaluate, UnionOmit } from '../../types/utils.js' @@ -25,6 +24,7 @@ import { } from '../../utils/abi/encodeFunctionData.js' import type { FormattedTransactionRequest } from '../../utils/formatters/transactionRequest.js' import { getAction } from '../../utils/getAction.js' +import type { GetMutabilityAwareValue } from '../public/simulateContract.js' import { type SendTransactionErrorType, type SendTransactionReturnType, @@ -59,10 +59,12 @@ export type WriteContractParameters< GetChainParameter & Prettify< GetAccountParameter & - GetValue< + GetMutabilityAwareValue< abi, + 'nonpayable' | 'payable', functionName, - FormattedTransactionRequest['value'] + FormattedTransactionRequest['value'], + args > & { /** Data to append to the end of the calldata. Useful for adding a ["domain" tag](https://opensea.notion.site/opensea/Seaport-Order-Attributions-ec2d69bf455041a5baa490941aad307f). */ dataSuffix?: Hex | undefined