rocketh 0.9.2 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-34ZUTWTW.js → chunk-M4UAFNSM.js} +196 -52
- package/dist/chunk-M4UAFNSM.js.map +1 -0
- package/dist/cli.cjs +214 -66
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +195 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -23
- package/dist/index.d.ts +39 -23
- package/dist/index.js +1 -1
- package/package.json +4 -2
- package/src/cli.ts +2 -3
- package/src/environment/deployments.ts +3 -3
- package/src/environment/index.ts +32 -12
- package/src/environment/types.ts +32 -13
- package/src/executor/index.ts +124 -39
- package/src/utils/eth.ts +103 -0
- package/dist/chunk-34ZUTWTW.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rocketh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "deploy smart contract on ethereum-compatible networks",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/figlet": "^1.5.8",
|
|
17
17
|
"@types/node": "^20.11.19",
|
|
18
|
+
"@types/prompts": "^2.4.9",
|
|
18
19
|
"abitype": "^1.0.0",
|
|
19
|
-
"eip-1193": "^0.
|
|
20
|
+
"eip-1193": "^0.5.0",
|
|
20
21
|
"ipfs-gateway-emulator": "4.2.1-ipfs.2",
|
|
21
22
|
"rimraf": "^5.0.5",
|
|
22
23
|
"tsup": "^8.0.2",
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"ldenv": "^0.3.9",
|
|
34
35
|
"named-logs": "^0.2.2",
|
|
35
36
|
"named-logs-console": "^0.3.0",
|
|
37
|
+
"prompts": "^2.4.2",
|
|
36
38
|
"viem": "^2.7.11"
|
|
37
39
|
},
|
|
38
40
|
"scripts": {
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
import {loadEnv} from 'ldenv';
|
|
3
|
-
import {loadAndExecuteDeployments, readConfig} from '.';
|
|
3
|
+
import {ConfigOptions, loadAndExecuteDeployments, readConfig} from '.';
|
|
4
4
|
import {Command} from 'commander';
|
|
5
5
|
import pkg from '../package.json';
|
|
6
6
|
|
|
@@ -20,6 +20,5 @@ program
|
|
|
20
20
|
.parse(process.argv);
|
|
21
21
|
|
|
22
22
|
const options = program.opts();
|
|
23
|
-
const config = readConfig(options as any);
|
|
24
23
|
|
|
25
|
-
loadAndExecuteDeployments({...
|
|
24
|
+
loadAndExecuteDeployments({...(options as ConfigOptions), logLevel: 1, askBeforeProceeding: true});
|
|
@@ -5,12 +5,12 @@ import {UnknownDeployments} from './types';
|
|
|
5
5
|
|
|
6
6
|
export function loadDeployments(
|
|
7
7
|
deploymentsPath: string,
|
|
8
|
-
|
|
8
|
+
networkName: string,
|
|
9
9
|
onlyABIAndAddress?: boolean,
|
|
10
10
|
expectedChain?: {chainId: string; genesisHash?: `0x${string}`; deleteDeploymentsIfDifferentGenesisHash?: boolean}
|
|
11
11
|
): {deployments: UnknownDeployments; chainId?: string; genesisHash?: `0x${string}`} {
|
|
12
12
|
const deploymentsFound: UnknownDeployments = {};
|
|
13
|
-
const deployPath = path.join(deploymentsPath,
|
|
13
|
+
const deployPath = path.join(deploymentsPath, networkName);
|
|
14
14
|
|
|
15
15
|
let filesStats;
|
|
16
16
|
try {
|
|
@@ -34,7 +34,7 @@ export function loadDeployments(
|
|
|
34
34
|
genesisHash = chainData.genesisHash;
|
|
35
35
|
} else {
|
|
36
36
|
throw new Error(
|
|
37
|
-
`A '.chain' or '.chainId' file is expected to be present in the deployment folder for network ${
|
|
37
|
+
`A '.chain' or '.chainId' file is expected to be present in the deployment folder for network ${networkName}`
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/environment/index.ts
CHANGED
|
@@ -85,7 +85,9 @@ export async function createEnvironment<
|
|
|
85
85
|
providedContext: ProvidedContext<Artifacts, NamedAccounts>
|
|
86
86
|
): Promise<{internal: InternalEnvironment; external: Environment<Artifacts, NamedAccounts, Deployments>}> {
|
|
87
87
|
const provider =
|
|
88
|
-
'provider' in config
|
|
88
|
+
'provider' in config.network
|
|
89
|
+
? config.network.provider
|
|
90
|
+
: (new JSONRPCHTTPProvider(config.network.nodeUrl) as EIP1193ProviderWithoutEvents);
|
|
89
91
|
|
|
90
92
|
const transport = custom(provider);
|
|
91
93
|
const viemClient = createPublicClient({transport});
|
|
@@ -100,24 +102,32 @@ export async function createEnvironment<
|
|
|
100
102
|
|
|
101
103
|
let networkName: string;
|
|
102
104
|
let saveDeployments: boolean;
|
|
103
|
-
let
|
|
105
|
+
let networkTags: {[tag: string]: boolean} = {};
|
|
106
|
+
for (const networkTag of config.network.tags) {
|
|
107
|
+
networkTags[networkTag] = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
104
110
|
if ('nodeUrl' in config) {
|
|
105
|
-
networkName = config.
|
|
111
|
+
networkName = config.network.name;
|
|
106
112
|
saveDeployments = true;
|
|
107
113
|
} else {
|
|
108
|
-
if (config.
|
|
109
|
-
networkName = config.
|
|
114
|
+
if (config.network.name) {
|
|
115
|
+
networkName = config.network.name;
|
|
110
116
|
} else {
|
|
111
117
|
networkName = 'memory';
|
|
112
118
|
}
|
|
113
119
|
if (networkName === 'memory' || networkName === 'hardhat') {
|
|
114
|
-
|
|
120
|
+
networkTags['memory'] = true;
|
|
115
121
|
saveDeployments = false;
|
|
116
122
|
} else {
|
|
117
123
|
saveDeployments = true;
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
|
|
127
|
+
if (config.saveDeployments !== undefined) {
|
|
128
|
+
saveDeployments = config.saveDeployments;
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
const resolvedAccounts: {[name: string]: ResolvedAccount} = {};
|
|
122
132
|
|
|
123
133
|
const accountCache: {[name: string]: ResolvedAccount} = {};
|
|
@@ -205,16 +215,26 @@ export async function createEnvironment<
|
|
|
205
215
|
artifacts: providedContext.artifacts as Artifacts,
|
|
206
216
|
network: {
|
|
207
217
|
name: networkName,
|
|
218
|
+
fork: config.network.fork,
|
|
208
219
|
saveDeployments,
|
|
209
|
-
tags,
|
|
220
|
+
tags: networkTags,
|
|
210
221
|
},
|
|
211
222
|
};
|
|
212
223
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
// console.log(`context`, JSON.stringify(context.network, null, 2));
|
|
225
|
+
|
|
226
|
+
const {deployments} = loadDeployments(
|
|
227
|
+
config.deployments,
|
|
228
|
+
context.network.name,
|
|
229
|
+
false,
|
|
230
|
+
context.network.fork
|
|
231
|
+
? undefined
|
|
232
|
+
: {
|
|
233
|
+
chainId,
|
|
234
|
+
genesisHash,
|
|
235
|
+
deleteDeploymentsIfDifferentGenesisHash: true,
|
|
236
|
+
}
|
|
237
|
+
);
|
|
218
238
|
|
|
219
239
|
const namedAccounts: {[name: string]: EIP1193Account} = {};
|
|
220
240
|
const namedSigners: {[name: string]: NamedSigner} = {};
|
package/src/environment/types.ts
CHANGED
|
@@ -209,30 +209,49 @@ export type Context<
|
|
|
209
209
|
artifacts: Artifacts;
|
|
210
210
|
};
|
|
211
211
|
|
|
212
|
-
type
|
|
213
|
-
|
|
212
|
+
type NetworkConfigBase = {
|
|
213
|
+
name: string;
|
|
214
|
+
tags: string[];
|
|
215
|
+
fork?: boolean;
|
|
216
|
+
};
|
|
217
|
+
type NetworkConfigForJSONRPC = NetworkConfigBase & {
|
|
218
|
+
nodeUrl: string;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
type NetworkConfigForEIP1193Provider = NetworkConfigBase & {
|
|
222
|
+
provider: EIP1193ProviderWithoutEvents;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export type NetworkConfig = NetworkConfigForJSONRPC | NetworkConfigForEIP1193Provider;
|
|
226
|
+
|
|
227
|
+
export type Config = {
|
|
228
|
+
network: NetworkConfig;
|
|
229
|
+
networkTags?: string[];
|
|
214
230
|
scripts?: string;
|
|
215
231
|
deployments?: string;
|
|
232
|
+
saveDeployments?: boolean;
|
|
216
233
|
|
|
217
234
|
tags?: string[];
|
|
235
|
+
askBeforeProceeding?: boolean;
|
|
236
|
+
|
|
218
237
|
logLevel?: number;
|
|
219
238
|
// TODO
|
|
220
239
|
gasPricing?: {};
|
|
221
240
|
};
|
|
222
241
|
|
|
223
|
-
type
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
export type ResolvedConfig = Config & {
|
|
243
|
+
deployments: string;
|
|
244
|
+
scripts: string;
|
|
245
|
+
tags: string[];
|
|
246
|
+
network: {
|
|
247
|
+
name: string;
|
|
248
|
+
tags: string[];
|
|
249
|
+
fork?: boolean;
|
|
250
|
+
};
|
|
251
|
+
saveDeployments?: boolean;
|
|
252
|
+
askBeforeProceeding?: boolean;
|
|
230
253
|
};
|
|
231
254
|
|
|
232
|
-
export type Config = ConfigForJSONRPC | ConfigForEIP1193Provider;
|
|
233
|
-
|
|
234
|
-
export type ResolvedConfig = Config & {deployments: string; scripts: string; tags: string[]; networkName: string};
|
|
235
|
-
|
|
236
255
|
export interface Environment<
|
|
237
256
|
Artifacts extends UnknownArtifacts = UnknownArtifacts,
|
|
238
257
|
NamedAccounts extends UnresolvedUnknownNamedAccounts = UnresolvedUnknownNamedAccounts,
|
package/src/executor/index.ts
CHANGED
|
@@ -13,6 +13,10 @@ import type {
|
|
|
13
13
|
import {createEnvironment} from '../environment';
|
|
14
14
|
import {DeployScriptFunction, DeployScriptModule, ProvidedContext} from './types';
|
|
15
15
|
import {logger, setLogLevel, spin} from '../internal/logging';
|
|
16
|
+
import {EIP1193GenericRequestProvider, EIP1193ProviderWithoutEvents} from 'eip-1193';
|
|
17
|
+
import {getRoughGasPriceEstimate} from '../utils/eth';
|
|
18
|
+
import prompts from 'prompts';
|
|
19
|
+
import {formatEther} from 'viem';
|
|
16
20
|
|
|
17
21
|
if (!process.env['ROCKETH_SKIP_ESBUILD']) {
|
|
18
22
|
require('esbuild-register/dist/node').register();
|
|
@@ -39,28 +43,71 @@ export function execute<
|
|
|
39
43
|
return scriptModule as unknown as DeployScriptModule<Artifacts, NamedAccounts, ArgumentsType, Deployments>;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export type ConfigOptions = {
|
|
46
|
+
export type ConfigOptions = {
|
|
47
|
+
network?: string | {fork: string};
|
|
48
|
+
deployments?: string;
|
|
49
|
+
scripts?: string;
|
|
50
|
+
tags?: string;
|
|
51
|
+
logLevel?: number;
|
|
52
|
+
provider?: EIP1193ProviderWithoutEvents | EIP1193GenericRequestProvider;
|
|
53
|
+
ignoreMissingRPC?: boolean;
|
|
54
|
+
saveDeployments?: boolean;
|
|
55
|
+
askBeforeProceeding?: boolean;
|
|
56
|
+
};
|
|
43
57
|
|
|
44
|
-
export function readConfig(options: ConfigOptions
|
|
45
|
-
type Networks = {[name: string]: {rpcUrl
|
|
46
|
-
type ConfigFile = {networks: Networks};
|
|
58
|
+
export function readConfig(options: ConfigOptions): Config {
|
|
59
|
+
type Networks = {[name: string]: {rpcUrl?: string; tags?: string[]}};
|
|
60
|
+
type ConfigFile = {networks: Networks; deployments?: string; scripts?: string};
|
|
47
61
|
let configFile: ConfigFile | undefined;
|
|
48
62
|
try {
|
|
49
63
|
const configString = fs.readFileSync('./rocketh.json', 'utf-8');
|
|
50
64
|
configFile = JSON.parse(configString);
|
|
51
65
|
} catch {}
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
if (configFile) {
|
|
68
|
+
if (!options.deployments && configFile.deployments) {
|
|
69
|
+
options.deployments = configFile.deployments;
|
|
70
|
+
}
|
|
71
|
+
if (!options.scripts && configFile.scripts) {
|
|
72
|
+
options.scripts = configFile.scripts;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
const fromEnv = process.env['ETH_NODE_URI_' + options.network];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
const fork = typeof options.network !== 'string';
|
|
78
|
+
let networkName = 'memory';
|
|
79
|
+
if (options.network) {
|
|
80
|
+
if (typeof options.network === 'string') {
|
|
81
|
+
networkName = options.network;
|
|
82
|
+
} else if ('fork' in options.network) {
|
|
83
|
+
networkName = options.network.fork;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let networkTags: string[] = (configFile?.networks && configFile?.networks[networkName]?.tags) || [];
|
|
88
|
+
if (!options.provider) {
|
|
89
|
+
let nodeUrl: string;
|
|
90
|
+
if (typeof fromEnv === 'string') {
|
|
91
|
+
nodeUrl = fromEnv;
|
|
92
|
+
} else {
|
|
93
|
+
if (configFile) {
|
|
94
|
+
const network = configFile.networks && configFile.networks[networkName];
|
|
95
|
+
if (network && network.rpcUrl) {
|
|
96
|
+
nodeUrl = network.rpcUrl;
|
|
97
|
+
} else {
|
|
98
|
+
if (options?.ignoreMissingRPC) {
|
|
99
|
+
nodeUrl = '';
|
|
100
|
+
} else {
|
|
101
|
+
if (options.network === 'localhost') {
|
|
102
|
+
nodeUrl = 'http://127.0.0.1:8545';
|
|
103
|
+
} else {
|
|
104
|
+
logger.error(`network "${options.network}" is not configured. Please add it to the rocketh.json file`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
62
109
|
} else {
|
|
63
|
-
if (
|
|
110
|
+
if (options?.ignoreMissingRPC) {
|
|
64
111
|
nodeUrl = '';
|
|
65
112
|
} else {
|
|
66
113
|
if (options.network === 'localhost') {
|
|
@@ -71,40 +118,52 @@ export function readConfig(options: ConfigOptions, extra?: {ignoreMissingRPC?: b
|
|
|
71
118
|
}
|
|
72
119
|
}
|
|
73
120
|
}
|
|
74
|
-
} else {
|
|
75
|
-
if (extra?.ignoreMissingRPC) {
|
|
76
|
-
nodeUrl = '';
|
|
77
|
-
} else {
|
|
78
|
-
if (options.network === 'localhost') {
|
|
79
|
-
nodeUrl = 'http://127.0.0.1:8545';
|
|
80
|
-
} else {
|
|
81
|
-
logger.error(`network "${options.network}" is not configured. Please add it to the rocketh.json file`);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
121
|
}
|
|
122
|
+
return {
|
|
123
|
+
network: {
|
|
124
|
+
nodeUrl,
|
|
125
|
+
name: networkName,
|
|
126
|
+
tags: networkTags,
|
|
127
|
+
fork,
|
|
128
|
+
},
|
|
129
|
+
deployments: options.deployments,
|
|
130
|
+
saveDeployments: options.saveDeployments,
|
|
131
|
+
scripts: options.scripts,
|
|
132
|
+
tags: typeof options.tags === 'undefined' ? undefined : options.tags.split(','),
|
|
133
|
+
logLevel: options.logLevel,
|
|
134
|
+
askBeforeProceeding: options.askBeforeProceeding,
|
|
135
|
+
};
|
|
136
|
+
} else {
|
|
137
|
+
return {
|
|
138
|
+
network: {
|
|
139
|
+
provider: options.provider as EIP1193ProviderWithoutEvents,
|
|
140
|
+
name: networkName,
|
|
141
|
+
tags: networkTags,
|
|
142
|
+
fork,
|
|
143
|
+
},
|
|
144
|
+
deployments: options.deployments,
|
|
145
|
+
saveDeployments: options.saveDeployments,
|
|
146
|
+
scripts: options.scripts,
|
|
147
|
+
tags: typeof options.tags === 'undefined' ? undefined : options.tags.split(','),
|
|
148
|
+
logLevel: options.logLevel,
|
|
149
|
+
askBeforeProceeding: options.askBeforeProceeding,
|
|
150
|
+
};
|
|
86
151
|
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
nodeUrl,
|
|
90
|
-
networkName: options.network,
|
|
91
|
-
deployments: options.deployments,
|
|
92
|
-
scripts: options.scripts,
|
|
93
|
-
tags: typeof options.tags === 'undefined' ? undefined : options.tags.split(','),
|
|
94
|
-
};
|
|
95
152
|
}
|
|
96
153
|
|
|
97
|
-
export function readAndResolveConfig(options: ConfigOptions
|
|
98
|
-
return resolveConfig(readConfig(options
|
|
154
|
+
export function readAndResolveConfig(options: ConfigOptions): ResolvedConfig {
|
|
155
|
+
return resolveConfig(readConfig(options));
|
|
99
156
|
}
|
|
100
157
|
|
|
101
158
|
export function resolveConfig(config: Config): ResolvedConfig {
|
|
102
159
|
const resolvedConfig: ResolvedConfig = {
|
|
103
160
|
...config,
|
|
104
|
-
|
|
161
|
+
network: config.network, // TODO default to || {name: 'memory'....}
|
|
105
162
|
deployments: config.deployments || 'deployments',
|
|
106
163
|
scripts: config.scripts || 'deploy',
|
|
107
164
|
tags: config.tags || [],
|
|
165
|
+
networkTags: config.networkTags || [],
|
|
166
|
+
saveDeployments: config.saveDeployments,
|
|
108
167
|
};
|
|
109
168
|
return resolvedConfig;
|
|
110
169
|
}
|
|
@@ -112,8 +171,8 @@ export function resolveConfig(config: Config): ResolvedConfig {
|
|
|
112
171
|
export async function loadEnvironment<
|
|
113
172
|
Artifacts extends UnknownArtifacts = UnknownArtifacts,
|
|
114
173
|
NamedAccounts extends UnresolvedUnknownNamedAccounts = UnresolvedUnknownNamedAccounts
|
|
115
|
-
>(
|
|
116
|
-
const resolvedConfig =
|
|
174
|
+
>(options: ConfigOptions, context: ProvidedContext<Artifacts, NamedAccounts>): Promise<Environment> {
|
|
175
|
+
const resolvedConfig = readAndResolveConfig(options);
|
|
117
176
|
const {external, internal} = await createEnvironment(resolvedConfig, context);
|
|
118
177
|
return external;
|
|
119
178
|
}
|
|
@@ -123,8 +182,10 @@ export async function loadAndExecuteDeployments<
|
|
|
123
182
|
NamedAccounts extends UnresolvedUnknownNamedAccounts = UnresolvedUnknownNamedAccounts,
|
|
124
183
|
ArgumentsType = undefined,
|
|
125
184
|
Deployments extends UnknownDeployments = UnknownDeployments
|
|
126
|
-
>(
|
|
127
|
-
const resolvedConfig =
|
|
185
|
+
>(options: ConfigOptions, args?: ArgumentsType): Promise<Environment> {
|
|
186
|
+
const resolvedConfig = readAndResolveConfig(options);
|
|
187
|
+
// console.log(JSON.stringify(options, null, 2));
|
|
188
|
+
// console.log(JSON.stringify(resolvedConfig, null, 2));
|
|
128
189
|
return executeDeployScripts<Artifacts, NamedAccounts, ArgumentsType, Deployments>(resolvedConfig, args);
|
|
129
190
|
}
|
|
130
191
|
|
|
@@ -269,6 +330,30 @@ export async function executeDeployScripts<
|
|
|
269
330
|
recurseDependencies(scriptFilePath);
|
|
270
331
|
}
|
|
271
332
|
|
|
333
|
+
if (config.askBeforeProceeding) {
|
|
334
|
+
const gasPriceEstimate = await getRoughGasPriceEstimate(external.network.provider);
|
|
335
|
+
const prompt = await prompts({
|
|
336
|
+
type: 'confirm',
|
|
337
|
+
name: 'proceed',
|
|
338
|
+
message: `gas price is currently in this range:
|
|
339
|
+
slow: ${formatEther(gasPriceEstimate.slow.maxFeePerGas)} (priority: ${formatEther(
|
|
340
|
+
gasPriceEstimate.slow.maxPriorityFeePerGas
|
|
341
|
+
)})
|
|
342
|
+
average: ${formatEther(gasPriceEstimate.average.maxFeePerGas)} (priority: ${formatEther(
|
|
343
|
+
gasPriceEstimate.average.maxPriorityFeePerGas
|
|
344
|
+
)})
|
|
345
|
+
fast: ${formatEther(gasPriceEstimate.fast.maxFeePerGas)} (priority: ${formatEther(
|
|
346
|
+
gasPriceEstimate.fast.maxPriorityFeePerGas
|
|
347
|
+
)})
|
|
348
|
+
|
|
349
|
+
Do you want to proceed (note that gas price can change for each tx)`,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (!prompt.proceed) {
|
|
353
|
+
process.exit();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
272
357
|
for (const deployScript of scriptsToRun.concat(scriptsToRunAtTheEnd)) {
|
|
273
358
|
const filename = path.basename(deployScript.filePath);
|
|
274
359
|
const relativeFilepath = path.relative('.', deployScript.filePath);
|
package/src/utils/eth.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {EIP1193BlockTag, EIP1193ProviderWithoutEvents} from 'eip-1193';
|
|
2
|
+
|
|
3
|
+
function avg(arr: bigint[]) {
|
|
4
|
+
const sum = arr.reduce((a: bigint, v: bigint) => a + v);
|
|
5
|
+
return sum / BigInt(arr.length);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type EIP1193FeeHistory = {
|
|
9
|
+
oldestBlock: string;
|
|
10
|
+
reward: `0x${string}`[][];
|
|
11
|
+
baseFeePerGas: string[];
|
|
12
|
+
gasUsedRatio: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type EstimateGasPriceOptions = {
|
|
16
|
+
blockCount: number;
|
|
17
|
+
newestBlock: EIP1193BlockTag;
|
|
18
|
+
rewardPercentiles: number[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RoughEstimateGasPriceOptions = {
|
|
22
|
+
blockCount: number;
|
|
23
|
+
newestBlock: EIP1193BlockTag;
|
|
24
|
+
rewardPercentiles: [number, number, number];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type GasPrice = {maxFeePerGas: bigint; maxPriorityFeePerGas: bigint};
|
|
28
|
+
export type EstimateGasPriceResult = GasPrice[];
|
|
29
|
+
export type RoughEstimateGasPriceResult = {slow: GasPrice; average: GasPrice; fast: GasPrice};
|
|
30
|
+
|
|
31
|
+
export async function getGasPriceEstimate(
|
|
32
|
+
provider: EIP1193ProviderWithoutEvents,
|
|
33
|
+
options?: Partial<EstimateGasPriceOptions>
|
|
34
|
+
): Promise<EstimateGasPriceResult> {
|
|
35
|
+
const defaultOptions: EstimateGasPriceOptions = {
|
|
36
|
+
blockCount: 20,
|
|
37
|
+
newestBlock: 'pending',
|
|
38
|
+
rewardPercentiles: [10, 50, 80],
|
|
39
|
+
};
|
|
40
|
+
const optionsResolved = options ? {...defaultOptions, ...options} : defaultOptions;
|
|
41
|
+
|
|
42
|
+
const historicalBlocks = optionsResolved.blockCount;
|
|
43
|
+
|
|
44
|
+
const rawFeeHistory = await provider.request<EIP1193FeeHistory>({
|
|
45
|
+
method: 'eth_feeHistory',
|
|
46
|
+
params: [`0x${historicalBlocks.toString(16)}`, optionsResolved.newestBlock, optionsResolved.rewardPercentiles],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let blockNum = Number(rawFeeHistory.oldestBlock);
|
|
50
|
+
const lastBlock = blockNum + rawFeeHistory.reward.length;
|
|
51
|
+
let index = 0;
|
|
52
|
+
const blocksHistory: {number: number; baseFeePerGas: bigint; gasUsedRatio: number; priorityFeePerGas: bigint[]}[] =
|
|
53
|
+
[];
|
|
54
|
+
while (blockNum < lastBlock) {
|
|
55
|
+
blocksHistory.push({
|
|
56
|
+
number: blockNum,
|
|
57
|
+
baseFeePerGas: BigInt(rawFeeHistory.baseFeePerGas[index]),
|
|
58
|
+
gasUsedRatio: Number(rawFeeHistory.gasUsedRatio[index]),
|
|
59
|
+
priorityFeePerGas: rawFeeHistory.reward[index].map((x) => BigInt(x)),
|
|
60
|
+
});
|
|
61
|
+
blockNum += 1;
|
|
62
|
+
index += 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const percentilePriorityFeeAverages: bigint[] = [];
|
|
66
|
+
for (let i = 0; i < optionsResolved.rewardPercentiles.length; i++) {
|
|
67
|
+
percentilePriorityFeeAverages.push(avg(blocksHistory.map((b) => b.priorityFeePerGas[i])));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const baseFeePerGas = BigInt(rawFeeHistory.baseFeePerGas[rawFeeHistory.baseFeePerGas.length - 1]);
|
|
71
|
+
|
|
72
|
+
const result: EstimateGasPriceResult = [];
|
|
73
|
+
for (let i = 0; i < optionsResolved.rewardPercentiles.length; i++) {
|
|
74
|
+
result.push({
|
|
75
|
+
maxFeePerGas: percentilePriorityFeeAverages[i] + baseFeePerGas,
|
|
76
|
+
maxPriorityFeePerGas: percentilePriorityFeeAverages[i],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function getRoughGasPriceEstimate(
|
|
83
|
+
provider: EIP1193ProviderWithoutEvents,
|
|
84
|
+
options?: Partial<RoughEstimateGasPriceOptions>
|
|
85
|
+
): Promise<RoughEstimateGasPriceResult> {
|
|
86
|
+
const defaultOptions: EstimateGasPriceOptions = {
|
|
87
|
+
blockCount: 20,
|
|
88
|
+
newestBlock: 'pending',
|
|
89
|
+
rewardPercentiles: [10, 50, 80],
|
|
90
|
+
};
|
|
91
|
+
const optionsResolved = options ? {...defaultOptions, ...options} : defaultOptions;
|
|
92
|
+
|
|
93
|
+
if (optionsResolved.rewardPercentiles.length !== 3) {
|
|
94
|
+
throw new Error(`rough gas estimate require 3 percentile, it defaults to [10,50,80]`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await getGasPriceEstimate(provider, optionsResolved);
|
|
98
|
+
return {
|
|
99
|
+
slow: result[0],
|
|
100
|
+
average: result[1],
|
|
101
|
+
fast: result[2],
|
|
102
|
+
};
|
|
103
|
+
}
|