sol2uml 2.5.21 → 2.5.22

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/README.md CHANGED
@@ -57,9 +57,9 @@ Options:
57
57
  -f, --outputFormat <value> output file format. (choices: "svg", "png", "dot", "all", default: "svg")
58
58
  -o, --outputFileName <value> output file name
59
59
  -i, --ignoreFilesOrFolders <names> comma-separated list of files or folders to ignore
60
- -n, --network <network> Ethereum network which maps to a blockchain explorer (choices: "mainnet", "holesky", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo", "scroll", "base", "sonic", default: "mainnet", env: ETH_NETWORK)
60
+ -n, --network <network> Name or chain id of the blockchain explorer. A name like `ethereum` or `base` will map to a chain id, eg 1 or 8453. Alternatively, use an integer of the chain id. Supported names: ethereum, sepolia, holesky, hoodi, arbitrum, optimism, polygon, avalanche, base, bsc, crono, fantom, sonic, gnosis, moonbeam, celo, scroll, linea, blast, berachain, zksync (default: "ethereum", env: ETH_NETWORK)
61
61
  -e, --explorerUrl <url> Override the `network` option with a custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api (env: EXPLORER_URL)
62
- -k, --apiKey <key> Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan, SonicScan or SnowTrace API key (env: SCAN_API_KEY)
62
+ -k, --apiKey <key> Blockchain explorer API key. (env: SCAN_API_KEY)
63
63
  -bc, --backColor <color> Canvas background color. "none" will use a transparent canvas. (default: "white")
64
64
  -sc, --shapeColor <color> Basic drawing color for graphics, not text (default: "black")
65
65
  -fc, --fillColor <color> Color used to fill the background of a node (default: "gray95")
@@ -81,7 +81,7 @@ Commands:
81
81
  3. File imports are commented out.
82
82
  4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier".
83
83
  5. Contract dependencies are analysed so the files are merged in an order that will compile.
84
- diff [options] <addressA> <fileFoldersAddress> Compare verified Solidity code to another verified contract, a local file or local source files.
84
+ diff [options] <addressA> <fileFoldersAddress> Compare verified contract code on Etherscan-like explorers to another verified contract, a local file or multiple local files.
85
85
 
86
86
  The results show the comparison of contract A to B.
87
87
  The green sections are additions to contract B that are not in contract A.
@@ -106,7 +106,7 @@ Arguments:
106
106
  sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9
107
107
 
108
108
  Options:
109
- -b, --baseContractNames <names> only output contracts connected to these comma separated base contract names
109
+ -b, --baseContractNames <names> only output contracts connected to these comma-separated base contract names
110
110
  -d, --depth <value> depth of connected classes to the base contracts. 1 will only show directly connected contracts, interfaces, libraries, structs and enums. (default: all)
111
111
  -c, --clusterFolders cluster contracts into source folders (default: false)
112
112
  -hv, --hideVariables hide variables from contracts, interfaces, structs and enums (default: false)
@@ -139,8 +139,8 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
139
139
  Arguments:
140
140
  fileFolderAddress file name, folder(s) or contract address.
141
141
  When a folder is used, all *.sol files in that folder and all sub folders are used.
142
- A comma-separated list of files and folders can also be used. For example
143
- sol2uml contracts,node_modules/openzeppelin-solidity
142
+ A comma-separated list of files and folders can also be used. For example,
143
+ sol2uml contracts,node_modules/@openzeppelin
144
144
  If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example
145
145
  sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9
146
146
 
@@ -203,13 +203,12 @@ Options:
203
203
  -s, --summary Only show a summary of the file differences (default: false)
204
204
  -af --aFile <value> Limit code compare to contract A source file with the full path and extension as displayed in the file summary (default: compares all source files)
205
205
  -bf --bFile <value> Contract B source file with the full path and extension as displayed in the file summary. Used if aFile is specified and the source file has been renamed (default: aFile if specified)
206
- -bn, --bNetwork <network> Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option) (choices: "mainnet", "holesky", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo", "base")
207
- -be, --bExplorerUrl <url> Override the `bNetwork` option with custom blockchain explorer API URL for contract B if on a different blockchain to contract A. Contract A uses the `explorerUrl` (default: value of `explorerUrl` option)
208
- -bk, --bApiKey <key> Blockchain explorer API key for contract B if on a different blockchain to contract A. Contract A uses the `apiKey` option (default: value of `apiKey` option)
206
+ -bn, --bNetwork <network> Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option) (choices: "ethereum", "sepolia", "holesky", "hoodi", "arbitrum", "optimism", "polygon", "avalanche", "base", "bsc", "crono", "fantom", "sonic", "gnosis", "moonbeam", "celo", "scroll", "linea", "blast", "berachain", "zksync")
209
207
  --flatten Flatten into a single file before comparing. Only works when comparing two verified contracts, not to local files (default: false)
