Skip to content

Walkthrough

Let's use ABIType to create a type-safe function that calls "read" contract methods. We'll infer function names, argument types, and return types from a user-provided ABI, and make sure it works for function overloads.

You can spin up a TypeScript Playground to code along.

1. Scaffolding readContract

First, we start off by declaring1 the function readContract with some basic types:

import { 
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
} from 'abitype'
declare function
function readContract(config: { abi: Abi; functionName: string; args: readonly unknown[]; }): unknown
readContract
(
config: { abi: Abi; functionName: string; args: readonly unknown[]; }
config
: {
abi: Abi
abi
:
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
functionName: string
functionName
: string
args: readonly unknown[]
args
: readonly unknown[]
}): unknown

The function accepts a config object which includes the ABI, function name, and arguments. The return type is unknown since we don't know what the function will return quite yet.2 Next, let's call the function using the following values:

import { 
const abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
} from './abi'
const
const res: unknown
res
=
function readContract(config: { abi: Abi; functionName: string; args: readonly unknown[]; }): unknown
readContract
({
abi: Abi
abi
,
functionName: string
functionName
: 'balanceOf',
args: readonly unknown[]
args
: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
})

2. Adding inference to functionName

functionName and args types aren't inferred from the ABI yet so we can pass any value we want. Let's fix that! Often, you'll want to pull types into generics when trying to infer parameters. We'll do the same here, starting with functionName:

import { 
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
,
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
} from 'abitype'
import {
const abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
} from './abi'
declare function
function readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }): unknown
readContract
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }): unknown
abi
extends
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
,
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }): unknown
functionName
extends
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }): unknown
abi
, 'pure' | 'view'>,
>(
config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }
config
: {
abi: abi extends Abi
abi
:
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }): unknown
abi
functionName: ExtractAbiFunctionNames<abi, "view" | "pure"> | functionName
functionName
:
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }): unknown
functionName
|
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: readonly unknown[]; }): unknown
abi
, 'pure' | 'view'>
args: readonly unknown[]
args
: readonly unknown[]
}): unknown const
const res: unknown
res
=
function readContract<readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }], "balanceOf">(config: { ...; }): unknown
readContract
({
abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
,
functionName: "balanceOf" | "tokenURI"
functionName
: 'balanceOf',
args: readonly unknown[]
args
: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
})

First, we create two generics abi and functionName, and constrain their types. abi is set to the config.abi property and the Abi type. For functionName, we import ExtractAbiFunctionNames and use it to parse out all the read function names (state mutability 'pure' | 'view'3) from the ABI. Finally, config.functionName is set to the user-defined functionName and another instance of ExtractAbiFunctionNames. This allows us to add the full union (not just the current value) to functionName's scope.4

If you are following along in a TypeScript Playground or editor, you can try various values for functionName. functionName will autocomplete and only accept 'balanceOf' | 'tokenURI'. You can also try renaming the function names in abi and types will update as well.

const 
const res: unknown
res
=
function readContract<readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }], "balanceOf" | "tokenURI">(config: { ...; }): unknown
readContract
({
abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
,
functionName: "balanceOf" | "tokenURI"
functionName
: '
  • 'balanceOf'
  • 'tokenURI'
})

3. Adding inference to args

With functionName complete, we can move on to args. This time we don't need to add a generic slot because args depends completely on abi and functionName and doesn't need to infer user input.

import {
  
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
,
type AbiParametersToPrimitiveTypes<abiParameters extends readonly AbiParameter[], abiParameterKind extends AbiParameterKind = AbiParameterKind> = { [key in keyof { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }]: { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }[key]; }

Converts array of AbiParameter to corresponding TypeScript primitive types.

AbiParametersToPrimitiveTypes
,
type ExtractAbiFunction<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi>, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }> extends { name: functionName; } ? { name: functionName; } & Extract<...> : never

Extracts AbiFunction with name from Abi.

