Skip to content

Commit 7afdee8

Browse files
committed
feat: readContract
1 parent 81327ef commit 7afdee8

File tree

12 files changed

+237
-12
lines changed

12 files changed

+237
-12
lines changed

.changeset/empty-poets-destroy.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 `readContract`

site/.vitepress/sidebar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ export const sidebar: DefaultTheme.Sidebar = {
413413
link: '/docs/contract/multicall',
414414
},
415415
{
416-
text: 'readContract 🚧',
416+
text: 'readContract',
417417
link: '/docs/contract/readContract',
418418
},
419419
{

src/actions/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ test('exports actions', () => {
3737
"increaseTime": [Function],
3838
"inspectTxpool": [Function],
3939
"mine": [Function],
40+
"readContract": [Function],
4041
"removeBlockTimestampInterval": [Function],
4142
"requestAccounts": [Function],
4243
"requestPermissions": [Function],

src/actions/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export {
2020
getTransactionConfirmations,
2121
getTransactionCount,
2222
getTransactionReceipt,
23+
readContract,
2324
simulateContract,
2425
uninstallFilter,
2526
waitForTransactionReceipt,
@@ -69,6 +70,8 @@ export type {
6970
OnBlockResponse,
7071
OnTransactions,
7172
OnTransactionsResponse,
73+
ReadContractArgs,
74+
ReadContractResponse,
7275
ReplacementReason,
7376
ReplacementResponse,
7477
SimulateContractArgs,

src/actions/public/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ test('exports actions', () => {
2626
"getTransactionConfirmations": [Function],
2727
"getTransactionCount": [Function],
2828
"getTransactionReceipt": [Function],
29+
"readContract": [Function],
2930
"simulateContract": [Function],
3031
"uninstallFilter": [Function],
3132
"waitForTransactionReceipt": [Function],

src/actions/public/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export { simulateContract } from './simulateContract'
55
export type {
66
SimulateContractArgs,
77
SimulateContractResponse,
8-
FormattedSimulateContract,
98
} from './simulateContract'
109

1110
export { createPendingTransactionFilter } from './createPendingTransactionFilter'
@@ -91,6 +90,12 @@ export type {
9190
GetTransactionReceiptResponse,
9291
} from './getTransactionReceipt'
9392

93+
export { readContract } from './readContract'
94+
export type {
95+
ReadContractArgs,
96+
ReadContractResponse,
97+
} from './readContract'
98+
9499
export { uninstallFilter } from './uninstallFilter'
95100
export type {
96101
UninstallFilterArgs,
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* TODO: Heaps more test cases :D
3+
* - Complex calldata types
4+
* - Complex return types (tuple/structs)
5+
* - Calls against blocks
6+
*/
7+
8+
import { describe, expect, test } from 'vitest'
9+
import {
10+
accounts,
11+
publicClient,
12+
testClient,
13+
wagmiContractConfig,
14+
walletClient,
15+
} from '../../_test'
16+
import { baycContractConfig } from '../../_test/abis'
17+
import { encodeFunctionData } from '../../utils'
18+
import { mine } from '../test'
19+
import { sendTransaction } from '../wallet'
20+
21+
import { deployContract } from './deployContract'
22+
import { getTransactionReceipt } from './getTransactionReceipt'
23+
import { readContract } from './readContract'
24+
25+
describe('wagmi', () => {
26+
test('default', async () => {
27+
expect(
28+
await readContract(publicClient, {
29+
...wagmiContractConfig,
30+
functionName: 'balanceOf',
31+
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'],
32+
}),
33+
).toEqual(3n)
34+
expect(
35+
await readContract(publicClient, {
36+
...wagmiContractConfig,
37+
functionName: 'getApproved',
38+
args: [420n],
39+
}),
40+
).toEqual('0x0000000000000000000000000000000000000000')
41+
expect(
42+
await readContract(publicClient, {
43+
...wagmiContractConfig,
44+
functionName: 'isApprovedForAll',
45+
args: [
46+
'0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
47+
'0x0000000000000000000000000000000000000000',
48+
],
49+
}),
50+
).toEqual(false)
51+
expect(
52+
await readContract(publicClient, {
53+
...wagmiContractConfig,
54+
functionName: 'name',
55+
}),
56+
).toEqual('wagmi')
57+
expect(
58+
await readContract(publicClient, {
59+
...wagmiContractConfig,
60+
functionName: 'ownerOf',
61+
args: [420n],
62+
}),
63+
).toEqual('0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC')
64+
expect(
65+
await readContract(publicClient, {
66+
...wagmiContractConfig,
67+
functionName: 'supportsInterface',
68+
args: ['0x1a452251'],
69+
}),
70+
).toEqual(false)
71+
expect(
72+
await readContract(publicClient, {
73+
...wagmiContractConfig,
74+
functionName: 'symbol',
75+
}),
76+
).toEqual('WAGMI')
77+
expect(
78+
await readContract(publicClient, {
79+
...wagmiContractConfig,
80+
functionName: 'tokenURI',
81+
args: [420n],
82+
}),
83+
).toMatchInlineSnapshot(
84+
'"data:application/json;base64,eyJuYW1lIjogIndhZ21pICM0MjAiLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0l4TURJMElpQm9aV2xuYUhROUlqRXdNalFpSUdacGJHdzlJbTV2Ym1VaVBqeHdZWFJvSUdacGJHdzlJbWh6YkNneE1UY3NJREV3TUNVc0lERXdKU2tpSUdROUlrMHdJREJvTVRBeU5IWXhNREkwU0RCNklpQXZQanhuSUdacGJHdzlJbWh6YkNneU9EZ3NJREV3TUNVc0lEa3dKU2tpUGp4d1lYUm9JR1E5SWswNU1ETWdORE0zTGpWak1DQTVMakV4TXkwM0xqTTRPQ0F4Tmk0MUxURTJMalVnTVRZdU5YTXRNVFl1TlMwM0xqTTROeTB4Tmk0MUxURTJMalVnTnk0ek9EZ3RNVFl1TlNBeE5pNDFMVEUyTGpVZ01UWXVOU0EzTGpNNE55QXhOaTQxSURFMkxqVjZUVFk1T0M0MU1qa2dOVFkyWXpZdU9USXhJREFnTVRJdU5UTXROUzQxT1RZZ01USXVOVE10TVRJdU5YWXROVEJqTUMwMkxqa3dOQ0ExTGpZd09TMHhNaTQxSURFeUxqVXlPUzB4TWk0MWFESTFMakExT1dNMkxqa3lJREFnTVRJdU5USTVJRFV1TlRrMklERXlMalV5T1NBeE1pNDFkalV3WXpBZ05pNDVNRFFnTlM0Mk1Ea2dNVEl1TlNBeE1pNDFNeUF4TWk0MWN6RXlMalV5T1MwMUxqVTVOaUF4TWk0MU1qa3RNVEl1TlhZdE5UQmpNQzAyTGprd05DQTFMall3T1MweE1pNDFJREV5TGpVekxURXlMalZvTWpVdU1EVTVZell1T1RJZ01DQXhNaTQxTWprZ05TNDFPVFlnTVRJdU5USTVJREV5TGpWMk5UQmpNQ0EyTGprd05DQTFMall3T1NBeE1pNDFJREV5TGpVeU9TQXhNaTQxYURNM0xqVTRPV00yTGpreUlEQWdNVEl1TlRJNUxUVXVOVGsySURFeUxqVXlPUzB4TWk0MWRpMDNOV013TFRZdU9UQTBMVFV1TmpBNUxURXlMalV0TVRJdU5USTVMVEV5TGpWekxURXlMalV6SURVdU5UazJMVEV5TGpVeklERXlMalYyTlRZdU1qVmhOaTR5TmpRZ05pNHlOalFnTUNBeElERXRNVEl1TlRJNUlEQldORGM0TGpWak1DMDJMamt3TkMwMUxqWXdPUzB4TWk0MUxURXlMalV6TFRFeUxqVklOams0TGpVeU9XTXROaTQ1TWlBd0xURXlMalV5T1NBMUxqVTVOaTB4TWk0MU1qa2dNVEl1TlhZM05XTXdJRFl1T1RBMElEVXVOakE1SURFeUxqVWdNVEl1TlRJNUlERXlMalY2SWlBdlBqeHdZWFJvSUdROUlrMHhOVGN1TmpVMUlEVTBNV010Tmk0NU16SWdNQzB4TWk0MU5USXROUzQxT1RZdE1USXVOVFV5TFRFeUxqVjJMVFV3WXpBdE5pNDVNRFF0TlM0Mk1Ua3RNVEl1TlMweE1pNDFOVEV0TVRJdU5WTXhNakFnTkRjeExqVTVOaUF4TWpBZ05EYzRMalYyTnpWak1DQTJMamt3TkNBMUxqWXlJREV5TGpVZ01USXVOVFV5SURFeUxqVm9NVFV3TGpZeVl6WXVPVE16SURBZ01USXVOVFV5TFRVdU5UazJJREV5TGpVMU1pMHhNaTQxZGkwMU1HTXdMVFl1T1RBMElEVXVOakU1TFRFeUxqVWdNVEl1TlRVeUxURXlMalZvTVRRMExqTTBOV016TGpRMk5TQXdJRFl1TWpjMklESXVOems0SURZdU1qYzJJRFl1TWpWekxUSXVPREV4SURZdU1qVXROaTR5TnpZZ05pNHlOVWd6TWpBdU9ESTRZeTAyTGprek15QXdMVEV5TGpVMU1pQTFMalU1TmkweE1pNDFOVElnTVRJdU5YWXpOeTQxWXpBZ05pNDVNRFFnTlM0Mk1Ua2dNVEl1TlNBeE1pNDFOVElnTVRJdU5XZ3hOVEF1TmpKak5pNDVNek1nTUNBeE1pNDFOVEl0TlM0MU9UWWdNVEl1TlRVeUxURXlMalYyTFRjMVl6QXROaTQ1TURRdE5TNDJNVGt0TVRJdU5TMHhNaTQxTlRJdE1USXVOVWd5T0RNdU1UY3lZeTAyTGprek1pQXdMVEV5TGpVMU1TQTFMalU1TmkweE1pNDFOVEVnTVRJdU5YWTFNR013SURZdU9UQTBMVFV1TmpFNUlERXlMalV0TVRJdU5UVXlJREV5TGpWb0xUSTFMakV3TTJNdE5pNDVNek1nTUMweE1pNDFOVEl0TlM0MU9UWXRNVEl1TlRVeUxURXlMalYyTFRVd1l6QXROaTQ1TURRdE5TNDJNaTB4TWk0MUxURXlMalUxTWkweE1pNDFjeTB4TWk0MU5USWdOUzQxT1RZdE1USXVOVFV5SURFeUxqVjJOVEJqTUNBMkxqa3dOQzAxTGpZeE9TQXhNaTQxTFRFeUxqVTFNU0F4TWk0MWFDMHlOUzR4TURSNmJUTXdNUzR5TkRJdE5pNHlOV013SURNdU5EVXlMVEl1T0RFeElEWXVNalV0Tmk0eU56WWdOaTR5TlVnek16a3VOalUxWXkwekxqUTJOU0F3TFRZdU1qYzJMVEl1TnprNExUWXVNamMyTFRZdU1qVnpNaTQ0TVRFdE5pNHlOU0EyTGpJM05pMDJMakkxYURFeE1pNDVOalpqTXk0ME5qVWdNQ0EyTGpJM05pQXlMamM1T0NBMkxqSTNOaUEyTGpJMWVrMDBPVGNnTlRVekxqZ3hPR013SURZdU9USTVJRFV1TmpJNElERXlMalUwTmlBeE1pNDFOekVnTVRJdU5UUTJhREV6TW1FMkxqSTRJRFl1TWpnZ01DQXdJREVnTmk0eU9EWWdOaTR5TnpJZ05pNHlPQ0EyTGpJNElEQWdNQ0F4TFRZdU1qZzJJRFl1TWpjemFDMHhNekpqTFRZdU9UUXpJREF0TVRJdU5UY3hJRFV1TmpFMkxURXlMalUzTVNBeE1pNDFORFpCTVRJdU5UWWdNVEl1TlRZZ01DQXdJREFnTlRBNUxqVTNNU0EyTURSb01UVXdMamcxT0dNMkxqazBNeUF3SURFeUxqVTNNUzAxTGpZeE5pQXhNaTQxTnpFdE1USXVOVFExZGkweE1USXVPVEZqTUMwMkxqa3lPQzAxTGpZeU9DMHhNaTQxTkRVdE1USXVOVGN4TFRFeUxqVTBOVWcxTURrdU5UY3hZeTAyTGprME15QXdMVEV5TGpVM01TQTFMall4TnkweE1pNDFOekVnTVRJdU5UUTFkamMxTGpJM00zcHRNemN1TnpFMExUWXlMamN5TjJNdE5pNDVORE1nTUMweE1pNDFOekVnTlM0Mk1UY3RNVEl1TlRjeElERXlMalUwTlhZeU5TNHdPVEZqTUNBMkxqa3lPU0ExTGpZeU9DQXhNaTQxTkRZZ01USXVOVGN4SURFeUxqVTBObWd4TURBdU5UY3lZell1T1RReklEQWdNVEl1TlRjeExUVXVOakUzSURFeUxqVTNNUzB4TWk0MU5EWjJMVEkxTGpBNU1XTXdMVFl1T1RJNExUVXVOakk0TFRFeUxqVTBOUzB4TWk0MU56RXRNVEl1TlRRMVNEVXpOQzQzTVRSNklpQm1hV3hzTFhKMWJHVTlJbVYyWlc1dlpHUWlJQzgrUEM5blBqd3ZjM1puUGc9PSJ9"',
85+
)
86+
expect(
87+
await readContract(publicClient, {
88+
...wagmiContractConfig,
89+
functionName: 'totalSupply',
90+
}),
91+
).toEqual(558n)
92+
})
93+
})
94+
95+
test('fake contract address', async () => {
96+
await expect(() =>
97+
readContract(publicClient, {
98+
abi: wagmiContractConfig.abi,
99+
address: '0x0000000000000000000000000000000000000069',
100+
functionName: 'totalSupply',
101+
}),
102+
).rejects.toThrowErrorMatchingInlineSnapshot(`
103+
"The contract method \\"totalSupply\\" returned no data (\\"0x\\"). This could be due to any of the following:
104+
- The contract does not have the function \\"totalSupply\\",
105+
- The parameters passed to the contract function may be invalid, or
106+
- The address is not a contract.
107+
108+
Contract: 0x0000000000000000000000000000000000000000
109+
Function: totalSupply()
110+
> \\"0x\\"
111+
112+
113+
`)
114+
})
115+
116+
// Deploy BAYC Contract
117+
async function deployBAYC() {
118+
const hash = await deployContract(walletClient, {
119+
...baycContractConfig,
120+
args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n],
121+
from: accounts[0].address,
122+
})
123+
await mine(testClient, { blocks: 1 })
124+
const { contractAddress } = await getTransactionReceipt(publicClient, {
125+
hash,
126+
})
127+
return { contractAddress }
128+
}

src/actions/public/readContract.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
} from '../../types'
11+
import {
12+
EncodeFunctionDataArgs,
13+
decodeFunctionResult,
14+
encodeFunctionData,
15+
getContractError,
16+
} from '../../utils'
17+
import { call, CallArgs, FormattedCall } from './call'
18+
19+
export type FormattedReadContract<
20+
TFormatter extends Formatter | undefined = Formatter,
21+
> = FormattedCall<TFormatter>
22+
23+
export type ReadContractArgs<
24+
TAbi extends Abi | readonly unknown[] = Abi,
25+
TFunctionName extends string = any,
26+
> = Omit<
27+
CallArgs,
28+
| 'accessList'
29+
| 'chain'
30+
| 'from'
31+
| 'gas'
32+
| 'gasPrice'
33+
| 'maxFeePerGas'
34+
| 'maxPriorityFeePerGas'
35+
| 'nonce'
36+
| 'to'
37+
| 'data'
38+
| 'value'
39+
> & {
40+
address: Address
41+
abi: TAbi
42+
functionName: ExtractFunctionNameFromAbi<TAbi, TFunctionName, 'pure' | 'view'>
43+
} & ExtractArgsFromAbi<TAbi, TFunctionName>
44+
45+
export type ReadContractResponse<
46+
TAbi extends Abi | readonly unknown[] = Abi,
47+
TFunctionName extends string = string,
48+
> = ExtractResultFromAbi<TAbi, TFunctionName>
49+
50+
export async function readContract<
51+
TAbi extends Abi = Abi,
52+
TFunctionName extends string = any,
53+
>(
54+
client: PublicClient,
55+
{
56+
abi,
57+
address,
58+
args,
59+
functionName,
60+
...callRequest
61+
}: ReadContractArgs<TAbi, TFunctionName>,
62+
): Promise<ReadContractResponse<TAbi, TFunctionName>> {
63+
const calldata = encodeFunctionData({
64+
abi,
65+
args,
66+
functionName,
67+
} as unknown as EncodeFunctionDataArgs<TAbi, TFunctionName>)
68+
try {
69+
const { data } = await call(client, {
70+
data: calldata,
71+
to: address,
72+
...callRequest,
73+
} as unknown as CallArgs)
74+
return decodeFunctionResult({
75+
abi,
76+
functionName,
77+
data: data || '0x',
78+
})
79+
} catch (err) {
80+
throw getContractError(err, {
81+
abi,
82+
address,
83+
args,
84+
functionName,
85+
})
86+
}
87+
}

src/actions/public/simulateContract.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@ import {
1616
getContractError,
1717
} from '../../utils'
1818
import { WriteContractArgs } from '../wallet'
19-
import { call, CallArgs, FormattedCall } from './call'
20-
21-
export type FormattedSimulateContract<
22-
TFormatter extends Formatter | undefined = Formatter,
23-
> = FormattedCall<TFormatter>
19+
import { call, CallArgs } from './call'
2420

2521
export type SimulateContractArgs<
2622
TChain extends Chain = Chain,

src/actions/wallet/writeContract.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,11 @@ import type {
1010
} from '../../types'
1111
import { EncodeFunctionDataArgs, encodeFunctionData } from '../../utils'
1212
import {
13-
FormattedTransactionRequest,
1413
sendTransaction,
1514
SendTransactionArgs,
1615
SendTransactionResponse,
1716
} from './sendTransaction'
1817

19-
export type FormattedWriteContract<
20-
TFormatter extends Formatter | undefined = Formatter,
21-
> = FormattedTransactionRequest<TFormatter>
22-
2318
export type WriteContractArgs<
2419
TChain extends Chain = Chain,
2520
TAbi extends Abi | readonly unknown[] = Abi,

0 commit comments

Comments
 (0)