210
208
  --saveFiles Save the flattened contract code to the filesystem when using the `flatten` option. The file names will be the contract address with a .sol extension (default: false)
211
209
  -l, --lineBuffer <value> Minimum number of lines before and after changes (default: 4)
212
210
  -h, --help display help for command
211
+
213
212
  ```
214
213
 
215
214
  ## UML Class diagram examples
@@ -13,16 +13,23 @@ const findAssociatedClass = (association, sourceUmlClass, umlClasses, searchedAb
13
13
  // If a link was found
14
14
  if (umlClass)
15
15
  return umlClass;
16
- // Could not find association so now need to recursively look at imports of imports
17
- // add to already recursively processed files to avoid getting stuck in circular imports
18
- searchedAbsolutePaths.push(sourceUmlClass.absolutePath);
19
- const importedType = findChainedImport(association, sourceUmlClass, umlClasses, searchedAbsolutePaths);
20
- if (importedType)
21
- return importedType;
22
- // Still could not find association so now need to recursively look for inherited types
23
- const inheritedType = findInheritedType(association, sourceUmlClass, umlClasses);
24
- if (inheritedType)
25
- return inheritedType;
16
+ // // Could not find association so now need to recursively look at imports of imports
17
+ // // add to already recursively processed files to avoid getting stuck in circular imports
18
+ // searchedAbsolutePaths.push(sourceUmlClass.absolutePath)
19
+ // const importedType = findChainedImport(
20
+ // association,
21
+ // sourceUmlClass,
22
+ // umlClasses,
23
+ // searchedAbsolutePaths,
24
+ // )
25
+ // if (importedType) return importedType
26
+ // // Still could not find association so now need to recursively look for inherited types
27
+ // const inheritedType = findInheritedType(
28
+ // association,
29
+ // sourceUmlClass,
30
+ // umlClasses,
31
+ // )
32
+ // if (inheritedType) return inheritedType
26
33
  return undefined;
27
34
  };
28
35
  exports.findAssociatedClass = findAssociatedClass;
@@ -109,39 +116,62 @@ const findInheritedType = (association, sourceUmlClass, umlClasses) => {
109
116
  }
110
117
  return undefined;
111
118
  };
112
- const findChainedImport = (association, sourceUmlClass, umlClasses, searchedRelativePaths) => {
113
- // Get all valid imports. That is, imports that do not explicitly import contracts or interfaces
114
- // or explicitly import the source class
115
- const imports = sourceUmlClass.imports.filter((i) => i.classNames.length === 0 ||
116
- i.classNames.some((cn) => (association.targetUmlClassName === cn.className &&
117
- !cn.alias) ||
118
- association.targetUmlClassName === cn.alias));
119
- // For each import
120
- for (const importDetail of imports) {
121
- // Find a class with the same absolute path as the import so we can get the new imports
122
- const newSourceUmlClass = umlClasses.find((c) => c.absolutePath === importDetail.absolutePath);
123
- if (!newSourceUmlClass) {
124
- // Could not find a class in the import file so just move onto the next loop
125
- continue;
126
- }
127
- // Avoid circular imports
128
- if (searchedRelativePaths.includes(newSourceUmlClass.absolutePath)) {
129
- // Have already recursively looked for imports of imports in this file
130
- continue;
131
- }
132
- // find class linked to the association without aliased imports
133
- const umlClass = (0, exports.findAssociatedClass)(association, newSourceUmlClass, umlClasses, searchedRelativePaths);
134
- if (umlClass)
135
- return umlClass;
136
- // find all aliased imports
137
- const aliasedImports = importDetail.classNames.filter((cn) => cn.alias);
138
- // For each aliased import
139
- for (const aliasedImport of aliasedImports) {
140
- const umlClass = (0, exports.findAssociatedClass)({ ...association, targetUmlClassName: aliasedImport.className }, newSourceUmlClass, umlClasses, searchedRelativePaths);
141
- if (umlClass)
142
- return umlClass;
143
- }
144
- }
145
- return undefined;
146
- };
119
+ // const findChainedImport = (
120
+ // association: Association,
121
+ // sourceUmlClass: UmlClass,
122
+ // umlClasses: readonly UmlClass[],
123
+ // searchedRelativePaths: string[],
124
+ // ): UmlClass | undefined => {
125
+ // // Get all valid imports. That is, imports that do not explicitly import contracts or interfaces
126
+ // // or explicitly import the source class
127
+ // const imports = sourceUmlClass.imports.filter(
128
+ // (i) =>
129
+ // i.classNames.length === 0 ||
130
+ // i.classNames.some(
131
+ // (cn) =>
132
+ // (association.targetUmlClassName === cn.className &&
133
+ // !cn.alias) ||
134
+ // association.targetUmlClassName === cn.alias,
135
+ // ),
136
+ // )
137
+ // // For each import
138
+ // for (const importDetail of imports) {
139
+ // // Find a class with the same absolute path as the import so we can get the new imports
140
+ // const newSourceUmlClass = umlClasses.find(
141
+ // (c) => c.absolutePath === importDetail.absolutePath,
142
+ // )
143
+ // if (!newSourceUmlClass) {
144
+ // // Could not find a class in the import file so just move onto the next loop
145
+ // continue
146
+ // }
147
+ // // Avoid circular imports
148
+ // if (searchedRelativePaths.includes(newSourceUmlClass.absolutePath)) {
149
+ // // Have already recursively looked for imports of imports in this file
150
+ // continue
151
+ // }
152
+ //
153
+ // // find class linked to the association without aliased imports
154
+ // const umlClass = findAssociatedClass(
155
+ // association,
156
+ // newSourceUmlClass,
157
+ // umlClasses,
158
+ // searchedRelativePaths,
159
+ // )
160
+ // if (umlClass) return umlClass
161
+ //
162
+ // // find all aliased imports
163
+ // const aliasedImports = importDetail.classNames.filter((cn) => cn.alias)
164
+ // // For each aliased import
165
+ // for (const aliasedImport of aliasedImports) {
166
+ // const umlClass = findAssociatedClass(
167
+ // { ...association, targetUmlClassName: aliasedImport.className },
168
+ // newSourceUmlClass,
169
+ // umlClasses,
170
+ // searchedRelativePaths,
171
+ // )
172
+ // if (umlClass) return umlClass
173
+ // }
174
+ // }
175
+ // return undefined
176
+ // }
147
177
  //# sourceMappingURL=associations.js.map
@@ -15,15 +15,26 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.renameFile = exports.convertAST2UmlClasses = void 0;
36
+ exports.renameFile = void 0;
37
+ exports.convertAST2UmlClasses = convertAST2UmlClasses;
27
38
  const path = __importStar(require("path"));
28
39
  const path_1 = require("path");
29
40
  const umlClass_1 = require("./umlClass");
@@ -48,7 +59,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
48
59
  name: childNode.name,
49
60
  absolutePath: filesystem
50
61
  ? path.resolve(relativePath) // resolve the absolute path
51
- : relativePath,
62
+ : relativePath, // from Etherscan so don't resolve
52
63
  relativePath,
53
64
  });
54
65
  umlClasses.push(umlClass);
@@ -62,7 +73,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
62
73
  stereotype: umlClass_1.ClassStereotype.Struct,
63
74
  absolutePath: filesystem
64
75
  ? path.resolve(relativePath) // resolve the absolute path
65
- : relativePath,
76
+ : relativePath, // from Etherscan so don't resolve
66
77
  relativePath,
67
78
  });
68
79
  parseStructDefinition(childNode, umlClass);
@@ -76,7 +87,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
76
87
  stereotype: umlClass_1.ClassStereotype.Enum,
77
88
  absolutePath: filesystem
78
89
  ? path.resolve(relativePath) // resolve the absolute path
79
- : relativePath,
90
+ : relativePath, // from Etherscan so don't resolve
80
91
  relativePath,
81
92
  });
82
93
  debug(`Added enum ${umlClass.name}`);
@@ -143,7 +154,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
143
154
  stereotype: umlClass_1.ClassStereotype.Constant,
144
155
  absolutePath: filesystem
145
156
  ? path.resolve(relativePath) // resolve the absolute path
146
- : relativePath,
157
+ : relativePath, // from Etherscan so don't resolve
147
158
  relativePath,
148
159
  attributes: [
149
160
  {
@@ -181,7 +192,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
181
192
  stereotype: umlClass_1.ClassStereotype.Import,
182
193
  absolutePath: filesystem
183
194
  ? path.resolve(relativePath) // resolve the absolute path
184
- : relativePath,
195
+ : relativePath, // from Etherscan so don't resolve
185
196
  relativePath,
186
197
  });
187
198
  importUmlClass.imports = imports;
@@ -189,7 +200,6 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
189
200
  }
190
201
  return umlClasses;
191
202
  }
192
- exports.convertAST2UmlClasses = convertAST2UmlClasses;
193
203
  /**
194
204
  * Parse struct definition for UML attributes and associations.
195
205
  * @param node defined in ASTNode as `StructDefinition`
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addAssociationsToDot = exports.convertUmlClasses2Dot = void 0;
3
+ exports.convertUmlClasses2Dot = convertUmlClasses2Dot;
4
+ exports.addAssociationsToDot = addAssociationsToDot;
4
5
  const path_1 = require("path");
5
6
  const converterClass2Dot_1 = require("./converterClass2Dot");
6
7
  const umlClass_1 = require("./umlClass");
@@ -49,7 +50,6 @@ label="${codeFolder}"`;
49
50
  debug(dotString);