ExtractAbiFunction
,
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
,
} from 'abitype' import {
const abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
} from './abi'
declare function
function readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
readContract
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
abi
extends
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
,
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
functionName
extends
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
abi
, 'pure' | 'view'>,
>(
config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }
config
: {
abi: abi extends Abi
abi
:
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
abi
functionName: ExtractAbiFunctionNames<abi, "view" | "pure"> | functionName
functionName
:
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
functionName
|
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
abi
, 'pure' | 'view'>
args: { [key in keyof { [key in keyof ExtractAbiFunction<abi, functionName>["inputs"]]: AbiParameterToPrimitiveType<ExtractAbiFunction<abi, functionName>["inputs"][key], "inputs">; }]: { [key in keyof ExtractAbiFunction<abi, functionName>["inputs"]]: AbiParameterToPrimitiveType<ExtractAbiFunction<abi, functionName>["inputs"][key], "inputs">; }[key]; }
args
:
type AbiParametersToPrimitiveTypes<abiParameters extends readonly AbiParameter[], abiParameterKind extends AbiParameterKind = AbiParameterKind> = { [key in keyof { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }]: { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }[key]; }

Converts array of AbiParameter to corresponding TypeScript primitive types.

AbiParametersToPrimitiveTypes
<
type ExtractAbiFunction<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi>, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }> extends { name: functionName; } ? { name: functionName; } & Extract<...> : never

Extracts AbiFunction with name from Abi.

ExtractAbiFunction
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
abi
,
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<ExtractAbiFunction<abi, functionName>["inputs"], "inputs">; }): unknown
functionName
>['inputs'],
'inputs' > }): unknown const
const res: unknown
res
=
function readContract<readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }], "balanceOf">(config: { ...; }): unknown
readContract
({
abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
,
functionName: "balanceOf" | "tokenURI"
functionName
: 'balanceOf',
args: readonly [`0x${string}`] | readonly [`0x${string}`, bigint]
args
: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
})

Since args's type can be completely defined inline, we import ExtractAbiFunction and AbiParametersToPrimitiveTypes and wire them up. First, we use ExtractAbiFunction to get the function from the ABI that matches functionName. Then, we use AbiParametersToPrimitiveTypes to convert the function's inputs to their TypeScript primitive types.

For abi, you'll notice there are two 'balanceOf' functions. This means 'balanceOf' is overloaded on the contract. The cool thing about TypeScript is that we can still infer the correct types for overloaded functions (e.g. union like readonly [`0x${string}`] | readonly [`0x${string}`, bigint])! This uses a TypeScript feature called distributivity and is worth learning more about if you're interested.

4. Adding the return type

Finally, we can add the return type:

import {
  
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
,
type AbiFunction = { type: "function"; constant?: boolean | undefined; gas?: number | undefined; inputs: readonly AbiParameter[]; name: string; outputs: readonly AbiParameter[]; payable?: boolean | undefined; stateMutability: AbiStateMutability; }

ABI "function" type

AbiFunction
,
type AbiParametersToPrimitiveTypes<abiParameters extends readonly AbiParameter[], abiParameterKind extends AbiParameterKind = AbiParameterKind> = { [key in keyof { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }]: { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }[key]; }

Converts array of AbiParameter to corresponding TypeScript primitive types.

AbiParametersToPrimitiveTypes
,
type ExtractAbiFunction<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi>, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }> extends { name: functionName; } ? { name: functionName; } & Extract<...> : never

Extracts AbiFunction with name from Abi.

ExtractAbiFunction
,
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
,
} from 'abitype' import {
const abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
} from './abi'
declare function
function readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
readContract
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abi
extends
type Abi = readonly (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[]
Abi
,
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
functionName
extends
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abi
, 'pure' | 'view'>,
function (type parameter) abiFunction in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abiFunction
extends
type AbiFunction = { type: "function"; constant?: boolean | undefined; gas?: number | undefined; inputs: readonly AbiParameter[]; name: string; outputs: readonly AbiParameter[]; payable?: boolean | undefined; stateMutability: AbiStateMutability; }

ABI "function" type

AbiFunction
=
type ExtractAbiFunction<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi>, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }> extends { name: functionName; } ? { name: functionName; } & Extract<...> : never

Extracts AbiFunction with name from Abi.

