-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathetherscanApi.ts
More file actions
130 lines (110 loc) · 5.58 KB
/
etherscanApi.ts
File metadata and controls
130 lines (110 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { Address, Hex, Chain } from 'viem'
import { TransactionData, GetContractSourceCodeResponse } from '../types/types'
import { avalanche } from 'viem/chains'
import { ChainApi } from '../types/types'
/**
* EtherscanApi class to interact with Etherscan API
* It uses the Etherscan API 2.0
* Supported chains can be found at https://docs.etherscan.io/etherscan-v2/supported-chains
*/
class EtherscanApi implements ChainApi {
public chain: Chain
private apiKey: string
constructor(chain: Chain, apiKey: string) {
this.chain = chain
this.apiKey = apiKey
}
private getApiUrl(): string {
// TODO: Validate chain is supported by EtherscanAPI.
return `https://api.etherscan.io/v2/api?chainid=${this.chain.id}&`
}
private async fetchFromApi(url: string): Promise<any> {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Error fetching data from API: ${response.statusText} for ${url}`)
}
return response.json()
}
private async tryFallbackExplorer(chain: Chain, address: Address): Promise<any | null> {
// Check if this is Avalanche chain and try Snowscan as fallback
if (this.chain.id === avalanche.id) {
console.log(`Trying Snowscan fallback for address ${address}`)
// this is a working routescan api example url https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api?module=contract&action=getsourcecode&address=0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7
const snowtraceUrl = `https://api.routescan.io/v2/network/mainnet/evm/${this.chain.id}/etherscan/api?module=contract&action=getsourcecode&address=${address}`
try {
const snowtraceResponse = await fetch(snowtraceUrl)
if (snowtraceResponse.ok) {
const fallbackData = await snowtraceResponse.json()
if (
fallbackData.status === '1' &&
fallbackData.result[0].ABI !== 'Contract source code not verified'
) {
console.log(`Successfully found contract on Snowscan for address ${address}`)
return fallbackData
}
}
} catch (error) {
console.log(`Snowscan fallback failed for address ${address}:`, error)
}
}
return null
}
public async getDeploymentTxHashAndBlock(
addresses: Address[],
): Promise<{ address: Address; deploymentTxHash: Hex; blockNumber: string }[]> {
const apiUrl = this.getApiUrl()
const fetchingUrl = `${apiUrl}module=contract&action=getcontractcreation&contractaddresses=${addresses.join(',')}&apikey=${this.apiKey}`
const data: TransactionData = await this.fetchFromApi(fetchingUrl)
return data.result.map((entry, index) => ({
address: addresses[index],
deploymentTxHash: entry.txHash,
blockNumber: entry.blockNumber,
}))
}
public async getSourceCode(
addresses: Address[],
): Promise<{ address: Address; Proxy: string; ContractName: string; ABI: string; Implementation: Address }[]> {
const apiUrl = this.getApiUrl()
const results = []
for (const address of addresses) {
try {
// https://api.routescan.io/v2/network/mainnet/evm/9745/etherscan/api?module=contract&action=getabi&address=0xcA11bde05977b3631167028862bE2a173976CA11&apikey=YourApiKeyToken
const fetchingUrl = `${apiUrl}module=contract&action=getsourcecode&address=${address}&apikey=${this.apiKey}`
const data: GetContractSourceCodeResponse = await this.fetchFromApi(fetchingUrl)
if (data.status !== '1') {
console.error(`Error fetching contract info for address ${address}: ${data.message}`)
continue // Skip this address
}
if (!data.result[0].ABI) {
console.error(`ABI is missing for address ${address}`)
continue // Skip this address
}
// the contract can be unverified
if (data.result[0].ABI === 'Contract source code not verified') {
console.log(`Contract is unverified for address ${address}, trying fallback block explorer`)
// Try fallback explorer (e.g., Snowscan for Avalanche)
const fallbackData = await this.tryFallbackExplorer(this.chain, address)
if (fallbackData) {
const { Proxy, ContractName, ABI, Implementation } = fallbackData.result[0]
results.push({ address, Proxy, ContractName, ABI, Implementation })
continue
}
console.log(`Contract is unverified on all explorers for address ${address}`)
continue // Skip this address
}
const { Proxy, ContractName, ABI, Implementation } = data.result[0]
results.push({ address, Proxy, ContractName, ABI, Implementation })
} catch (error) {
console.error(`Error processing address ${address}:`, error)
// Skip this address and continue with the next one
}
// Add a delay between API calls
await this.delay(1000)
}
return results
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
}
export default EtherscanApi