50
51
  return dotString;
51
52
  }
52
- exports.convertUmlClasses2Dot = convertUmlClasses2Dot;
53
53
  let subGraphCount = 0;
54
54
  function getSubGraphName(clusterFolders = false) {
55
55
  if (clusterFolders) {
@@ -104,7 +104,6 @@ function addAssociationsToDot(umlClasses, classOptions = {}) {
104
104
  }
105
105
  return dotString;
106
106
  }
107
- exports.addAssociationsToDot = addAssociationsToDot;
108
107
  function addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions = {}) {
109
108
  // do not include library or interface associations if hidden
110
109
  // Or associations to Structs, Enums or Constants if they are hidden
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.convertStorage2Dot = exports.convertStorages2Dot = void 0;
3
+ exports.convertStorages2Dot = void 0;
4
+ exports.convertStorage2Dot = convertStorage2Dot;
4
5
  const converterClasses2Storage_1 = require("./converterClasses2Storage");
5
6
  const umlClass_1 = require("./umlClass");
6
7
  const formatters_1 = require("./utils/formatters");
@@ -113,7 +114,6 @@ function convertStorage2Dot(storageSection, dotString, options) {
113
114
  dotString += '}}"]\n';
114
115
  return dotString;
115
116
  }
116
- exports.convertStorage2Dot = convertStorage2Dot;
117
117
  const dotVariable = (variable, contractName) => {
118
118
  const port = variable.referenceSectionId !== undefined ? `<${variable.id}>` : '';
119
119
  const contractNamePrefix = variable.contractName !== contractName
@@ -4,13 +4,14 @@ export interface Remapping {
4
4
  from: RegExp;
5
5
  to: string;
6
6
  }
7
- export declare const networks: readonly ["mainnet", "holesky", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo", "scroll", "base", "sonic"];
7
+ export declare const networks: readonly ["ethereum", "sepolia", "holesky", "hoodi", "arbitrum", "optimism", "polygon", "avalanche", "base", "bsc", "crono", "fantom", "sonic", "gnosis", "moonbeam", "celo", "scroll", "linea", "blast", "berachain", "zksync"];
8
8
  export type Network = (typeof networks)[number];
9
+ export declare const setChainId: (network: string) => number;
9
10
  export declare class EtherscanParser {
10
- protected apikey: string;
11
+ protected apiKey?: string;
11
12
  network: Network;
12
13
  readonly url: string;
13
- constructor(apikey?: string, network?: Network, url?: string);
14
+ constructor(apiKey?: string, network?: Network, url?: string);
14
15
  /**
15
16
  * Parses the verified source code files from Etherscan
16
17
  * @param contractAddress Ethereum contract address with a 0x prefix
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.parseRemapping = exports.parseRemappings = exports.EtherscanParser = exports.networks = void 0;
6
+ exports.parseRemapping = exports.parseRemappings = exports.EtherscanParser = exports.setChainId = exports.networks = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const parser_1 = require("@solidity-parser/parser");
9
9
  const converterAST2Classes_1 = require("./converterAST2Classes");
@@ -13,92 +13,89 @@ const path_1 = __importDefault(require("path"));
13
13
  require('axios-debug-log');
14
14
  const debug = require('debug')('sol2uml');
15
15
  exports.networks = [
16
- 'mainnet',
17
- 'holesky',
16
+ 'ethereum',
18
17
  'sepolia',
19
- 'polygon',
18
+ 'holesky',
19
+ 'hoodi',
20
20
  'arbitrum',
21
+ 'optimism',
22
+ 'polygon',
21
23
  'avalanche',
24
+ 'base',
22
25
  'bsc',
23
26
  'crono',
24
27
  'fantom',
25
- 'moonbeam',
26
- 'optimism',
28
+ 'sonic',
27
29
  'gnosis',
30
+ 'moonbeam',
28
31
  'celo',
29
32
  'scroll',
30
- 'base',
31
- 'sonic',
33
+ 'linea',
34
+ 'blast',
35
+ 'berachain',
36
+ 'zksync',
32
37
  ];
38
+ const setChainId = (network) =>
39
+ // If an integer is passed, return it as is
40
+ /^-?(0|[1-9]\d*)$/.test(network)
41
+ ? parseInt(network)
42
+ : network === 'sepolia'
43
+ ? 11155111
44
+ : network === 'holesky'
45
+ ? 17000
46
+ : network === 'hoodi'
47
+ ? 560048
48
+ : network === 'arbitrum'
49
+ ? 42161
50
+ : network === 'optimism'
51
+ ? 10
52
+ : network === 'polygon'
53
+ ? 137
54
+ : network === 'avalanche'
55
+ ? 43114
56
+ : network === 'base'
57
+ ? 8453
58
+ : network === 'bsc'
59
+ ? 56
60
+ : network === 'crono'
61
+ ? 25
62
+ : network === 'fantom'
63
+ ? 250
64
+ : network === 'sonic'
65
+ ? 146
66
+ : network === 'gnosis'
67
+ ? 100
68
+ : network === 'moonbeam'
69
+ ? 1284
70
+ : network === 'celo'
71
+ ? 42220
72
+ : network === 'scroll'
73
+ ? 534352
74
+ : network === 'linea'
75
+ ? 59144
76
+ : network === 'blast'
77
+ ? 81457
78
+ : network === 'berachain'
79
+ ? 80094
80
+ : network === 'zksync'
81
+ ? 324
82
+ : 1;
83
+ exports.setChainId = setChainId;
33
84
  class EtherscanParser {
34
- constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet', url) {
35
- this.apikey = apikey;
85
+ constructor(apiKey, network = 'ethereum', url) {
86
+ this.apiKey = apiKey;
36
87
  this.network = network;
37
88
  if (url) {
38
89
  this.url = url;
39
90
  return;
40
91
  }
41
- if (!exports.networks.includes(network)) {
42
- throw new Error(`Invalid network "${network}". Must be one of ${exports.networks}`);
43
- }
44
- else if (network === 'mainnet') {
45
- this.url = 'https://api.etherscan.io/api';
46
- }
47
- else if (network === 'polygon') {
48
- this.url = 'https://api.polygonscan.com/api';
49
- this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB';
50
- }
51
- else if (network === 'arbitrum') {
52
- this.url = 'https://api.arbiscan.io/api';
53
- this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ';
54
- }
55
- else if (network === 'avalanche') {
56
- this.url = 'https://api.snowtrace.io/api';
57
- this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY';
58
- }
59
- else if (network === 'bsc') {
60
- this.url = 'https://api.bscscan.com/api';
61
- this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN';
62
- }
63
- else if (network === 'crono') {
64
- this.url = 'https://api.cronoscan.com/api';
65
- this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2';
66
- }
67
- else if (network === 'fantom') {
68
- this.url = 'https://api.ftmscan.com/api';
69
- this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX';
70
- }
71
- else if (network === 'optimism') {
72
- this.url = `https://api-optimistic.etherscan.io/api`;
73
- this.apikey = 'FEXS1HXVA4Y2RNTMEA8V1UTK21S4JWHH9U';
74
- }
75
- else if (network === 'moonbeam') {
76
- this.url = 'https://api-moonbeam.moonscan.io/api';
77
- this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ';
78
- }
79
- else if (network === 'gnosis') {
80
- this.url = 'https://api.gnosisscan.io/api';
81
- this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB';
82
- }
83
- else if (network === 'scroll') {
84
- this.url = 'https://api.scrollscan.com/api';
85
- this.apikey = '4V37ZJFIN9AURJSU9YG1RP3MSVTPH6D6Z4';
86
- }
87
- else if (network === 'celo') {
88
- this.url = 'https://api.celoscan.io/api';
89
- this.apikey = 'JBV78T5KP15W7WKKKD6KC4J8RX2F4PK8AF';
90
- }
91
- else if (network === 'base') {
92
- this.url = 'https://api.basescan.org/api';
93
- this.apikey = '9I5HUJHPD4ZNXJ4M8TZJ1HD2QBVP1U3M3J';
94
- }
95
- else if (network === 'sonic') {
96
- this.url = 'https://api.sonicscan.org/api';
97
- this.apikey = 'STCM7CPYP341C66C4IVV1IFMWDYRUTI1QY';
98
- }
99
- else {
100
- this.url = `https://api-${network}.etherscan.io/api`;
92
+ if (!apiKey) {
93
+ console.error(`The apiKey option must be set when getting verified source code from an Etherscan like explorer`);
94
+ process.exit(1);
101
95
  }
96
+ const chainId = (0, exports.setChainId)(network);
97
+ debug(`Chain id ${chainId} for network ${network}`);
98
+ this.url = `https://api.etherscan.io/v2/api?chainid=${chainId}`;
102
99
  }
103
100
  /**
104
101
  * Parses the verified source code files from Etherscan
@@ -200,7 +197,7 @@ class EtherscanParser {
200
197
  module: 'contract',
201
198
  action: 'getsourcecode',
202
199
  address: contractAddress,
203
- apikey: this.apikey,
200
+ apikey: this.apiKey,
204
201
  },
205
202
  });
206
203
  if (!Array.isArray(response?.data?.result)) {
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isFolder = exports.isFile = exports.readFile = exports.parseSolidityFile = exports.getSolidityFilesFromFolderOrFile = exports.getSolidityFilesFromFolderOrFiles = exports.parseUmlClassesFromFiles = void 0;
6
+ exports.isFolder = exports.isFile = exports.readFile = exports.parseUmlClassesFromFiles = void 0;
7
+ exports.getSolidityFilesFromFolderOrFiles = getSolidityFilesFromFolderOrFiles;
8
+ exports.getSolidityFilesFromFolderOrFile = getSolidityFilesFromFolderOrFile;
9
+ exports.parseSolidityFile = parseSolidityFile;
7
10
  const fs_1 = require("fs");
8
11
  const path_1 = require("path");
9
12
  const klaw_1 = __importDefault(require("klaw"));
@@ -30,7 +33,6 @@ async function getSolidityFilesFromFolderOrFiles(folderOrFilePaths, ignoreFilesO
30
33
  }
31
34
  return files;
32
35
  }
33
- exports.getSolidityFilesFromFolderOrFiles = getSolidityFilesFromFolderOrFiles;
34
36
  function getSolidityFilesFromFolderOrFile(folderOrFilePath, ignoreFilesOrFolders = [], depthLimit = -1) {
35
37
  debug(`About to get Solidity files under ${folderOrFilePath}`);
36
38
  return new Promise((resolve, reject) => {
@@ -87,7 +89,6 @@ function getSolidityFilesFromFolderOrFile(folderOrFilePath, ignoreFilesOrFolders
87
89
  }
88
90
  });
89
91
  }
90
- exports.getSolidityFilesFromFolderOrFile = getSolidityFilesFromFolderOrFile;
91
92
  function parseSolidityFile(fileName) {
92
93
  const solidityCode = (0, exports.readFile)(fileName);
93
94
  try {
@@ -99,7 +100,6 @@ function parseSolidityFile(fileName) {
99
100
  });
100
101
  }
101
102
  }
102
- exports.parseSolidityFile = parseSolidityFile;
103
103
  const readFile = (fileName, extension) => {
104
104
  try {
105
105
  // try to read file with no extension
@@ -16,7 +16,7 @@ const parserUmlClasses = async (fileFolderAddress, options) => {
16
16
  };
17
17
  if ((0, regEx_1.isAddress)(fileFolderAddress)) {
18
18
  debug(`argument ${fileFolderAddress} is an Ethereum address so checking Etherscan for the verified source code`);
19
- const etherscanApiKey = options.apiKey || 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1';
19
+ const etherscanApiKey = options.apiKey;
20
20
  const etherscanParser = new parserEtherscan_1.EtherscanParser(etherscanApiKey, options.network, options.explorerUrl);
21
21
  result = await etherscanParser.getUmlClasses(fileFolderAddress);
22
22
  }
@@ -22,7 +22,7 @@ export declare const parseValue: (variable: Variable) => string;
22
22
  * @param blockTag block number or `latest`
23
23
  * @return slotValues array of 32 byte slot values as hexadecimal strings
24
24
  */
25
- export declare const getSlotValues: (url: string, contractAddress: string, slotKeys: readonly BigNumberish[], blockTag?: BigNumberish | 'latest') => Promise<string[]>;
25
+ export declare const getSlotValues: (url: string, contractAddress: string, slotKeys: readonly BigNumberish[], blockTag?: BigNumberish | "latest") => Promise<string[]>;
26
26
  /**
27
27
  * Get storage slot values from JSON-RPC API provider.
28
28
  * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy
@@ -32,7 +32,7 @@ export declare const getSlotValues: (url: string, contractAddress: string, slotK
32
32
  * @param blockTag block number or `latest`
33
33
  * @return slotValue 32 byte slot value as hexadecimal string
34
34
  */
35
- export declare const getSlotValue: (url: string, contractAddress: string, slotKey: BigNumberish, blockTag: BigNumberish | 'latest') => Promise<string>;
35
+ export declare const getSlotValue: (url: string, contractAddress: string, slotKey: BigNumberish, blockTag: BigNumberish | "latest") => Promise<string>;
36
36
  /**
37
37
  * Calculates the number of string characters or bytes of a string or bytes type.
38
38
  * See the following for how string and bytes are stored in storage slots
package/lib/sol2uml.js CHANGED
@@ -30,12 +30,12 @@ Can also flatten or compare verified source files on Etherscan-like explorers.`)
30
30
  .default('svg'))
31
31
  .option('-o, --outputFileName <value>', 'output file name')
32
32
  .option('-i, --ignoreFilesOrFolders <names>', 'comma-separated list of files or folders to ignore', validators_1.validateNames)
33
- .addOption(new commander_1.Option('-n, --network <network>', 'Ethereum network which maps to a blockchain explorer')
34
- .choices(parserEtherscan_1.networks)
35
- .default('mainnet')
33
+ .addOption(new commander_1.Option('-n, --network <network>', 'Name or chain id of the blockchain explorer. A name like `ethereum` or `base` will map to a chain id, eg 1 or 8453. Alternatively, use an integer of the chain id. Supported names: ' +
34
+ parserEtherscan_1.networks.join(', '))
35
+ .default('ethereum')
36
36
  .env('ETH_NETWORK'))
37
37
  .addOption(new commander_1.Option('-e, --explorerUrl <url>', 'Override the `network` option with a custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api').env('EXPLORER_URL'))
38
- .addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan, SonicScan or SnowTrace API key').env('SCAN_API_KEY'))
38
+ .addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key.').env('SCAN_API_KEY'))
39
39
  .option('-bc, --backColor <color>', 'Canvas background color. "none" will use a transparent canvas.', 'white')
40
40
  .option('-sc, --shapeColor <color>', 'Basic drawing color for graphics, not text', 'black')
41
41
  .option('-fc, --fillColor <color>', 'Color used to fill the background of a node', 'gray95')
@@ -235,8 +235,6 @@ The line numbers are from contract B. There are no line numbers for the red sect
235
235
  .option('-af --aFile <value>', 'Limit code compare to contract A source file with the full path and extension as displayed in the file summary (default: compares all source files)')
236
236
  .option('-bf --bFile <value>', 'Contract B source file with the full path and extension as displayed in the file summary. Used if aFile is specified and the source file has been renamed (default: aFile if specified)')
237
237
  .addOption(new commander_1.Option('-bn, --bNetwork <network>', 'Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option)').choices(parserEtherscan_1.networks))
238
- .option('-be, --bExplorerUrl <url>', 'Override the `bNetwork` option with custom blockchain explorer API URL for contract B if on a different blockchain to contract A. Contract A uses the `explorerUrl` (default: value of `explorerUrl` option)')
239
- .option('-bk, --bApiKey <key>', 'Blockchain explorer API key for contract B if on a different blockchain to contract A. Contract A uses the `apiKey` option (default: value of `apiKey` option)')
240
238
  .option('--flatten', 'Flatten into a single file before comparing. Only works when comparing two verified contracts, not to local files', false)
241
239
  .option('--saveFiles', 'Save the flattened contract code to the filesystem when using the `flatten` option. The file names will be the contract address with a .sol extension', false)
242
240
  .option('-l, --lineBuffer <value>', 'Minimum number of lines before and after changes (default: 4)', validators_1.validateLineBuffer)
@@ -250,7 +248,7 @@ The line numbers are from contract B. There are no line numbers for the red sect
250
248
  const aEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network, combinedOptions.explorerUrl);
251
249
  if ((0, regEx_1.isAddress)(fileFoldersAddress)) {
252
250
  const addressB = fileFoldersAddress;
253
- const bEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.bApiKey || combinedOptions.apiKey, combinedOptions.bNetwork || combinedOptions.network, combinedOptions.bExplorerUrl || combinedOptions.explorerUrl);
251
+ const bEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.bNetwork || combinedOptions.network, combinedOptions.explorerUrl);
254
252
  // If flattening
255
253
  if (options.flatten) {
256
254
  await (0, diffContracts_1.compareFlattenContracts)(addressA, addressB, aEtherscanParser, bEtherscanParser, combinedOptions);
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.squashUmlClasses = void 0;
27
37
  const umlClass_1 = require("./umlClass");
package/lib/utils/diff.js CHANGED
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.diffCode = void 0;
27
37
  const diff_match_patch_1 = __importStar(require("diff-match-patch"));
@@ -3,7 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writePng = exports.writeSVG = exports.writeDot = exports.writeSourceCode = exports.convertDot2Svg = exports.writeOutputFiles = void 0;
6
+ exports.writeOutputFiles = void 0;
7
+ exports.convertDot2Svg = convertDot2Svg;
8
+ exports.writeSourceCode = writeSourceCode;
9
+ exports.writeDot = writeDot;
10
+ exports.writeSVG = writeSVG;
11
+ exports.writePng = writePng;
7
12
  const fs_1 = require("fs");
8
13
  const path_1 = __importDefault(require("path"));
9
14
  const sync_1 = __importDefault(require("@aduh95/viz.js/sync"));
@@ -63,7 +68,6 @@ function convertDot2Svg(dot) {
63
68
  throw new Error(`Failed to parse dot string`, { cause: err });
64
69
  }
65
70
  }
66
- exports.convertDot2Svg = convertDot2Svg;
67
71
  function writeSourceCode(code, filename = 'source', extension = '.sol') {
68
72
  const fileExtension = path_1.default.extname(filename);
69
73
  const outputFile = fileExtension === extension ? filename : filename + extension;
@@ -79,7 +83,6 @@ function writeSourceCode(code, filename = 'source', extension = '.sol') {
79
83
  }
80
84
  });
81
85
  }
82
- exports.writeSourceCode = writeSourceCode;
83
86
  function writeDot(dot, filename) {
84
87
  const dotFilename = changeFileExtension(filename, 'dot');
85
88
  debug(`About to write Dot file to ${dotFilename}`);
@@ -94,7 +97,6 @@ function writeDot(dot, filename) {
94
97
  }
95
98
  });
96
99
  }
97
- exports.writeDot = writeDot;
98
100
  /**
99
101
  * Writes an SVG file to the file system.
100
102
  * @param svg The SVG input to be written to the file system.
@@ -127,7 +129,6 @@ function writeSVG(svg, svgFilename = 'classDiagram.svg', outputFormats = 'png')
127
129
  });
128
130
  });
129
131
  }
130
- exports.writeSVG = writeSVG;
131
132
  /**
132
133
  * Asynchronously writes a PNG file to the file system from an SVG input.
133
134
  * @param svg - The SVG input to be converted to a PNG file.
@@ -161,7 +162,6 @@ async function writePng(svg, filename) {
161
162
  });
162
163
  }
163
164
  }
164
- exports.writePng = writePng;
165
165
  // put a new file extension on a filename
166
166
  const changeFileExtension = (filename, extension) => {
167
167
  const parsedFile = path_1.default.parse(filename);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.5.21",
3
+ "version": "2.5.22",
4
4
  "description": "Solidity contract visualisation tool.",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -23,28 +23,28 @@
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
25
  "@aduh95/viz.js": "^3.7.0",
26
- "@solidity-parser/parser": "^0.16.1",
27
- "axios": "^1.6.0",
26
+ "@solidity-parser/parser": "^0.20.1",
27
+ "axios": "^1.10.0",
28
28
  "axios-debug-log": "^1.0.0",
29
- "cli-color": "^2.0.3",
30
- "commander": "^11.1.0",
29
+ "cli-color": "^2.0.4",
30
+ "commander": "^12.1.0",
31
31
  "convert-svg-to-png": "^0.6.4",
32
- "debug": "^4.3.4",
32
+ "debug": "^4.4.1",
33
33
  "diff-match-patch": "^1.0.5",
34
- "ethers": "^5.7.2",
34
+ "ethers": "^5.8.0",
35
35
  "js-graph-algorithms": "^1.0.18",
36
36
  "klaw": "^4.1.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@openzeppelin/contracts": "^4.9.3",
40
- "@types/diff-match-patch": "^1.0.35",
41
- "@types/jest": "^29.5.7",
42
- "@types/klaw": "^3.0.5",
43
- "jest": "^29.7.0",
44
- "prettier": "^3.0.3",
45
- "ts-jest": "^29.1.1",
46
- "ts-node": "^10.9.1",
47
- "typescript": "^5.2.2"
40
+ "@types/diff-match-patch": "^1.0.36",
41
+ "@types/jest": "^29.5.14",
42
+ "@types/klaw": "^3.0.7",
43
+ "jest": "^30.0.0",
44
+ "prettier": "^3.5.3",
45
+ "ts-jest": "^29.4.0",
46
+ "ts-node": "^10.9.2",
47
+ "typescript": "^5.8.3"
48
48
  },
49
49
  "files": [
50
50
  "lib/*.js",