@@ -51,6 +51,42 @@ const NETWORK_CONFIGS: Record<string, NetworkConfig> = {
5151 apiUrl : "https://zetachain.blockscout.com/api" ,
5252 explorerType : "blockscout" ,
5353 } ,
54+ sepolia_testnet : {
55+ name : "Ethereum Sepolia" ,
56+ apiUrl : "https://api.etherscan.io/v2/api?chainid=11155111" ,
57+ apiKeyEnv : process . env . ETHERSCAN_API_KEY ! ,
58+ explorerType : "etherscan" ,
59+ } ,
60+ bsc_testnet : {
61+ name : "BNB Smart Chain Testnet" ,
62+ apiUrl : "https://api.etherscan.io/v2/api?chainid=97" ,
63+ apiKeyEnv : process . env . ETHERSCAN_API_KEY ! ,
64+ explorerType : "etherscan" ,
65+ } ,
66+ amoy_testnet : {
67+ name : "Polygon Amoy" ,
68+ apiUrl : "https://api.etherscan.io/v2/api?chainid=80002" ,
69+ apiKeyEnv : process . env . ETHERSCAN_API_KEY ! ,
70+ explorerType : "etherscan" ,
71+ } ,
72+ base_sepolia : {
73+ name : "Base Sepolia" ,
74+ apiUrl : "https://api.etherscan.io/v2/api?chainid=84532" ,
75+ apiKeyEnv : process . env . ETHERSCAN_API_KEY ! ,
76+ explorerType : "etherscan" ,
77+ } ,
78+ arbitrum_sepolia : {
79+ name : "Arbitrum Sepolia" ,
80+ apiUrl : "https://api.etherscan.io/v2/api?chainid=421614" ,
81+ apiKeyEnv : process . env . ETHERSCAN_API_KEY ! ,
82+ explorerType : "etherscan" ,
83+ } ,
84+ avalanche_testnet : {
85+ name : "Avalanche Fuji" ,
86+ apiUrl : "https://api.etherscan.io/v2/api?chainid=43113" ,
87+ apiKeyEnv : process . env . ETHERSCAN_API_KEY ! ,
88+ explorerType : "etherscan" ,
89+ }
5490}
5591
5692interface ContractSource {
@@ -94,6 +130,15 @@ interface BlockscoutResponse {
94130 abi ?: string ;
95131}
96132
133+ /**
134+ * Fetches contract source code from Blockscout explorer API.
135+ * Converts the Blockscout response format to match Etherscan's format for consistency.
136+ *
137+ * @param address - The contract address to fetch
138+ * @param apiUrl - The Blockscout API base URL
139+ * @returns ContractSource object with source code and metadata
140+ * @throws Error if contract is not verified or fetch fails
141+ */
97142async function fetchFromBlockscout (
98143 address : string ,
99144 apiUrl : string
@@ -145,18 +190,27 @@ async function fetchFromBlockscout(
145190 } catch ( error ) {
146191 if ( axios . isAxiosError ( error ) ) {
147192 if ( error . response ?. status === 404 ) {
148- throw new Error ( " Contract not found or not verified on Blockscout" ) ;
193+ throw new Error ( ` Contract not found or not verified on Blockscout: ${ error . message } ` ) ;
149194 }
150195 throw new Error ( `Failed to fetch contract from Blockscout: ${ error . message } ` ) ;
151196 }
152197 throw error ;
153198 }
154199}
155200
201+ /**
202+ * Fetches contract source code from Etherscan-compatible API.
203+ *
204+ * @param address - The contract address to fetch
205+ * @param apiUrl - The Etherscan API URL with chainid
206+ * @param apiKey - The Etherscan API key for authentication
207+ * @returns ContractSource object with source code and metadata
208+ * @throws Error if contract is not verified or fetch fails
209+ */
156210async function fetchFromEtherscan (
157- address : string ,
158- apiUrl : string ,
159- apiKey : string
211+ address : string ,
212+ apiUrl : string ,
213+ apiKey : string
160214) : Promise < ContractSource > {
161215 try {
162216 const response = await axios . get < EtherscanResponse > ( apiUrl , {
@@ -194,6 +248,15 @@ async function fetchFromEtherscan(
194248 }
195249}
196250
251+ /**
252+ * Fetches contract source code from the appropriate explorer based on network configuration.
253+ * Routes to either Blockscout or Etherscan API based on the explorer type.
254+ *
255+ * @param address - The contract address to fetch
256+ * @param network - The network identifier
257+ * @returns ContractSource object with source code and metadata
258+ * @throws Error if network is unknown or fetch fails
259+ */
197260async function fetchContractSource (
198261 address : string ,
199262 network : string
@@ -214,6 +277,13 @@ async function fetchContractSource(
214277 }
215278}
216279
280+ /**
281+ * Extracts the declaration order of contracts, interfaces, and libraries from Solidity source code.
282+ * Used to maintain consistent ordering when flattening multi-file contracts.
283+ *
284+ * @param sourceCode - The Solidity source code to analyze
285+ * @returns Array of contract/interface/library names in order of declaration
286+ */
217287function extractDeclarationOrder ( sourceCode : string ) : string [ ] {
218288 const order : string [ ] = [ ] ;
219289
@@ -228,6 +298,13 @@ function extractDeclarationOrder(sourceCode: string): string[] {
228298 return order ;
229299}
230300
301+ /**
302+ * Finds the file content that contains a specific contract, interface, or library declaration.
303+ *
304+ * @param sources - Object containing file paths/names as keys and file content as values
305+ * @param contractName - The name of the contract/interface/library to find
306+ * @returns The file content containing the contract, or null if not found
307+ */
231308function findFileByContractName ( sources : any , contractName : string ) : string | null {
232309 for ( const [ , fileData ] of Object . entries ( sources ) ) {
233310 const content = typeof fileData === "object" && fileData !== null
@@ -243,6 +320,13 @@ function findFileByContractName(sources: any, contractName: string): string | nu
243320 return null ;
244321}
245322
323+ /**
324+ * Removes duplicate SPDX license identifiers and pragma statements from flattened code.
325+ * Keeps only the first occurrence of each to avoid compilation errors.
326+ *
327+ * @param code - The Solidity source code to clean
328+ * @returns Cleaned source code with duplicate headers removed
329+ */
246330function cleanDuplicateHeaders ( code : string ) : string {
247331 const lines = code . split ( '\n' ) ;
248332 const result : string [ ] = [ ] ;
@@ -276,6 +360,14 @@ function cleanDuplicateHeaders(code: string): string {
276360 return result . join ( '\n' ) ;
277361}
278362
363+ /**
364+ * Flattens multi-file contract source code into a single file.
365+ * Handles both standard JSON format and double-braced format from explorers.
366+ * Files are concatenated in alphabetical order and duplicate headers are removed.
367+ *
368+ * @param sourceCode - The source code
369+ * @returns Flattened source code as a single string
370+ */
279371function flattenSourceCode ( sourceCode : string ) : string {
280372 if ( sourceCode . startsWith ( "{{" ) || sourceCode . startsWith ( "{" ) ) {
281373 try {
@@ -308,6 +400,14 @@ function flattenSourceCode(sourceCode: string): string {
308400 return sourceCode ;
309401}
310402
403+ /**
404+ * Reorders multi-file contract source to match the declaration order of a single-file version.
405+ * This ensures consistent ordering when comparing contracts that have different file structures.
406+ *
407+ * @param singleFileCode - The single-file version used as reference for ordering
408+ * @param multiFileCode - The multi-file version source code to reorder
409+ * @returns Reordered and flattened source code matching the single-file order
410+ */
311411function reorderMultiFileToMatchSingleFile (
312412 singleFileCode : string ,
313413 multiFileCode : string
@@ -366,6 +466,15 @@ function reorderMultiFileToMatchSingleFile(
366466 }
367467}
368468
469+ /**
470+ * Saves contract source code to a file in the specified output directory.
471+ * Creates the directory if it doesn't exist.
472+ *
473+ * @param content - The contract source code to save
474+ * @param fileName - The name of the file to create
475+ * @param outputDir - The directory where the file should be saved
476+ * @returns The full path to the saved file
477+ */
369478function saveContractToFile (
370479 content : string ,
371480 fileName : string ,
@@ -381,6 +490,16 @@ function saveContractToFile(
381490 return filePath ;
382491}
383492
493+ /**
494+ * Main function to fetch, flatten, and compare two contract implementations.
495+ * Fetches both contracts, handles single vs multi-file structures,
496+ * flattens them for comparison, and saves the results to disk.
497+ *
498+ * @param oldAddress - The address of the old/previous contract implementation
499+ * @param newAddress - The address of the new/updated contract implementation
500+ * @param network - The network identifier where the contracts are deployed
501+ * @throws Error if fetching or processing fails
502+ */
384503export async function fetchAndFlattenContract (
385504 oldAddress : string ,
386505 newAddress : string ,
0 commit comments