rocketh 0.4.41 → 0.5.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.
@@ -0,0 +1,392 @@
1
+ import fs from 'node:fs';
2
+
3
+ import {createPublicClient, custom} from 'viem';
4
+ import {
5
+ AccountType,
6
+ Deployment,
7
+ Environment,
8
+ NamedSigner,
9
+ PendingDeployment,
10
+ ResolvedAccount,
11
+ ResolvedConfig,
12
+ ResolvedNamedAccounts,
13
+ ResolvedNamedSigners,
14
+ UnknownArtifacts,
15
+ UnknownDeployments,
16
+ UnresolvedUnknownNamedAccounts,
17
+ } from './types';
18
+ import {JSONRPCHTTPProvider} from 'eip-1193-json-provider';
19
+ import {Abi} from 'abitype';
20
+ import {InternalEnvironment} from '../internal/types';
21
+ import path from 'node:path';
22
+ import {JSONToString, stringToJSON} from '../utils/json';
23
+ import {loadDeployments} from './deployments';
24
+ import {EIP1193Account, EIP1193ProviderWithoutEvents} from 'eip-1193';
25
+ import {ProvidedContext} from '../executor/types';
26
+
27
+ export type EnvironmentExtenstion = (env: Environment) => Environment;
28
+ //we store this globally so this is not lost
29
+ (globalThis as any).extensions = [];
30
+ export function extendEnvironment(extension: EnvironmentExtenstion): void {
31
+ (globalThis as any).extensions.push(extension);
32
+ }
33
+
34
+ export type SignerProtocolFunction = (protocolString: string) => Promise<NamedSigner>;
35
+ export type SignerProtocol = {
36
+ getSigner: SignerProtocolFunction;
37
+ };
38
+
39
+ //we store this globally so this is not lost
40
+ (globalThis as any).signerProtocols = {};
41
+ export function handleSignerProtocol(protocol: string, getSigner: SignerProtocolFunction): void {
42
+ (globalThis as any).signerProtocols[protocol] = {
43
+ getSigner,
44
+ };
45
+ }
46
+
47
+ export async function createEnvironment<
48
+ Artifacts extends UnknownArtifacts = UnknownArtifacts,
49
+ NamedAccounts extends UnresolvedUnknownNamedAccounts = UnresolvedUnknownNamedAccounts,
50
+ Deployments extends UnknownDeployments = UnknownDeployments
51
+ >(
52
+ config: ResolvedConfig,
53
+ providedContext: ProvidedContext<Artifacts, NamedAccounts>
54
+ ): Promise<{internal: InternalEnvironment; external: Environment}> {
55
+ const provider =
56
+ 'provider' in config ? config.provider : (new JSONRPCHTTPProvider(config.nodeUrl) as EIP1193ProviderWithoutEvents);
57
+
58
+ const transport = custom(provider);
59
+ const viemClient = createPublicClient({transport});
60
+
61
+ const chainId = (await viemClient.getChainId()).toString();
62
+
63
+ let networkName: string;
64
+ let saveDeployments: boolean;
65
+ let tags: {[tag: string]: boolean} = {};
66
+ if ('nodeUrl' in config) {
67
+ networkName = config.networkName;
68
+ saveDeployments = true;
69
+ } else {
70
+ if (config.networkName) {
71
+ networkName = config.networkName;
72
+ } else {
73
+ networkName = 'memory';
74
+ }
75
+ if (networkName === 'memory' || networkName === 'hardhat') {
76
+ tags['memory'] = true;
77
+ saveDeployments = false;
78
+ } else {
79
+ saveDeployments = true;
80
+ }
81
+ }
82
+
83
+ const resolvedAccounts: {[name: string]: ResolvedAccount} = {};
84
+
85
+ const accountCache: {[name: string]: ResolvedAccount} = {};
86
+ async function getAccount(
87
+ name: string,
88
+ accounts: UnresolvedUnknownNamedAccounts,
89
+ accountDef: AccountType
90
+ ): Promise<ResolvedAccount | undefined> {
91
+ if (accountCache[name]) {
92
+ return accountCache[name];
93
+ }
94
+ let account: ResolvedAccount | undefined;
95
+ if (typeof accountDef === 'number') {
96
+ const accounts = await provider.request({method: 'eth_accounts'});
97
+ const accountPerIndex = accounts[accountDef];
98
+ if (accountPerIndex) {
99
+ accountCache[name] = account = {
100
+ type: 'remote',
101
+ address: accountPerIndex,
102
+ signer: provider,
103
+ };
104
+ }
105
+ } else if (typeof accountDef === 'string') {
106
+ if (accountDef.startsWith('0x')) {
107
+ if (accountDef.length === 66) {
108
+ const privateKeyProtocol: SignerProtocol = (globalThis as any).signerProtocols['privateKey'];
109
+ if (privateKeyProtocol) {
110
+ const namedSigner = await privateKeyProtocol.getSigner(`privateKey:${accountDef}`);
111
+ const [address] = await namedSigner.signer.request({method: 'eth_accounts'});
112
+ accountCache[name] = account = {
113
+ ...namedSigner,
114
+ address,
115
+ };
116
+ }
117
+ } else {
118
+ accountCache[name] = account = {
119
+ type: 'remote',
120
+ address: accountDef as `0x${string}`,
121
+ signer: provider,
122
+ };
123
+ }
124
+ } else {
125
+ if (accountDef.indexOf(':') > 0) {
126
+ const [protocolID, extra] = accountDef.split(':');
127
+ const protocol: SignerProtocol = (globalThis as any).signerProtocols[protocolID];
128
+ if (!protocol) {
129
+ throw new Error(`protocol: ${protocol} is not supported`);
130
+ }
131
+ const namedSigner = await protocol.getSigner(accountDef);
132
+ const [address] = await namedSigner.signer.request({method: 'eth_accounts'});
133
+ accountCache[name] = account = {
134
+ ...namedSigner,
135
+ address,
136
+ };
137
+ } else {
138
+ const accountFetched = await getAccount(name, accounts, accounts[accountDef]);
139
+ if (accountFetched) {
140
+ accountCache[name] = account = accountFetched;
141
+ }
142
+ }
143
+ }
144
+ } else {
145
+ const accountForNetwork = accountDef[networkName] || accountDef[chainId] || accountDef['default'];
146
+ if (accountForNetwork) {
147
+ const accountFetched = await getAccount(name, accounts, accountForNetwork);
148
+ if (accountFetched) {
149
+ accountCache[name] = account = accountFetched;
150
+ }
151
+ }
152
+ }
153
+
154
+ return account;
155
+ }
156
+
157
+ if (providedContext.accounts) {
158
+ const accountNames = Object.keys(providedContext.accounts);
159
+ for (const accountName of accountNames) {
160
+ let account = await getAccount(accountName, providedContext.accounts, providedContext.accounts[accountName]);
161
+ (resolvedAccounts as any)[accountName] = account;
162
+ }
163
+ }
164
+
165
+ const context = {
166
+ accounts: resolvedAccounts,
167
+ artifacts: providedContext.artifacts as Artifacts,
168
+ network: {
169
+ name: networkName,
170
+ saveDeployments,
171
+ tags,
172
+ },
173
+ };
174
+
175
+ const {deployments} = loadDeployments(config.deployments, context.network.name, false, chainId);
176
+
177
+ const namedAccounts: {[name: string]: EIP1193Account} = {};
178
+ const namedSigners: {[name: string]: NamedSigner} = {};
179
+ const addressSigners: {[name: `0x${string}`]: NamedSigner} = {};
180
+
181
+ for (const entry of Object.entries(resolvedAccounts)) {
182
+ const name = entry[0];
183
+ const {address, ...namedSigner} = entry[1];
184
+ namedAccounts[name] = address;
185
+ addressSigners[address] = namedSigner;
186
+ namedSigners[name] = namedSigner;
187
+ }
188
+
189
+ const perliminaryEnvironment = {
190
+ config,
191
+ deployments: deployments as Deployments,
192
+ accounts: namedAccounts as ResolvedNamedAccounts<NamedAccounts>,
193
+ signers: namedSigners as ResolvedNamedSigners<ResolvedNamedAccounts<NamedAccounts>>,
194
+ addressSigners: addressSigners,
195
+ artifacts: context.artifacts,
196
+ network: {
197
+ chainId,
198
+ name: context.network.name,
199
+ tags: context.network.tags,
200
+ provider,
201
+ },
202
+ };
203
+
204
+ function ensureDeploymentFolder(): string {
205
+ const folderPath = path.join(config.deployments, context.network.name);
206
+ fs.mkdirSync(folderPath, {recursive: true});
207
+ const chainIdFilepath = path.join(folderPath, '.chainId');
208
+ if (!fs.existsSync(chainIdFilepath)) {
209
+ fs.writeFileSync(chainIdFilepath, chainId);
210
+ }
211
+ return folderPath;
212
+ }
213
+
214
+ // const signer = {
215
+ // async sendTransaction(
216
+ // provider: EIP1193ProviderWithoutEvents,
217
+ // account: {
218
+ // addresss: EIP1193Account;
219
+ // config: unknown;
220
+ // },
221
+ // transaction: EIP1193TransactionEIP1193DATA
222
+ // ): Promise<EIP1193DATA> {
223
+ // return '0x';
224
+ // },
225
+ // };
226
+
227
+ // async function sendTransaction(transaction: EIP1193TransactionEIP1193DATA): Promise<EIP1193DATA> {
228
+ // return '0x';
229
+ // }
230
+
231
+ function get<TAbi extends Abi>(name: string): Deployment<TAbi> | undefined {
232
+ return deployments[name] as Deployment<TAbi> | undefined;
233
+ }
234
+
235
+ async function save<TAbi extends Abi>(name: string, deployment: Deployment<TAbi>): Promise<Deployment<TAbi>> {
236
+ deployments[name] = deployment;
237
+ if (context.network.saveDeployments) {
238
+ const folderPath = ensureDeploymentFolder();
239
+ fs.writeFileSync(`${folderPath}/${name}.json`, JSONToString(deployment, 2));
240
+ }
241
+ return deployment;
242
+ }
243
+
244
+ async function recoverTransactionsIfAny<TAbi extends Abi = Abi>(): Promise<void> {
245
+ const filepath = path.join(config.deployments, context.network.name, '.pending_transactions.json');
246
+ let existingPendingDeployments: {name: string; transaction: PendingDeployment<TAbi>}[];
247
+ try {
248
+ existingPendingDeployments = stringToJSON(fs.readFileSync(filepath, 'utf-8'));
249
+ } catch {
250
+ existingPendingDeployments = [];
251
+ }
252
+ if (existingPendingDeployments.length > 0) {
253
+ while (existingPendingDeployments.length > 0) {
254
+ const pendingTransaction = existingPendingDeployments.shift();
255
+ if (pendingTransaction) {
256
+ console.log(
257
+ `recovering ${pendingTransaction.name} with transaction ${pendingTransaction.transaction.txHash}`
258
+ );
259
+ await waitForTransactionAndSave(pendingTransaction.name, pendingTransaction.transaction);
260
+ console.log(`transaction ${pendingTransaction.transaction.txHash} complete`);
261
+ fs.writeFileSync(filepath, JSONToString(existingPendingDeployments, 2));
262
+ }
263
+ }
264
+ fs.rmSync(filepath);
265
+ }
266
+ }
267
+
268
+ async function saveTransaction<TAbi extends Abi = Abi>(name: string, transaction: PendingDeployment<TAbi>) {
269
+ if (context.network.saveDeployments) {
270
+ const folderPath = ensureDeploymentFolder();
271
+ const filepath = path.join(folderPath, '.pending_transactions.json');
272
+ let existingPendingDeployments: {name: string; transaction: PendingDeployment<TAbi>}[];
273
+ try {
274
+ existingPendingDeployments = stringToJSON(fs.readFileSync(filepath, 'utf-8'));
275
+ } catch {
276
+ existingPendingDeployments = [];
277
+ }
278
+ existingPendingDeployments.push({name, transaction});
279
+ fs.writeFileSync(filepath, JSONToString(existingPendingDeployments, 2));
280
+ }
281
+ return deployments;
282
+ }
283
+
284
+ async function deleteTransaction<TAbi extends Abi = Abi>(hash: string) {
285
+ if (context.network.saveDeployments) {
286
+ const filepath = path.join(config.deployments, context.network.name, '.pending_transactions.json');
287
+ let existingPendingDeployments: {name: string; transaction: PendingDeployment<TAbi>}[];
288
+ try {
289
+ existingPendingDeployments = stringToJSON(fs.readFileSync(filepath, 'utf-8'));
290
+ } catch {
291
+ existingPendingDeployments = [];
292
+ }
293
+ existingPendingDeployments = existingPendingDeployments.filter((v) => v.transaction.txHash !== hash);
294
+ if (existingPendingDeployments.length === 0) {
295
+ fs.rmSync(filepath);
296
+ } else {
297
+ fs.writeFileSync(filepath, JSONToString(existingPendingDeployments, 2));
298
+ }
299
+ }
300
+ }
301
+
302
+ async function exportDeploymentsAsTypes() {
303
+ const folderPath = './generated';
304
+ fs.mkdirSync(folderPath, {recursive: true});
305
+ fs.writeFileSync(`${folderPath}/deployments.ts`, `export default ${JSONToString(deployments, 2)} as const;`);
306
+ }
307
+
308
+ async function waitForTransactionAndSave<TAbi extends Abi = Abi>(
309
+ name: string,
310
+ pendingDeployment: PendingDeployment<TAbi>
311
+ ): Promise<Deployment<TAbi>> {
312
+ const receipt = await viemClient.waitForTransactionReceipt({
313
+ hash: pendingDeployment.txHash,
314
+ });
315
+
316
+ if (!receipt.contractAddress) {
317
+ throw new Error(`failed to deploy contract ${name}`);
318
+ }
319
+ const {abi, ...artifactObjectWithoutABI} = pendingDeployment.partialDeployment;
320
+
321
+ if (!artifactObjectWithoutABI.nonce) {
322
+ const transaction = await provider.request({
323
+ method: 'eth_getTransactionByHash',
324
+ params: [pendingDeployment.txHash],
325
+ });
326
+ if (transaction) {
327
+ artifactObjectWithoutABI.nonce = transaction.nonce;
328
+ artifactObjectWithoutABI.txOrigin = transaction.from;
329
+ }
330
+ }
331
+
332
+ // TODO options
333
+ for (const key of Object.keys(artifactObjectWithoutABI)) {
334
+ if (key.startsWith('_')) {
335
+ delete (artifactObjectWithoutABI as any)[key];
336
+ }
337
+ if (key === 'evm') {
338
+ const {gasEstimates} = artifactObjectWithoutABI.evm;
339
+ artifactObjectWithoutABI.evm = {
340
+ gasEstimates,
341
+ };
342
+ }
343
+ }
344
+
345
+ const deployment = {
346
+ address: receipt.contractAddress,
347
+ txHash: pendingDeployment.txHash,
348
+ abi,
349
+ ...artifactObjectWithoutABI,
350
+ };
351
+ return save(name, deployment);
352
+ }
353
+
354
+ async function saveWhilePending<TAbi extends Abi = Abi>(name: string, pendingDeployment: PendingDeployment<TAbi>) {
355
+ await saveTransaction<TAbi>(name, pendingDeployment);
356
+ const transaction = await provider.request({
357
+ method: 'eth_getTransactionByHash',
358
+ params: [pendingDeployment.txHash],
359
+ });
360
+
361
+ const deployment = waitForTransactionAndSave<TAbi>(
362
+ name,
363
+ transaction
364
+ ? {
365
+ ...pendingDeployment,
366
+ nonce: transaction.nonce,
367
+ txOrigin: transaction.from,
368
+ }
369
+ : pendingDeployment
370
+ );
371
+ await deleteTransaction(pendingDeployment.txHash);
372
+ return deployment;
373
+ }
374
+
375
+ let env: Environment<Artifacts, NamedAccounts, Deployments> = {
376
+ ...perliminaryEnvironment,
377
+ save,
378
+ saveWhilePending,
379
+ get,
380
+ };
381
+ for (const extension of (globalThis as any).extensions) {
382
+ env = extension(env);
383
+ }
384
+
385
+ return {
386
+ external: env,
387
+ internal: {
388
+ exportDeploymentsAsTypes,
389
+ recoverTransactionsIfAny,
390
+ },
391
+ };
392
+ }
@@ -0,0 +1,158 @@
1
+ import {
2
+ EIP1193Account,
3
+ EIP1193DATA,
4
+ EIP1193ProviderWithoutEvents,
5
+ EIP1193SignerProvider,
6
+ EIP1193TransactionEIP1193DATA,
7
+ EIP1193WalletProvider,
8
+ } from 'eip-1193';
9
+ import {Abi, Narrow} from 'abitype';
10
+ import type {DeployContractParameters} from 'viem/contract';
11
+ import type {Chain} from 'viem';
12
+
13
+ export type Libraries = {[libraryName: string]: EIP1193Account};
14
+
15
+ export type Deployment<TAbi extends Abi> = {
16
+ address: EIP1193Account;
17
+ abi: Narrow<TAbi>;
18
+ txHash: EIP1193DATA;
19
+ txOrigin?: EIP1193Account;
20
+ nonce?: EIP1193DATA;
21
+ bytecode: EIP1193DATA;
22
+ argsData: EIP1193DATA;
23
+ metadata: string;
24
+ libraries?: Libraries;
25
+ deployedBytecode?: EIP1193DATA;
26
+ linkReferences?: any;
27
+ deployedLinkReferences?: any;
28
+ devdoc?: any; // TODO type
29
+ evm?: any; // TODO type
30
+ storageLayout?: any; // TODO type
31
+ userdoc?: any; // TODO type
32
+ };
33
+
34
+ export type Artifact<TAbi extends Abi = Abi> = {
35
+ abi: TAbi;
36
+ bytecode: EIP1193DATA;
37
+ metadata: string;
38
+ deployedBytecode?: EIP1193DATA;
39
+ linkReferences?: any;
40
+ deployedLinkReferences?: any;
41
+ devdoc?: any; // TODO type
42
+ evm?: any; // TODO type
43
+ storageLayout?: any; // TODO type
44
+ userdoc?: any; // TODO type
45
+ };
46
+
47
+ export type AccountDefinition = EIP1193Account | string | number;
48
+
49
+ export type AccountType =
50
+ | AccountDefinition
51
+ | {
52
+ [networkOrChainId: string | number]: AccountDefinition;
53
+ };
54
+
55
+ export type ResolvedAccount = {
56
+ address: EIP1193Account;
57
+ } & NamedSigner;
58
+
59
+ export type UnknownDeployments = Record<string, Deployment<Abi>>;
60
+ export type UnknownArtifacts = {[name: string]: Artifact};
61
+ export type UnknownNamedAccounts = {
62
+ [name: string]: EIP1193Account;
63
+ };
64
+
65
+ export type UnresolvedUnknownNamedAccounts = {
66
+ [name: string]: AccountType;
67
+ };
68
+
69
+ export type ResolvedNamedAccounts<T extends UnresolvedUnknownNamedAccounts> = {
70
+ [Property in keyof T]: EIP1193Account;
71
+ };
72
+
73
+ export type NamedSigner =
74
+ | {type: 'signerOnly'; signer: EIP1193SignerProvider}
75
+ | {type: 'remote'; signer: EIP1193ProviderWithoutEvents}
76
+ | {type: 'wallet'; signer: EIP1193WalletProvider};
77
+
78
+ export type ResolvedNamedSigners<T extends UnknownNamedAccounts> = {
79
+ [Property in keyof T]: NamedSigner;
80
+ };
81
+
82
+ export type UnknownDeploymentsAcrossNetworks = Record<string, UnknownDeployments>;
83
+
84
+ export type Context<
85
+ Artifacts extends UnknownArtifacts = UnknownArtifacts,
86
+ NamedAccounts extends UnknownNamedAccounts = UnknownNamedAccounts
87
+ > = {
88
+ network: {
89
+ name: string;
90
+ tags: {[tag: string]: boolean};
91
+ saveDeployments: boolean;
92
+ };
93
+ accounts: NamedAccounts;
94
+ artifacts: Artifacts;
95
+ };
96
+
97
+ type BaseConfig = {
98
+ networkName?: string;
99
+ scripts?: string;
100
+ deployments?: string;
101
+
102
+ tags?: string[];
103
+ };
104
+
105
+ type ConfigForJSONRPC = BaseConfig & {
106
+ networkName: string;
107
+ nodeUrl: string;
108
+ };
109
+
110
+ type ConfigForEIP1193Provider = BaseConfig & {
111
+ provider: EIP1193ProviderWithoutEvents;
112
+ };
113
+
114
+ export type Config = ConfigForJSONRPC | ConfigForEIP1193Provider;
115
+
116
+ export type ResolvedConfig = Config & {deployments: string; scripts: string; tags: string[]; networkName: string};
117
+
118
+ export interface Environment<
119
+ Artifacts extends UnknownArtifacts = UnknownArtifacts,
120
+ NamedAccounts extends UnresolvedUnknownNamedAccounts = UnresolvedUnknownNamedAccounts,
121
+ Deployments extends UnknownDeployments = UnknownDeployments
122
+ > {
123
+ config: ResolvedConfig;
124
+ network: {
125
+ chainId: string;
126
+ name: string;
127
+ tags: {[tag: string]: boolean};
128
+ provider: EIP1193ProviderWithoutEvents;
129
+ };
130
+ deployments: Deployments;
131
+ accounts: ResolvedNamedAccounts<NamedAccounts>;
132
+ signers: ResolvedNamedSigners<ResolvedNamedAccounts<NamedAccounts>>;
133
+ addressSigners: {[name: `0x${string}`]: NamedSigner};
134
+ artifacts: Artifacts;
135
+ save<TAbi extends Abi = Abi>(name: string, deployment: Deployment<TAbi>): Promise<Deployment<TAbi>>;
136
+ saveWhilePending<TAbi extends Abi = Abi>(
137
+ name: string,
138
+ pendingDeployment: PendingDeployment<TAbi>
139
+ ): Promise<Deployment<TAbi>>;
140
+ get<TAbi extends Abi>(name: string): Deployment<TAbi> | undefined;
141
+ }
142
+
143
+ export type DeploymentConstruction<TAbi extends Abi, TChain extends Chain = Chain> = Omit<
144
+ DeployContractParameters<TAbi, TChain>,
145
+ 'bytecode' | 'account' | 'abi'
146
+ > & {account: string | EIP1193Account; artifact: string | Artifact<TAbi>};
147
+
148
+ export type PartialDeployment<TAbi extends Abi = Abi> = Artifact<TAbi> & {
149
+ txOrigin?: EIP1193Account;
150
+ nonce?: EIP1193DATA;
151
+ argsData: EIP1193DATA;
152
+ libraries?: Libraries;
153
+ };
154
+
155
+ export type PendingDeployment<TAbi extends Abi = Abi> = DeploymentConstruction<TAbi> & {
156
+ txHash: `0x${string}`;
157
+ partialDeployment: PartialDeployment<TAbi>;
158
+ };