Skip to content

Commit 1f65640

Browse files
authored
feat: initial callContract implementation (#31)
* feat: callContract * chore: changeset * cleanup * cleanup * cleanup * format
1 parent f99b996 commit 1f65640

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2803
-177
lines changed

.changeset/moody-gorillas-wait.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"viem": patch
3+
---
4+
5+
Added initial `callContract` implementation

src/actions/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ test('exports actions', () => {
77
{
88
"addChain": [Function],
99
"call": [Function],
10+
"callContract": [Function],
1011
"createBlockFilter": [Function],
1112
"createPendingTransactionFilter": [Function],
1213
"dropTransaction": [Function],

src/actions/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export {
22
call,
3+
callContract,
34
createBlockFilter,
45
createPendingTransactionFilter,
56
estimateGas,
@@ -25,6 +26,8 @@ export {
2526
} from './public'
2627
export type {
2728
CallArgs,
29+
CallContractArgs,
30+
CallContractResponse,
2831
CallResponse,
2932
CreateBlockFilterResponse,
3033
CreatePendingTransactionFilterResponse,

src/actions/public/call.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Chain, Formatter } from '../../chains'
22
import type { PublicClient } from '../../clients'
33
import { InvalidGasArgumentsError } from '../../errors'
44
import type {
5+
Address,
56
BlockTag,
67
Hex,
78
MergeIntersectionProperties,
@@ -21,6 +22,7 @@ export type CallArgs<TChain extends Chain = Chain> = FormattedCall<
2122
TransactionRequestFormatter<TChain>
2223
> & {
2324
chain?: TChain
25+
from?: Address
2426
} & (
2527
| {
2628
/** The balance of the account at a block number. */
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Contract } from 'ethers'
2+
import { bench, describe } from 'vitest'
3+
4+
import {
5+
ethersProvider,
6+
publicClient,
7+
wagmiContractConfig,
8+
} from '../../../test'
9+
10+
import { callContract } from './callContract'
11+
12+
describe('Call Contract', () => {
13+
bench('viem: `callContract`', async () => {
14+
await callContract(publicClient, {
15+
...wagmiContractConfig,
16+
functionName: 'totalSupply',
17+
})
18+
})
19+
20+
bench('ethers: `callStatic`', async () => {
21+
const wagmi = new Contract(
22+
wagmiContractConfig.address,
23+
wagmiContractConfig.abi,
24+
ethersProvider,
25+
)
26+
await wagmi.callStatic.mint(1)
27+
})
28+
})

src/actions/public/callContract.test.ts

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.

src/actions/public/callContract.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { Abi } from 'abitype'
2+
3+
import type { Chain, Formatter } from '../../chains'
4+
import type { PublicClient } from '../../clients'
5+
import type {
6+
Address,
7+
ExtractArgsFromAbi,
8+
ExtractResultFromAbi,
9+
ExtractFunctionNameFromAbi,
10+
GetValue,
11+
} from '../../types'
12+
import {
13+
EncodeFunctionDataArgs,
14+
decodeFunctionResult,
15+
encodeFunctionData,
16+
getContractError,
17+
} from '../../utils'
18+
import { call, CallArgs, FormattedCall } from './call'
19+
20+
export type FormattedCallContract<
21+
TFormatter extends Formatter | undefined = Formatter,
22+
> = FormattedCall<TFormatter>
23+
24+
export type CallContractArgs<
25+
TChain extends Chain = Chain,
26+
TAbi extends Abi | readonly unknown[] = Abi,
27+
TFunctionName extends string = any,
28+
> = Omit<CallArgs<TChain>, 'from' | 'to' | 'data' | 'value'> & {
29+
address: Address
30+
abi: TAbi
31+
from?: Address
32+
functionName: ExtractFunctionNameFromAbi<TAbi, TFunctionName>
33+
value?: GetValue<TAbi, TFunctionName, CallArgs<TChain>['value']>
34+
} & ExtractArgsFromAbi<TAbi, TFunctionName>
35+
36+
export type CallContractResponse<
37+
TAbi extends Abi | readonly unknown[] = Abi,
38+
TFunctionName extends string = string,
39+
> = ExtractResultFromAbi<TAbi, TFunctionName>
40+
41+
export async function callContract<
42+
TChain extends Chain,
43+
TAbi extends Abi = Abi,
44+
TFunctionName extends string = any,
45+
>(
46+
client: PublicClient,
47+
{
48+
abi,
49+
address,
50+
args,
51+
functionName,
52+
...callRequest
53+
}: CallContractArgs<TChain, TAbi, TFunctionName>,
54+
): Promise<CallContractResponse<TAbi, TFunctionName>> {
55+
const calldata = encodeFunctionData({
56+
abi,
57+
args,
58+
functionName,
59+
} as unknown as EncodeFunctionDataArgs<TAbi, TFunctionName>)
60+
try {
61+
const { data } = await call(client, {
62+
data: calldata,
63+
to: address,
64+
...callRequest,
65+
} as unknown as CallArgs<TChain>)
66+
return decodeFunctionResult({
67+
abi,
68+
functionName,
69+
data: data || '0x',
70+
}) as CallContractResponse<TAbi, TFunctionName>
71+
} catch (err) {
72+
throw getContractError(err, {
73+
abi,
74+
address,
75+
args,
76+
functionName,
77+
sender: callRequest.from,
78+
})
79+
}
80+
}

src/actions/public/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ test('exports actions', () => {
66
expect(actions).toMatchInlineSnapshot(`
77
{
88
"call": [Function],
9+
"callContract": [Function],
910
"createBlockFilter": [Function],
1011
"createPendingTransactionFilter": [Function],
1112
"estimateGas": [Function],

src/actions/public/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
export { call } from './call'
22
export type { CallArgs, CallResponse, FormattedCall } from './call'
33

4+
export { callContract } from './callContract'
5+
export type {
6+
CallContractArgs,
7+
CallContractResponse,
8+
FormattedCallContract,
9+
} from './callContract'
10+
411
export { createPendingTransactionFilter } from './createPendingTransactionFilter'
512
export type { CreatePendingTransactionFilterResponse } from './createPendingTransactionFilter'
613

src/errors/abi.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ export class AbiDecodingDataSizeInvalidError extends BaseError {
4343
}
4444
}
4545

46+
export class AbiDecodingZeroDataError extends BaseError {
47+
name = 'AbiDecodingZeroDataError'
48+
constructor() {
49+
super('Cannot decode zero data ("0x") with ABI parameters.')
50+
}
51+
}
52+
4653
export class AbiEncodingArrayLengthMismatchError extends BaseError {
4754
name = 'AbiEncodingArrayLengthMismatchError'
4855
constructor({
@@ -220,15 +227,12 @@ export class InvalidArrayError extends BaseError {
220227

221228
export class InvalidDefinitionTypeError extends BaseError {
222229
name = 'InvalidDefinitionTypeError'
223-
constructor(type: string, { docsPath }: { docsPath: string }) {
230+
constructor(type: string) {
224231
super(
225232
[
226233
`"${type}" is not a valid definition type.`,
227234
'Valid types: "function", "event", "error"',
228235
].join('\n'),
229-
{
230-
docsPath,
231-
},
232236
)
233237
}
234238
}

0 commit comments

Comments
 (0)