2727LOGGER = logging .getLogger ("CryticCompile" )
2828
2929
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"
3132
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)
3239ETHERSCAN_BASE_BYTECODE = "https://%s/address/%s#code"
3340
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" ),
55100}
56101
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+
57126
58127def _handle_bytecode (crytic_compile : "CryticCompile" , target : str , result_b : bytes ) -> None :
59128 """Parse the bytecode and populate CryticCompile info
@@ -215,15 +284,24 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
215284
216285 target = self ._target
217286
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 ]
221299 addr = target [target .find (":" ) + 1 :]
222- etherscan_url = ETHERSCAN_BASE % (prefix , addr )
300+ etherscan_url = ETHERSCAN_BASE_V1 % (prefix , addr )
223301 etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % (prefix_bytecode , addr )
224-
225302 else :
226- etherscan_url = ETHERSCAN_BASE % (".etherscan.io" , target )
303+ api_key_required = 2
304+ etherscan_url = ETHERSCAN_BASE_V2 % ("1" , target )
227305 etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % ("etherscan.io" , target )
228306 addr = target
229307 prefix = None
@@ -232,75 +310,36 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
232310 only_bytecode = kwargs .get ("etherscan_only_bytecode" , False )
233311
234312 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" )
246315
247316 export_dir = kwargs .get ("export_dir" , "crytic-export" )
248317 export_dir = os .path .join (
249318 export_dir , kwargs .get ("etherscan_export_dir" , "etherscan-contracts" )
250319 )
251320
252- if etherscan_api_key and "etherscan" in etherscan_url :
321+ if api_key_required == 2 and etherscan_api_key :
253322 etherscan_url += f"&apikey={ etherscan_api_key } "
254323 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}"
288328
289329 source_code : str = ""
290330 result : Dict [str , Union [bool , str , int ]] = {}
291331 contract_name : str = ""
292332
293333 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 ()
304343
305344 info = json .loads (html )
306345
0 commit comments