forked from ethereum-optimism/optimism-starter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathestimate-fees.ts
372 lines (349 loc) · 9.87 KB
/
estimate-fees.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/**
* Copied and altered from repo listed below to work with newer viem version as
* the package itself wouldn't work due to import errors.
* https://github.com/ethereum-optimism/optimism/tree/develop/packages/fee-estimation
*
* Marked bigger changes with `CHANGED:` for quicker scanning.
*/
import {
gasPriceOracleABI,
gasPriceOracleAddress,
} from "@eth-optimism/contracts-ts";
import { Abi } from "abitype";
import {
getContract,
createPublicClient,
http,
BlockTag,
Address,
EstimateGasParameters,
serializeTransaction,
encodeFunctionData,
EncodeFunctionDataParameters,
TransactionSerializableEIP1559,
TransactionSerializedEIP1559,
PublicClient,
} from "viem";
import * as chains from "viem/chains";
/**
* Bytes type representing a hex string with a 0x prefix
* @typedef {`0x${string}`} Bytes
*/
export type Bytes = `0x${string}`;
/**
* Options to query a specific block
*/
type BlockOptions = {
/**
* Block number to query from
*/
blockNumber?: bigint;
/**
* Block tag to query from
*/
blockTag?: BlockTag;
};
// CHANGED: updated all values to be IDs
const knownChains = [
chains.optimism.id,
chains.goerli.id,
chains.base.id,
chains.baseGoerli.id,
chains.zora.id,
chains.zoraTestnet.id,
];
/**
* ClientOptions type
* @typedef {Object} ClientOptions
* @property {keyof typeof gasPriceOracleAddress | number} chainId - Chain ID
* @property {string} [rpcUrl] - RPC URL. If not provided the provider will attempt to use public RPC URLs for the chain
* @property {chains.Chain['nativeCurrency']} [nativeCurrency] - Native currency. Defaults to ETH
*/
type ClientOptions =
// for known chains like base don't require an rpcUrl
| {
chainId: (typeof knownChains)[number];
rpcUrl?: string;
nativeCurrency?: chains.Chain["nativeCurrency"];
}
| {
chainId: number;
rpcUrl: string;
nativeCurrency?: chains.Chain["nativeCurrency"];
}
| PublicClient;
/**
* Options for all GasPriceOracle methods
*/
export type GasPriceOracleOptions = BlockOptions & { client: ClientOptions };
/**
* Options for specifying the transaction being estimated
*/
export type OracleTransactionParameters<
TAbi extends Abi | readonly unknown[],
TFunctionName extends string | undefined = undefined,
> = EncodeFunctionDataParameters<TAbi, TFunctionName> &
Omit<TransactionSerializableEIP1559, "data" | "type">;
/**
* Options for specifying the transaction being estimated
*/
export type GasPriceOracleEstimator = <
TAbi extends Abi | readonly unknown[],
TFunctionName extends string | undefined = undefined,
>(
options: OracleTransactionParameters<TAbi, TFunctionName> &
GasPriceOracleOptions,
) => Promise<bigint>;
/**
* Throws an error if fetch is not defined
* Viem requires fetch
*/
const validateFetch = (): void => {
if (typeof fetch === "undefined") {
throw new Error(
"No fetch implementation found. Please provide a fetch polyfill. This can be done in NODE by passing in NODE_OPTIONS=--experimental-fetch or by using the isomorphic-fetch npm package",
);
}
};
/**
* Internal helper to serialize a transaction
*/
const transactionSerializer = <
TAbi extends Abi | readonly unknown[],
TFunctionName extends string | undefined = undefined,
>(
options: EncodeFunctionDataParameters<TAbi, TFunctionName> &
Omit<TransactionSerializableEIP1559, "data">,
): TransactionSerializedEIP1559 => {
const encodedFunctionData = encodeFunctionData(options);
const serializedTransaction = serializeTransaction({
...options,
data: encodedFunctionData,
type: "eip1559",
});
return serializedTransaction as TransactionSerializedEIP1559;
};
/**
* Gets L2 client
* @example
* const client = getL2Client({ chainId: 1, rpcUrl: "http://localhost:8545" });
*/
export const getL2Client = (options: ClientOptions): PublicClient => {
validateFetch();
if ("chainId" in options && options.chainId) {
const viemChain = Object.values(chains)?.find(
(chain) => chain.id === options.chainId,
);
// CHANGED: had some config issues, so just forced the url here.
const rpcUrls = {
default: {
http: [options.rpcUrl!],
},
public: {
http: [options.rpcUrl!],
},
};
if (!rpcUrls) {
throw new Error(
`No rpcUrls found for chainId ${options.chainId}. Please explicitly provide one`,
);
}
return createPublicClient({
chain: {
id: options.chainId,
name: viemChain?.name ?? "op-chain",
nativeCurrency:
options.nativeCurrency ??
viemChain?.nativeCurrency ??
chains.optimism.nativeCurrency,
network: viemChain?.network ?? "Unknown OP Chain",
rpcUrls,
explorers:
(viemChain as typeof chains.optimism)?.blockExplorers ??
chains.optimism.blockExplorers,
},
transport: http(options.rpcUrl),
});
}
return options as PublicClient;
};
/**
* Get gas price Oracle contract
*/
export const getGasPriceOracleContract = (params: ClientOptions) => {
return getContract({
address: gasPriceOracleAddress["420"],
abi: gasPriceOracleABI,
publicClient: getL2Client(params),
});
};
/**
* Returns the base fee
* @returns {Promise<bigint>} - The base fee
* @example
* const baseFeeValue = await baseFee(params);
*/
export const baseFee = async ({
client,
blockNumber,
blockTag,
}: GasPriceOracleOptions): Promise<bigint> => {
const contract = getGasPriceOracleContract(client);
return contract.read.baseFee({ blockNumber, blockTag });
};
/**
* Returns the decimals used in the scalar
* @example
* const decimalsValue = await decimals(params);
*/
export const decimals = async ({
client,
blockNumber,
blockTag,
}: GasPriceOracleOptions): Promise<bigint> => {
const contract = getGasPriceOracleContract(client);
return contract.read.decimals({ blockNumber, blockTag });
};
/**
* Returns the gas price
* @example
* const gasPriceValue = await gasPrice(params);
*/
export const gasPrice = async ({
client,
blockNumber,
blockTag,
}: GasPriceOracleOptions): Promise<bigint> => {
const contract = getGasPriceOracleContract(client);
return contract.read.gasPrice({ blockNumber, blockTag });
};
/**
* Computes the L1 portion of the fee based on the size of the rlp encoded input
* transaction, the current L1 base fee, and the various dynamic parameters.
* @example
* const L1FeeValue = await getL1Fee(data, params);
*/
export const getL1Fee: GasPriceOracleEstimator = async (options) => {
const data = transactionSerializer(options);
const contract = getGasPriceOracleContract(options.client);
return contract.read.getL1Fee([data], {
blockNumber: options.blockNumber,
blockTag: options.blockTag,
});
};
/**
* Returns the L1 gas used
* @example
*/
export const getL1GasUsed: GasPriceOracleEstimator = async (options) => {
const data = transactionSerializer(options);
const contract = getGasPriceOracleContract(options.client);
return contract.read.getL1GasUsed([data], {
blockNumber: options.blockNumber,
blockTag: options.blockTag,
});
};
/**
* Returns the L1 base fee
* @example
* const L1BaseFeeValue = await l1BaseFee(params);
*/
export const l1BaseFee = async ({
client,
blockNumber,
blockTag,
}: GasPriceOracleOptions): Promise<bigint> => {
const contract = getGasPriceOracleContract(client);
return contract.read.l1BaseFee({ blockNumber, blockTag });
};
/**
* Returns the overhead
* @example
* const overheadValue = await overhead(params);
*/
export const overhead = async ({
client,
blockNumber,
blockTag,
}: GasPriceOracleOptions): Promise<bigint> => {
const contract = getGasPriceOracleContract(client);
return contract.read.overhead({ blockNumber, blockTag });
};
/**
* Returns the current fee scalar
* @example
* const scalarValue = await scalar(params);
*/
export const scalar = async ({
client,
...params
}: GasPriceOracleOptions): Promise<bigint> => {
const contract = getGasPriceOracleContract(client);
return contract.read.scalar(params);
};
/**
* Returns the version
* @example
* const versionValue = await version(params);
*/
export const version = async ({
client,
...params
}: GasPriceOracleOptions): Promise<string> => {
const contract = getGasPriceOracleContract(client);
return contract.read.version(params);
};
export type EstimateFeeParams = {
/**
* The transaction call data as a 0x-prefixed hex string
*/
data: Bytes;
/**
* The address of the account that will be sending the transaction
*/
account: Address;
} & GasPriceOracleOptions &
Omit<EstimateGasParameters, "data" | "account">;
// CHANGED: updated response type
type EstimateFeesResponse = {
l1Fee: bigint;
l2Fee: bigint;
total: bigint;
};
export type EstimateFees = <
TAbi extends Abi | readonly unknown[],
TFunctionName extends string | undefined = undefined,
>(
options: OracleTransactionParameters<TAbi, TFunctionName> &
GasPriceOracleOptions &
Omit<EstimateGasParameters, "data">,
) => Promise<EstimateFeesResponse>;
/**
* Estimates gas for an L2 transaction including the l1 fee
*/
export const estimateFees: EstimateFees = async (options) => {
const client = getL2Client(options.client);
const encodedFunctionData = encodeFunctionData({
abi: options.abi,
args: options.args,
functionName: options.functionName,
} as EncodeFunctionDataParameters);
const [l1Fee, l2Fee] = await Promise.all([
getL1Fee({
...options,
// account must be undefined or else viem will return undefined
account: undefined as any,
}),
client.estimateGas({
to: options.to,
account: options.account,
accessList: options.accessList,
blockNumber: options.blockNumber,
blockTag: options.blockTag,
data: encodedFunctionData,
value: options.value,
} as EstimateGasParameters<typeof chains.optimism>),
]);
// CHANGED: to return all fees at once
return { l1Fee, l2Fee, total: l1Fee + l2Fee };
};