-
Notifications
You must be signed in to change notification settings - Fork 174
/
createContract.ts
152 lines (139 loc) · 5.26 KB
/
createContract.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import {
Abi,
Account,
Address,
Chain,
GetContractParameters,
GetContractReturnType,
PublicClient,
SimulateContractParameters,
Transport,
WalletClient,
WriteContractParameters,
getContract,
} from "viem";
import pRetry from "p-retry";
import { createNonceManager } from "./createNonceManager";
import { debug as parentDebug } from "./debug";
import { UnionOmit } from "./type-utils/common";
const debug = parentDebug.extend("createContract");
// copied from viem because this isn't exported
// TODO: import from viem?
function getFunctionParameters(values: [args?: readonly unknown[], options?: object]): {
args: readonly unknown[];
options: object;
} {
const hasArgs = values.length && Array.isArray(values[0]);
const args = hasArgs ? values[0]! : [];
const options = (hasArgs ? values[1] : values[0]) ?? {};
return { args, options };
}
export function createContract<
TTransport extends Transport,
TAddress extends Address,
TAbi extends Abi,
TChain extends Chain,
TAccount extends Account,
TPublicClient extends PublicClient<TTransport, TChain>,
TWalletClient extends WalletClient<TTransport, TChain, TAccount>
>({
abi,
address,
publicClient,
walletClient,
}: Required<
GetContractParameters<TTransport, TChain, TAccount, TAbi, TPublicClient, TWalletClient, TAddress>
>): GetContractReturnType<TAbi, TPublicClient, TWalletClient, TAddress> {
const contract = getContract<TTransport, TAddress, TAbi, TChain, TAccount, TPublicClient, TWalletClient>({
abi,
address,
publicClient,
walletClient,
}) as unknown as GetContractReturnType<Abi, PublicClient, WalletClient>;
if (contract.write) {
const nonceManager = createNonceManager({
publicClient: publicClient as PublicClient,
address: walletClient.account.address,
});
// Replace write calls with our own proxy. Implemented ~the same as viem, but adds better handling of nonces (via queue + retries).
contract.write = new Proxy(
{},
{
get(_, functionName: string): GetContractReturnType<Abi, PublicClient, WalletClient>["write"][string] {
return async (...parameters) => {
const { args, options } = <
{
args: unknown[];
options: UnionOmit<WriteContractParameters, "abi" | "address" | "functionName" | "args">;
}
>getFunctionParameters(parameters as any);
// Temporarily override base fee for our default anvil config
// TODO: replace with https://github.com/wagmi-dev/viem/pull/1006 once merged
// TODO: more specific mud foundry check? or can we safely assume anvil+mud will be block fee zero for now?
if (
walletClient.chain.id === 31337 &&
options.maxFeePerGas == null &&
options.maxPriorityFeePerGas == null
) {
debug("assuming zero base fee for anvil chain");
options.maxFeePerGas = 0n;
options.maxPriorityFeePerGas = 0n;
}
async function prepareWrite(): Promise<
WriteContractParameters<TAbi, typeof functionName, TChain, TAccount>
> {
if (options.gas) {
debug("gas provided, skipping simulate", functionName, args, options);
return {
address,
abi,
functionName,
args,
...options,
} as unknown as WriteContractParameters<TAbi, typeof functionName, TChain, TAccount>;
}
debug("simulating write", functionName, args, options);
const { request } = await publicClient.simulateContract({
address,
abi,
functionName,
args,
...options,
account: options.account ?? walletClient.account,
} as unknown as SimulateContractParameters<TAbi, typeof functionName, TChain>);
return request as unknown as WriteContractParameters<TAbi, typeof functionName, TChain, TAccount>;
}
const preparedWrite = await prepareWrite();
return await pRetry(
async () => {
if (!nonceManager.hasNonce()) {
await nonceManager.resetNonce();
}
const nonce = nonceManager.nextNonce();
debug("calling write function with nonce", nonce, preparedWrite);
return await walletClient.writeContract({
nonce,
...preparedWrite,
});
},
{
retries: 3,
onFailedAttempt: async (error) => {
// On nonce errors, reset the nonce and retry
if (nonceManager.shouldResetNonce(error)) {
debug("got nonce error, retrying", error);
await nonceManager.resetNonce();
return;
}
// TODO: prepareWrite again if there are gas errors?
throw error;
},
}
);
};
},
}
);
}
return contract as unknown as GetContractReturnType<TAbi, TPublicClient, TWalletClient, TAddress>;
}