ethershell 0.1.4-beta.0 → 0.1.6-beta.0

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/bin/cli.js CHANGED
@@ -17,7 +17,8 @@ import {
17
17
  compilerOptions,
18
18
  getCompilerOptions,
19
19
  compile,
20
- changeCompPath
20
+ changeCompPath,
21
+ flatten
21
22
  } from '../src/services/build.js';
22
23
  import { set, get, getDefault } from '../src/services/network.js';
23
24
  import { deleteDirectory } from '../src/services/files.js';
@@ -37,7 +38,7 @@ import {
37
38
 
38
39
  import { deploy, add } from '../src/services/addContracts.js';
39
40
  import { getContracts } from '../src/services/contracts.js';
40
- import { getConfigInfo, getDefaultAccount } from '../src/services/config.js';
41
+ import { getConfigInfo, getDefaultAccount } from '../src/services/configSync.js';
41
42
 
42
43
  /**
43
44
  * REPL instance for EtherShell interactive environment
@@ -66,6 +67,7 @@ r.context.compInfo = getCompilerOptions;
66
67
  r.context.compOpts = compilerOptions;
67
68
  r.context.compPath = changeCompPath;
68
69
  r.context.build = compile;
70
+ r.context.flatten = flatten;
69
71
 
70
72
  // Config commands
71
73
  r.context.configInfo = getConfigInfo;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ethershell",
3
3
  "license": "MIT",
4
- "version": "0.1.4-beta.0",
4
+ "version": "0.1.6-beta.0",
5
5
  "description": "Interactive JavaScript console for Ethereum smart contract management",
6
6
  "author": "Alireza Kiakojouri (alirezaethdev@gmail.com)",
7
7
  "repository": {
@@ -8,11 +8,13 @@
8
8
 
9
9
  import { ethers } from 'ethers';
10
10
  import fs from 'fs';
11
- import { provider } from './config.js';
11
+ import {
12
+ provider,
13
+ configFile
14
+ } from './configSync.js';
12
15
  import { allAccounts } from './wallet.js';
13
16
  import { LocalStorage } from 'node-localstorage';
14
17
  import { r } from '../../bin/cli.js';
15
- import { configFile } from './config.js';
16
18
  import { createContractProxy } from '../utils/contractProxy.js';
17
19
  import { eventOf } from '../utils/event.js';
18
20
 
@@ -20,7 +20,7 @@ import {
20
20
  configPath,
21
21
  configFile,
22
22
  compConfig
23
- } from './config.js';
23
+ } from './configSync.js';
24
24
 
25
25
  /**
26
26
  * Update the Solidity compiler to a specific version
@@ -39,6 +39,7 @@ export async function updateCompiler(version){
39
39
  // Update config file
40
40
  configFile.compiler.version = extractLoadableVersion(compConfig.currentSolcInstance.version());
41
41
  fs.writeFileSync(configPath, JSON.stringify(configFile, null, 2));
42
+
42
43
  } catch(err) {
43
44
  console.error(err);
44
45
  }
@@ -155,6 +156,37 @@ export function compile(fullPath, selectedContracts, buildPath){
155
156
  console.log(`Contract compiled into ${path.resolve(buildPath)}`);
156
157
  }
157
158
 
159
+ ////////////////////
160
+ // Generate aggregated ABI after compilation
161
+ const abisDir = path.join(buildPath, 'abis');
162
+ const aggregatedAbiPath = path.join(buildPath, 'aggregated.abi.json');
163
+
164
+ if (fs.existsSync(abisDir)) {
165
+ const files = fs.readdirSync(abisDir).filter(f => f.endsWith('.abi.json'));
166
+
167
+ const aggregated = [];
168
+ for (const file of files) {
169
+ const p = path.join(abisDir, file);
170
+ try {
171
+ const abi = JSON.parse(fs.readFileSync(p, 'utf8'));
172
+ if (Array.isArray(abi)) {
173
+ aggregated.push(...abi);
174
+ } else {
175
+ console.warn(`ABI file ${file} is not an array, skipping from aggregation`);
176
+ }
177
+ } catch (e) {
178
+ console.warn(`Failed to read ABI file ${file} for aggregation: ${e.message}`);
179
+ }
180
+ }
181
+
182
+ fs.writeFileSync(aggregatedAbiPath, JSON.stringify(aggregated, null, 2));
183
+ console.log('Aggregated ABI generated at', path.resolve(aggregatedAbiPath));
184
+ } else {
185
+ console.warn('No ABI directory found, aggregated ABI not generated.');
186
+ }
187
+
188
+ ////////////////////
189
+
158
190
  // Generate TypeScript types
159
191
  const typesOutputPath = path.join(buildPath, 'types');
160
192
  generateAllTypes(buildPath, typesOutputPath);
@@ -172,4 +204,114 @@ export function changeCompPath(newPath) {
172
204
  compConfig.compilePath = newPath;
173
205
  configFile.compiler.compilePath = compConfig.compilePath;
174
206
  fs.writeFileSync(configPath, JSON.stringify(configFile, null, 2));
175
- }
207
+ }
208
+
209
+ /**
210
+ * Flatten a Solidity contract and its imports into a single file.
211
+ *
212
+ * @param {string} fullPath - Path to the root .sol file.
213
+ * @param {string} [outFile] - Optional path for the flattened output file.
214
+ * Defaults to `<dir>/<name>.flattened.sol`.
215
+ */
216
+ export function flatten(fullPath, outFile) {
217
+ if (!fullPath) {
218
+ throw new Error('flatten(): fullPath to the root Solidity file is required.');
219
+ }
220
+
221
+ const entryPath = path.resolve(fullPath);
222
+ if (!fs.existsSync(entryPath)) {
223
+ throw new Error(`flatten(): entry file does not exist: ${entryPath}`);
224
+ }
225
+
226
+ const visited = new Set();
227
+ const pieces = [];
228
+
229
+ let pragmaLine = null;
230
+ let hasSpdx = false;
231
+
232
+ function processFile(absPath, logicalName) {
233
+ if (visited.has(absPath)) return;
234
+ visited.add(absPath);
235
+
236
+ const raw = fs.readFileSync(absPath, 'utf8');
237
+
238
+ const lines = raw.split(/\r?\n/);
239
+
240
+ // Capture pragma and SPDX once from the entry or first file
241
+ for (const line of lines) {
242
+ const trimmed = line.trim();
243
+ if (!pragmaLine && trimmed.startsWith('pragma solidity')) {
244
+ pragmaLine = trimmed.replace(/[; ]+$/, ';');
245
+ }
246
+ if (!hasSpdx && trimmed.startsWith('// SPDX-License-Identifier:')) {
247
+ hasSpdx = true;
248
+ }
249
+ }
250
+
251
+ const imports = parseImports(raw);
252
+ for (const importPath of imports) {
253
+ const resolved = resolveImportPath(importPath, path.dirname(absPath));
254
+ processFile(resolved, importPath);
255
+ }
256
+
257
+ // Strip SPDX, pragma, and import lines from the body
258
+ const body = lines
259
+ .filter((line) => {
260
+ const trimmed = line.trim();
261
+ if (trimmed.startsWith('// SPDX-License-Identifier:')) return false;
262
+ if (trimmed.startsWith('pragma solidity')) return false;
263
+ if (trimmed.startsWith('import ')) return false;
264
+ return true;
265
+ })
266
+ .join('\n');
267
+
268
+ pieces.push(`\n\n// File: ${logicalName}\n\n${body}\n`);
269
+ }
270
+
271
+ const logicalName = path.basename(entryPath);
272
+ processFile(entryPath, logicalName);
273
+
274
+ let header = '';
275
+ if (hasSpdx) {
276
+ // Multiple different licenses may be mixed; explicit about it
277
+ header += '// SPDX-License-Identifier: MIXED\n';
278
+ }
279
+ if (pragmaLine) {
280
+ header += `${pragmaLine}\n\n`;
281
+ }
282
+
283
+ const flattened = `${header}${pieces.join('')}`;
284
+
285
+ const defaultOut =
286
+ outFile ||
287
+ path.join(
288
+ path.dirname(entryPath),
289
+ `${path.basename(entryPath, '.sol')}.flattened.sol`,
290
+ );
291
+
292
+ fs.writeFileSync(defaultOut, flattened);
293
+ console.log('Flattened contract written to', path.resolve(defaultOut));
294
+ }
295
+
296
+ /*============================= HELPER ==============================*/
297
+ // --- FLATTENING UTILITIES ---
298
+
299
+ function resolveImportPath(importPath, fromDir) {
300
+ if (importPath.startsWith('./') || importPath.startsWith('../')) {
301
+ return path.resolve(fromDir, importPath);
302
+ }
303
+ // Node-style imports from node_modules
304
+ return path.resolve(process.cwd(), 'node_modules', importPath);
305
+ }
306
+
307
+ function parseImports(content) {
308
+ const imports = [];
309
+ const importRegex =
310
+ /import\s+(?:(?:["']([^"']+)["'])|(?:.*?\sfrom\s+["']([^"']+)["']))\s*;/g;
311
+ let match;
312
+ while ((match = importRegex.exec(content))) {
313
+ const importPath = match[1] || match[2];
314
+ if (importPath) imports.push(importPath);
315
+ }
316
+ return imports;
317
+ }
@@ -96,7 +96,7 @@ if(storedUrl) {
96
96
  configFile.providerEndpoint = defaultUrl;
97
97
  }
98
98
 
99
- // 3) Set Compiler to Memeory:
99
+ // 3) Set Compiler to Memory:
100
100
  // Initialize global configuration of compiler
101
101
  if(storedCompConfig){
102
102
  configFile.compiler = storedCompConfig.compiler;
@@ -122,6 +122,7 @@ if(storedCompConfig){
122
122
  configFile.compiler.compilePath = compConfig.compilePath;
123
123
  }
124
124
 
125
+
125
126
  // 4) Set Default Account:
126
127
  // Set the default account from stored wallets
127
128
  const defWallet = configObj.defaultWallet;
@@ -12,7 +12,7 @@ import {
12
12
  provider,
13
13
  setProvider,
14
14
  defaultUrl
15
- } from './config.js';
15
+ } from './configSync.js';
16
16
 
17
17
  /**
18
18
  * Set a new network provider
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { ethers } from 'ethers';
10
- import { provider } from './config.js';
10
+ import { provider } from './configSync.js';
11
11
  import {
12
12
  deleteByIndex,
13
13
  deleteByIndexArr,
@@ -6,9 +6,9 @@
6
6
  */
7
7
 
8
8
  import { allAccounts, accounts, hdAccounts } from '../services/wallet.js';
9
- import { provider } from '../services/config.js';
9
+ import { provider } from '../services/configSync.js';
10
10
  import fs from 'fs';
11
- import { configFile, configPath } from '../services/config.js';
11
+ import { configFile, configPath } from '../services/configSync.js';
12
12
  import { serializeBigInts } from './serialize.js';
13
13
 
14
14
  /**
@@ -81,32 +81,32 @@ export function build(fullPath, selectedContracts, buildPath){
81
81
 
82
82
  // Get the directory containing the contract
83
83
  const contractDir = path.dirname(fullPath);
84
- const filename = path.basename(fullPath, '.sol');
85
- const source = fs.readFileSync(fullPath, 'utf8');
86
-
84
+ const filename = path.basename(fullPath); // keep extension here
85
+ const basename = path.basename(fullPath, '.sol');
86
+
87
+ // Build full standard JSON input, including all imports
88
+ const sources = collectSourcesForStandardJson(fullPath, filename);
89
+
87
90
  const input = {
88
91
  language: 'Solidity',
89
- sources: {
90
- [`${filename}`]: {
91
- content: source,
92
- },
93
- },
92
+ sources,
94
93
  settings: {
95
94
  outputSelection: {
96
95
  '*': {
97
96
  '*': ['*'],
98
97
  },
99
98
  },
100
- }
99
+ },
101
100
  };
102
101
 
103
102
  // Apply global compiler configuration
104
103
  if (compilerConfig.optimizer) {
105
104
  input.settings.optimizer = {
106
105
  enabled: true,
107
- runs: compilerConfig.optimizerRuns
106
+ runs: compilerConfig.optimizerRuns,
108
107
  };
109
108
  }
109
+
110
110
  if (compilerConfig.viaIR) {
111
111
  input.settings.viaIR = true;
112
112
  }
@@ -127,16 +127,20 @@ export function build(fullPath, selectedContracts, buildPath){
127
127
  }
128
128
  }
129
129
 
130
+
131
+
130
132
  // Generate sub-paths of build
131
- const artifacts = path.join(buildPath, 'artifacts');
132
- const abis = path.join(buildPath, 'abis');
133
- const bytecode = path.join(buildPath, 'bytecode');
134
- const metadata = path.join(buildPath, 'metadata');
133
+ const artifacts = path.join(buildPath, 'artifacts');
134
+ const abis = path.join(buildPath, 'abis');
135
+ const bytecode = path.join(buildPath, 'bytecode');
136
+ const metadata = path.join(buildPath, 'metadata');
137
+ const standardJsonDir = path.join(buildPath, 'standard-json');
135
138
  const subPaths = [
136
139
  artifacts,
137
140
  abis,
138
141
  bytecode,
139
- metadata
142
+ metadata,
143
+ standardJsonDir
140
144
  ];
141
145
 
142
146
  // Ensure all sub-paths exist
@@ -161,6 +165,9 @@ export function build(fullPath, selectedContracts, buildPath){
161
165
  // Save on metadata
162
166
  const metadataPath = path.join(metadata, `${contractName}.metadata.json`);
163
167
  fs.writeFileSync(metadataPath, JSON.stringify(contractsData.metadata, null, 2));
168
+ // Save standard JSON input for this entry file
169
+ const standardJsonPath = path.join(standardJsonDir,`${basename}.standard-input.json`);
170
+ fs.writeFileSync(standardJsonPath, JSON.stringify(input, null, 2));
164
171
  // Store abis and bytecode on local storage
165
172
  localStorage.setItem(`${contractName}_abi`, abisPath);
166
173
  localStorage.setItem(`${contractName}_bytecode`, bytecodePath);
@@ -182,3 +189,50 @@ export function extractLoadableVersion(fullVersion) {
182
189
  }
183
190
  return `v${match[1]}+commit.${match[2]}`;
184
191
  }
192
+
193
+ /*=========================== HELPER =============================*/
194
+ function resolveImportPath(importPath, fromDir) {
195
+ if (importPath.startsWith('./') || importPath.startsWith('../')) {
196
+ return path.resolve(fromDir, importPath);
197
+ }
198
+
199
+ // Node-style import, resolve from node_modules
200
+ // e.g. '@openzeppelin/contracts/token/ERC20/ERC20.sol'
201
+ return path.resolve(process.cwd(), 'node_modules', importPath);
202
+ }
203
+
204
+ function collectSourcesForStandardJson(entryPath, logicalEntryName) {
205
+ const visited = new Set();
206
+ const sources = {};
207
+
208
+ function processFile(absPath, logicalName) {
209
+ if (visited.has(absPath)) return;
210
+ visited.add(absPath);
211
+
212
+ if (!fs.existsSync(absPath)) {
213
+ console.warn(`Warning: cannot resolve import "${logicalName}" at path "${absPath}"`);
214
+ return;
215
+ }
216
+
217
+ const content = fs.readFileSync(absPath, 'utf8');
218
+ sources[logicalName] = { content };
219
+
220
+ const importRegex =
221
+ /import\s+(?:(?:["']([^"']+)["'])|(?:.*?\sfrom\s+["']([^"']+)["']))\s*;/g;
222
+
223
+ let match;
224
+ while ((match = importRegex.exec(content))) {
225
+ const importPath = match[1] || match[2];
226
+ if (!importPath) continue;
227
+
228
+ const resolved = resolveImportPath(importPath, path.dirname(absPath));
229
+ // Use the import string itself as the logical source key,
230
+ // which is what solc/Etherscan expect
231
+ processFile(resolved, importPath);
232
+ }
233
+ }
234
+
235
+ processFile(entryPath, logicalEntryName);
236
+ return sources;
237
+ }
238
+
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  configFile,
3
3
  configPath
4
- } from '../services/config.js';
4
+ } from '../services/configSync.js';
5
5
  import fs from 'fs';
6
6
 
7
7
  export function changeProvider(url) {