ExtractAbiFunction
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abi
,
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
functionName
>, >(
config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }
config
: {
abi: abi extends Abi
abi
:
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abi
functionName: ExtractAbiFunctionNames<abi, "view" | "pure"> | functionName
functionName
:
function (type parameter) functionName in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
functionName
|
type ExtractAbiFunctionNames<abi extends Abi, abiStateMutability extends AbiStateMutability = AbiStateMutability> = Extract<abi[number], { type: "function"; stateMutability: abiStateMutability; }>["name"]

Extracts all AbiFunction names from Abi.

ExtractAbiFunctionNames
<
function (type parameter) abi in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abi
, 'pure' | 'view'>
args: { [key in keyof { [key in keyof abiFunction["inputs"]]: AbiParameterToPrimitiveType<abiFunction["inputs"][key], "inputs">; }]: { [key in keyof abiFunction["inputs"]]: AbiParameterToPrimitiveType<abiFunction["inputs"][key], "inputs">; }[key]; }
args
:
type AbiParametersToPrimitiveTypes<abiParameters extends readonly AbiParameter[], abiParameterKind extends AbiParameterKind = AbiParameterKind> = { [key in keyof { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }]: { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }[key]; }

Converts array of AbiParameter to corresponding TypeScript primitive types.

AbiParametersToPrimitiveTypes
<
function (type parameter) abiFunction in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abiFunction
['inputs'], 'inputs'>
}):
type AbiParametersToPrimitiveTypes<abiParameters extends readonly AbiParameter[], abiParameterKind extends AbiParameterKind = AbiParameterKind> = { [key in keyof { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }]: { [key in keyof abiParameters]: AbiParameterToPrimitiveType<abiParameters[key], abiParameterKind>; }[key]; }

Converts array of AbiParameter to corresponding TypeScript primitive types.

AbiParametersToPrimitiveTypes
<
function (type parameter) abiFunction in readContract<abi extends Abi, functionName extends ExtractAbiFunctionNames<abi, "pure" | "view">, abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>>(config: { abi: abi; functionName: functionName | ExtractAbiFunctionNames<abi, "pure" | "view">; args: AbiParametersToPrimitiveTypes<abiFunction["inputs"], "inputs">; }): AbiParametersToPrimitiveTypes<abiFunction["outputs"], "outputs">
abiFunction
['outputs'], 'outputs'>
const
const res: readonly [bigint]
res
=
function readContract<readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }], "balanceOf", { readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; } | { ...; }>(config: { ...; }): readonly [...]
readContract
({
abi: readonly [{ readonly name: "balanceOf"; readonly type: "function"; readonly stateMutability: "view"; readonly inputs: readonly [{ readonly name: "owner"; readonly type: "address"; }]; readonly outputs: readonly [{ readonly name: "balance"; readonly type: "uint256"; }]; }, { ...; }, { ...; }, { ...; }]
abi
,
functionName: "balanceOf" | "tokenURI"
functionName
: 'balanceOf',
args: readonly [`0x${string}`] | readonly [`0x${string}`, bigint]
args
: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
})

We can refactor our ExtractAbiFunction call into a generic slot abiFunction (of type AbiFunction) and set the default to the result of ExtractAbiFunction. This allows us to use abiFunction in for args and the return type. Lastly, we wire up another AbiParametersToPrimitiveTypes call for the return type—this time using outputs.

5. Wrapping up

readContract's types are starting to look solid! It infers the correct types for functionName and args based on the ABI (and works with overloaded functions). It also infers the correct return type based on the ABI and functionName. The only thing left is to implement the function itself.

There are a few other ways to improve the typing that are out of scope for this walkthrough, but are worth noting:

  • abi requires a const assertion to ensure TypeScript takes the most specific type, but you can set things up so this is unnecessary for inline abi definitions.
  • args can be an empty array if the function doesn't take any arguments, but you could conditionally add args to config if it's not empty.
  • readContract's return type is an array, but you could unwrap it if the function only has one output or transform it to another type depending on your implementation.

The preceding points are all implemented in throughout the examples in this directory so check them out if you're interested.

Footnotes

  1. We use the declare keyword so we don't need to worry about the implementation. In this case, the implementation would look something like encoding arguments and sending with the eth_call RPC method.

  2. If this was a real function that read via RPC, we'd likely want to make it async and return a Promise, but we'll leave that out for simplicity.

  3. We could add or change this to 'nonpayable' | 'payable' to allow write functions.

  4. Try removing | ExtractAbiFunctionNames<abi, 'pure' | 'view'> from functionName, hover over functionName in your editor, and see what happens. You'll notice that the only functionName that shows up in the current value.