Skip to content
This repository was archived by the owner on Oct 25, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/probe-etherscan-base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: probe etherscan base deployment
on:
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/probe-etherscan-base.yml'
- 'scripts/probe-etherscan-base-deployment.ts'
- 'src/app.ts'
jobs:
probe:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install dependencies
run: npm install

- name: Probe Etherscan V2 Base getcontractcreation
run: npx tsx scripts/probe-etherscan-base-deployment.ts
env:
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
73 changes: 73 additions & 0 deletions scripts/probe-etherscan-base-deployment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* One-off probe: verify Etherscan V2 getcontractcreation works for Base
* with the addresses from issue #806 / the failing Blockscout batch.
*
* Usage (CI): npx tsx scripts/probe-etherscan-base-deployment.ts
*/
import { Address } from 'viem'
import { base } from 'viem/chains'
import EtherscanApi from '../src/services/etherscanApi'

const ADDRESSES = [
'0xb6862ada846003e66e2bc2de1e1acc4687f2ccb3',
'0x1be88d8b5696a69378973729523f3a135921854b',
'0x586d1d28d5f8aed81da8aef7016c4108f7a4ce51', // rate provider from issue #806
'0xe32ba1c7546cf0793ce88ddcb7883d37fba83789',
'0x9c2dcdbdb3f0a0f628d1112bbcabd9ae75353df3', // asset from issue #806
] as Address[]

async function probeRaw(apiKey: string) {
const url =
`https://api.etherscan.io/v2/api?chainid=${base.id}` +
`&module=contract&action=getcontractcreation` +
`&contractaddresses=${ADDRESSES.join(',')}` +
`&apikey=${apiKey}`

const response = await fetch(url)
const data = await response.json()
console.log(`HTTP ${response.status}`)
console.log(`status=${data.status} message=${data.message}`)
console.log(`result_type=${Array.isArray(data.result) ? 'array' : typeof data.result}`)
if (typeof data.result === 'string') {
console.log(`result=${data.result}`)
} else if (Array.isArray(data.result)) {
console.log(`result_len=${data.result.length}`)
}
return data
}

async function main() {
const apiKey = process.env.ETHERSCAN_API_KEY
if (!apiKey) {
throw new Error('ETHERSCAN_API_KEY is not set')
}

console.log(`Probing Etherscan V2 getcontractcreation on Base for ${ADDRESSES.length} addresses...`)
const raw = await probeRaw(apiKey)

if (raw.status !== '1' || !Array.isArray(raw.result)) {
throw new Error(
`Etherscan V2 Base getcontractcreation failed: status=${raw.status} message=${raw.message} result=${typeof raw.result === 'string' ? raw.result : JSON.stringify(raw.result)?.slice(0, 200)}`,
)
}

const api = new EtherscanApi(base, apiKey)
const results = await api.getDeploymentTxHashAndBlock(ADDRESSES)

for (const result of results) {
const ok = Boolean(result.deploymentTxHash)
console.log(
`${ok ? 'OK' : 'MISSING'} ${result.address} tx=${result.deploymentTxHash || 'n/a'} block=${result.blockNumber || 'n/a'}`,
)
if (!ok) {
throw new Error(`Missing deployment tx for ${result.address}`)
}
}

console.log(`SUCCESS: Etherscan V2 returned deployment data for all ${results.length} Base addresses`)
}

main().catch((error) => {
console.error('PROBE FAILED:', error instanceof Error ? error.message : error)
process.exit(1)
})
32 changes: 29 additions & 3 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,36 @@ class RateProviderDataService {
public async getDeploymentBlocks(addresses: Address[]): Promise<{ address: Address; deploymentTxHash: Hex }[]> {
const chainApi = this.getApiForFunction('getDeploymentBlocks')

// The addresses length can be arbitrary but the etherscan API only allows 5 addresses at a time
// so we need to chunk the addresses and call the API sequentially
try {
return await this.fetchDeploymentBlocksFromApi(chainApi, addresses)
} catch (error) {
// Base/Optimism use Blockscout for getDeploymentBlocks, but that endpoint is often
// flaky (HTTP 500) or rate-limited. Fall back to Etherscan V2 when a key is available.
const canFallbackToEtherscan =
(this.chain.id === base.id || this.chain.id === optimism.id) && Boolean(this.apiKey)

if (!canFallbackToEtherscan) {
throw error
}

console.warn(
`Blockscout getDeploymentBlocks failed for ${this.chain.name}; falling back to Etherscan V2:`,
error instanceof Error ? error.message : error,
)
return await this.fetchDeploymentBlocksFromApi(new EtherscanApi(this.chain, this.apiKey), addresses)
}
}

/**
* Chunks addresses and fetches deployment info from the given explorer API.
* Explorer APIs typically allow at most 5 addresses per getcontractcreation call.
*/
private async fetchDeploymentBlocksFromApi(
chainApi: ChainApi,
addresses: Address[],
): Promise<{ address: Address; deploymentTxHash: Hex }[]> {
const chunkSize = 5
const txHashes = []
const txHashes: { address: Address; deploymentTxHash: Hex }[] = []

for (let i = 0; i < addresses.length; i += chunkSize) {
const chunk = addresses.slice(i, i + chunkSize)
Expand Down
Loading