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 +4 -2
- package/package.json +1 -1
- package/src/services/addContracts.js +4 -2
- package/src/services/build.js +144 -2
- package/src/services/{config.js → configSync.js} +2 -1
- package/src/services/network.js +1 -1
- package/src/services/wallet.js +1 -1
- package/src/utils/accounter.js +2 -2
- package/src/utils/builder.js +69 -15
- package/src/utils/configFileUpdate.js +1 -1
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/
|
|
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
|
+
"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 {
|
|
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
|
|
package/src/services/build.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
configPath,
|
|
21
21
|
configFile,
|
|
22
22
|
compConfig
|
|
23
|
-
} from './
|
|
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
|
|
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;
|
package/src/services/network.js
CHANGED
package/src/services/wallet.js
CHANGED
package/src/utils/accounter.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { allAccounts, accounts, hdAccounts } from '../services/wallet.js';
|
|
9
|
-
import { provider } from '../services/
|
|
9
|
+
import { provider } from '../services/configSync.js';
|
|
10
10
|
import fs from 'fs';
|
|
11
|
-
import { configFile, configPath } from '../services/
|
|
11
|
+
import { configFile, configPath } from '../services/configSync.js';
|
|
12
12
|
import { serializeBigInts } from './serialize.js';
|
|
13
13
|
|
|
14
14
|
/**
|
package/src/utils/builder.js
CHANGED
|
@@ -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
|
|
85
|
-
const
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
|