27
27
LOGGER = logging .getLogger ("CryticCompile" )
28
28
29
29
30
- ETHERSCAN_BASE = "https://api%s/api?module=contract&action=getsourcecode&address=%s"
30
+ # Etherscan v1 API style (per-scanner URL)
31
+ ETHERSCAN_BASE_V1 = "https://api%s/api?module=contract&action=getsourcecode&address=%s"
31
32
33
+ # Etherscan v2 API style (unified)
34
+ ETHERSCAN_BASE_V2 = (
35
+ "https://api.etherscan.io/v2/api?chainid=%s&module=contract&action=getsourcecode&address=%s"
36
+ )
37
+
38
+ # Bytecode URL style (for scraping)
32
39
ETHERSCAN_BASE_BYTECODE = "https://%s/address/%s#code"
33
40
34
- SUPPORTED_NETWORK = {
35
- # Key, (prefix_base, perfix_bytecode)
36
- "mainet:" : (".etherscan.io" , "etherscan.io" ),
37
- "optim:" : ("-optimistic.etherscan.io" , "optimistic.etherscan.io" ),
38
- "goerli:" : ("-goerli.etherscan.io" , "goerli.etherscan.io" ),
39
- "sepolia:" : ("-sepolia.etherscan.io" , "sepolia.etherscan.io" ),
40
- "tobalaba:" : ("-tobalaba.etherscan.io" , "tobalaba.etherscan.io" ),
41
- "bsc:" : (".bscscan.com" , "bscscan.com" ),
42
- "testnet.bsc:" : ("-testnet.bscscan.com" , "testnet.bscscan.com" ),
43
- "arbi:" : (".arbiscan.io" , "arbiscan.io" ),
44
- "testnet.arbi:" : ("-testnet.arbiscan.io" , "testnet.arbiscan.io" ),
45
- "poly:" : (".polygonscan.com" , "polygonscan.com" ),
46
- "mumbai:" : ("-testnet.polygonscan.com" , "testnet.polygonscan.com" ),
47
- "avax:" : (".snowtrace.io" , "snowtrace.io" ),
48
- "testnet.avax:" : ("-testnet.snowtrace.io" , "testnet.snowtrace.io" ),
49
- "ftm:" : (".ftmscan.com" , "ftmscan.com" ),
50
- "goerli.base:" : ("-goerli.basescan.org" , "goerli.basescan.org" ),
51
- "base:" : (".basescan.org" , "basescan.org" ),
52
- "gno:" : (".gnosisscan.io" , "gnosisscan.io" ),
53
- "polyzk:" : ("-zkevm.polygonscan.com" , "zkevm.polygonscan.com" ),
54
- "blast:" : (".blastscan.io" , "blastscan.io" ),
41
+ # v1 style scanners
42
+ SUPPORTED_NETWORK_V1 : Dict [str , Tuple [str , str ]] = {
43
+ # None at this time. External tracer instances not operated by Etherscan would be here
44
+ }
45
+
46
+ # v2 style scanners
47
+ SUPPORTED_NETWORK_V2 : Dict [str , Tuple [str , str ]] = {
48
+ # Key, (chainid, perfix_bytecode)
49
+ "mainnet" : ("1" , "etherscan.io" ),
50
+ "sepolia" : ("11155111" , "sepolia.etherscan.io" ),
51
+ "holesky" : ("17000" , "holesky.etherscan.io" ),
52
+ "bsc" : ("56" , "bscscan.com" ),
53
+ "testnet.bsc" : ("97" , "testnet.bscscan.com" ),
54
+ "poly" : ("137" , "polygonscan.com" ),
55
+ "amoy.poly" : ("80002" , "amoy.polygonscan.com" ),
56
+ "polyzk" : ("1101" , "zkevm.polygonscan.com" ),
57
+ "cardona.polyzk" : ("2442" , "cardona-zkevm.polygonscan.com" ),
58
+ "base" : ("8453" , "basescan.org" ),
59
+ "sepolia.base" : ("84532" , "sepolia.basescan.org" ),
60
+ "arbi" : ("42161" , "arbiscan.io" ),
61
+ "nova.arbi" : ("42170" , "nova.arbiscan.io" ),
62
+ "sepolia.arbi" : ("421614" , "sepolia.arbiscan.io" ),
63
+ "linea" : ("59144" , "lineascan.build" ),
64
+ "sepolia.linea" : ("59141" , "sepolia.lineascan.build" ),
65
+ "ftm" : ("250" , "ftmscan.com" ),
66
+ "testnet.ftm" : ("4002" , "testnet.ftmscan.com" ),
67
+ "blast" : ("81457" , "blastscan.io" ),
68
+ "sepolia.blast" : ("168587773" , "sepolia.blastscan.io" ),
69
+ "optim" : ("10" , "optimistic.etherscan.io" ),
70
+ "sepolia.optim" : ("11155420" , "sepolia-optimism.etherscan.io" ),
71
+ "avax" : ("43114" , "snowscan.xyz" ),
72
+ "testnet.avax" : ("43113" , "testnet.snowscan.xyz" ),
73
+ "bttc" : ("199" , "bttcscan.com" ),
74
+ "testnet.bttc" : ("1028" , "testnet.bttcscan.com" ),
75
+ "celo" : ("42220" , "celoscan.io" ),
76
+ "alfajores.celo" : ("44787" , "alfajores.celoscan.io" ),
77
+ "cronos" : ("25" , "cronoscan.com" ),
78
+ "frax" : ("252" , "fraxscan.com" ),
79
+ "holesky.frax" : ("2522" , "holesky.fraxscan.com" ),
80
+ "gno" : ("100" , "gnosisscan.io" ),
81
+ "kroma" : ("255" , "kromascan.com" ),
82
+ "sepolia.kroma" : ("2358" , "sepolia.kromascan.com" ),
83
+ "mantle" : ("5000" , "mantlescan.xyz" ),
84
+ "sepolia.mantle" : ("5003" , "sepolia.mantlescan.xyz" ),
85
+ "moonbeam" : ("1284" , "moonbeam.moonscan.io" ),
86
+ "moonriver" : ("1285" , "moonriver.moonscan.io" ),
87
+ "moonbase" : ("1287" , "moonbase.moonscan.io" ),
88
+ "opbnb" : ("204" , "opbnb.bscscan.com" ),
89
+ "testnet.opbnb" : ("5611" , "opbnb-testnet.bscscan.com" ),
90
+ "scroll" : ("534352" , "scrollscan.com" ),
91
+ "sepolia.scroll" : ("534351" , "sepolia.scrollscan.com" ),
92
+ "taiko" : ("167000" , "taikoscan.io" ),
93
+ "hekla.taiko" : ("167009" , "hekla.taikoscan.io" ),
94
+ "wemix" : ("1111" , "wemixscan.com" ),
95
+ "testnet.wemix" : ("1112" , "testnet.wemixscan.com" ),
96
+ "era.zksync" : ("324" , "era.zksync.network" ),
97
+ "sepoliaera.zksync" : ("300" , "sepolia-era.zksync.network" ),
98
+ "xai" : ("660279" , "xaiscan.io" ),
99
+ "sepolia.xai" : ("37714555429" , "sepolia.xaiscan.io" ),
55
100
}
56
101
102
+ SUPPORTED_NETWORK = {** SUPPORTED_NETWORK_V1 , ** SUPPORTED_NETWORK_V2 }
103
+
104
+
105
+ def generate_supported_network_v2_list () -> None :
106
+ """Manual function to generate a dictionary for updating the SUPPORTED_NETWORK_V2 array"""
107
+
108
+ with urllib .request .urlopen ("https://api.etherscan.io/v2/chainlist" ) as response :
109
+ items = response .read ()
110
+ networks = json .loads (items )
111
+
112
+ id2name = {}
113
+ for name , (chainid , _ ) in SUPPORTED_NETWORK_V2 .items ():
114
+ id2name [chainid ] = name
115
+
116
+ results = {}
117
+ for network in networks ["result" ]:
118
+ name = id2name .get (network ["chainid" ], f"{ network ['chainid' ]} " )
119
+ results [name ] = (
120
+ network ["chainid" ],
121
+ network ["blockexplorer" ].replace ("https://" , "" ).strip ("/" ),
122
+ )
123
+
124
+ print (results )
125
+
57
126
58
127
def _handle_bytecode (crytic_compile : "CryticCompile" , target : str , result_b : bytes ) -> None :
59
128
"""Parse the bytecode and populate CryticCompile info
@@ -215,15 +284,24 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
215
284
216
285
target = self ._target
217
286
218
- if target .startswith (tuple (SUPPORTED_NETWORK )):
219
- prefix : Union [None , str ] = SUPPORTED_NETWORK [target [: target .find (":" ) + 1 ]][0 ]
220
- prefix_bytecode = SUPPORTED_NETWORK [target [: target .find (":" ) + 1 ]][1 ]
287
+ api_key_required = None
288
+
289
+ if target .startswith (tuple (SUPPORTED_NETWORK_V2 )):
290
+ api_key_required = 2
291
+ prefix , addr = target .split (":" , 2 )
292
+ chainid , prefix_bytecode = SUPPORTED_NETWORK_V2 [prefix ]
293
+ etherscan_url = ETHERSCAN_BASE_V2 % (chainid , addr )
294
+ etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % (prefix_bytecode , addr )
295
+ elif target .startswith (tuple (SUPPORTED_NETWORK_V1 )):
296
+ api_key_required = 1
297
+ prefix = SUPPORTED_NETWORK_V1 [target [: target .find (":" ) + 1 ]][0 ]
298
+ prefix_bytecode = SUPPORTED_NETWORK_V1 [target [: target .find (":" ) + 1 ]][1 ]
221
299
addr = target [target .find (":" ) + 1 :]
222
- etherscan_url = ETHERSCAN_BASE % (prefix , addr )
300
+ etherscan_url = ETHERSCAN_BASE_V1 % (prefix , addr )
223
301
etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % (prefix_bytecode , addr )
224
-
225
302
else :
226
- etherscan_url = ETHERSCAN_BASE % (".etherscan.io" , target )
303
+ api_key_required = 2
304
+ etherscan_url = ETHERSCAN_BASE_V2 % ("1" , target )
227
305
etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % ("etherscan.io" , target )
228
306
addr = target
229
307
prefix = None
@@ -232,75 +310,36 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
232
310
only_bytecode = kwargs .get ("etherscan_only_bytecode" , False )
233
311
234
312
etherscan_api_key = kwargs .get ("etherscan_api_key" , None )
235
- arbiscan_api_key = kwargs .get ("arbiscan_api_key" , None )
236
- polygonscan_api_key = kwargs .get ("polygonscan_api_key" , None )
237
- test_polygonscan_api_key = kwargs .get ("test_polygonscan_api_key" , None )
238
- avax_api_key = kwargs .get ("avax_api_key" , None )
239
- ftmscan_api_key = kwargs .get ("ftmscan_api_key" , None )
240
- bscan_api_key = kwargs .get ("bscan_api_key" , None )
241
- optim_api_key = kwargs .get ("optim_api_key" , None )
242
- base_api_key = kwargs .get ("base_api_key" , None )
243
- gno_api_key = kwargs .get ("gno_api_key" , None )
244
- polyzk_api_key = kwargs .get ("polyzk_api_key" , None )
245
- blast_api_key = kwargs .get ("blast_api_key" , None )
313
+ if etherscan_api_key is None :
314
+ etherscan_api_key = os .getenv ("ETHERSCAN_API_KEY" )
246
315
247
316
export_dir = kwargs .get ("export_dir" , "crytic-export" )
248
317
export_dir = os .path .join (
249
318
export_dir , kwargs .get ("etherscan_export_dir" , "etherscan-contracts" )
250
319
)
251
320
252
- if etherscan_api_key and "etherscan" in etherscan_url :
321
+ if api_key_required == 2 and etherscan_api_key :
253
322
etherscan_url += f"&apikey={ etherscan_api_key } "
254
323
etherscan_bytecode_url += f"&apikey={ etherscan_api_key } "
255
- if arbiscan_api_key and "arbiscan" in etherscan_url :
256
- etherscan_url += f"&apikey={ arbiscan_api_key } "
257
- etherscan_bytecode_url += f"&apikey={ arbiscan_api_key } "
258
- if polygonscan_api_key and "polygonscan" in etherscan_url :
259
- etherscan_url += f"&apikey={ polygonscan_api_key } "
260
- etherscan_bytecode_url += f"&apikey={ polygonscan_api_key } "
261
- if test_polygonscan_api_key and "polygonscan" in etherscan_url :
262
- etherscan_url += f"&apikey={ test_polygonscan_api_key } "
263
- etherscan_bytecode_url += f"&apikey={ test_polygonscan_api_key } "
264
- if avax_api_key and "snowtrace" in etherscan_url :
265
- etherscan_url += f"&apikey={ avax_api_key } "
266
- etherscan_bytecode_url += f"&apikey={ avax_api_key } "
267
- if ftmscan_api_key and "ftmscan" in etherscan_url :
268
- etherscan_url += f"&apikey={ ftmscan_api_key } "
269
- etherscan_bytecode_url += f"&apikey={ ftmscan_api_key } "
270
- if bscan_api_key and "bscscan" in etherscan_url :
271
- etherscan_url += f"&apikey={ bscan_api_key } "
272
- etherscan_bytecode_url += f"&apikey={ bscan_api_key } "
273
- if optim_api_key and "optim" in etherscan_url :
274
- etherscan_url += f"&apikey={ optim_api_key } "
275
- etherscan_bytecode_url += f"&apikey={ optim_api_key } "
276
- if base_api_key and "base" in etherscan_url :
277
- etherscan_url += f"&apikey={ base_api_key } "
278
- etherscan_bytecode_url += f"&apikey={ base_api_key } "
279
- if gno_api_key and "gno" in etherscan_url :
280
- etherscan_url += f"&apikey={ gno_api_key } "
281
- etherscan_bytecode_url += f"&apikey={ gno_api_key } "
282
- if polyzk_api_key and "zkevm" in etherscan_url :
283
- etherscan_url += f"&apikey={ polyzk_api_key } "
284
- etherscan_bytecode_url += f"&apikey={ polyzk_api_key } "
285
- if blast_api_key and "blast" in etherscan_url :
286
- etherscan_url += f"&apikey={ blast_api_key } "
287
- etherscan_bytecode_url += f"&apikey={ blast_api_key } "
324
+ # API key handling for external tracers would be here e.g.
325
+ # elif api_key_required == 1 and avax_api_key and "snowtrace" in etherscan_url:
326
+ # etherscan_url += f"&apikey={avax_api_key}"
327
+ # etherscan_bytecode_url += f"&apikey={avax_api_key}"
288
328
289
329
source_code : str = ""
290
330
result : Dict [str , Union [bool , str , int ]] = {}
291
331
contract_name : str = ""
292
332
293
333
if not only_bytecode :
294
- if "polygon" in etherscan_url or "basescan" in etherscan_url :
295
- # build object with headers, then send request
296
- new_etherscan_url = urllib .request .Request (
297
- etherscan_url , headers = {"User-Agent" : "Mozilla/5.0" }
298
- )
299
- with urllib .request .urlopen (new_etherscan_url ) as response :
300
- html = response .read ()
301
- else :
302
- with urllib .request .urlopen (etherscan_url ) as response :
303
- html = response .read ()
334
+ # build object with headers, then send request
335
+ new_etherscan_url = urllib .request .Request (
336
+ etherscan_url ,
337
+ headers = {
338
+ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 crytic-compile/0"
339
+ },
340
+ )
341
+ with urllib .request .urlopen (new_etherscan_url ) as response :
342
+ html = response .read ()
304
343
305
344
info = json .loads (html )
306
345
0 commit comments