diff --git a/.changeset/chatty-apricots-study.md b/.changeset/chatty-apricots-study.md new file mode 100644 index 0000000000..92d63a4d4b --- /dev/null +++ b/.changeset/chatty-apricots-study.md @@ -0,0 +1,5 @@ +--- +"@wagmi/cli": patch +--- + +Retry failed requests in etherscan plugin diff --git a/packages/cli/src/plugins/etherscan.ts b/packages/cli/src/plugins/etherscan.ts index f2597171bd..a236819db9 100644 --- a/packages/cli/src/plugins/etherscan.ts +++ b/packages/cli/src/plugins/etherscan.ts @@ -3,7 +3,7 @@ import type { Abi } from 'abitype' import { Address as AddressSchema } from 'abitype/zod' import { camelCase } from 'change-case' import { join } from 'pathe' -import type { Address } from 'viem' +import { type Address, withRetry } from 'viem' import { z } from 'zod' import type { ContractConfig } from '../config.js' @@ -107,19 +107,22 @@ export function etherscan( } let abi: Abi | undefined - const implementationAddress = await (async () => { - if (!tryFetchProxyImplementation) return - const json = await globalThis - .fetch(buildUrl({ ...options, action: 'getsourcecode' })) - .then((res) => res.json()) - const parsed = await GetSourceCodeResponse.safeParseAsync(json) - if (!parsed.success) - throw fromZodError(parsed.error, { prefix: 'Invalid response' }) - if (parsed.data.status === '0') throw new Error(parsed.data.result) - if (!parsed.data.result[0]) return - abi = parsed.data.result[0].ABI - return parsed.data.result[0].Implementation as Address - })() + const implementationAddress = await withRetry( + async () => { + if (!tryFetchProxyImplementation) return + const json = await globalThis + .fetch(buildUrl({ ...options, action: 'getsourcecode' })) + .then((res) => res.json()) + const parsed = await GetSourceCodeResponse.safeParseAsync(json) + if (!parsed.success) + throw fromZodError(parsed.error, { prefix: 'Invalid response' }) + if (parsed.data.status === '0') throw new Error(parsed.data.result) + if (!parsed.data.result[0]) return + abi = parsed.data.result[0].ABI + return parsed.data.result[0].Implementation as Address + }, + { delay: 1000, retryCount: 2 }, + ) if (abi) { const cacheDir = getCacheDir() diff --git a/packages/cli/src/plugins/fetch.ts b/packages/cli/src/plugins/fetch.ts index 9e6b737e09..c1a1297ccd 100644 --- a/packages/cli/src/plugins/fetch.ts +++ b/packages/cli/src/plugins/fetch.ts @@ -3,6 +3,7 @@ import { homedir } from 'node:os' import { join } from 'pathe' import type { Abi } from 'abitype' +import { withRetry } from 'viem' import type { ContractConfig, Plugin } from '../config.js' import type { Compute, RequiredBy } from '../types.js' @@ -86,20 +87,27 @@ export function fetch(config: FetchConfig): FetchResult { if (cachedFile?.timestamp > Date.now()) abi = cachedFile.abi else { try { + let aborted = false const controller = new globalThis.AbortController() - const timeout = setTimeout( - () => controller.abort(), - timeoutDuration, - ) + const timeout = setTimeout(() => { + aborted = true + controller.abort() + }, timeoutDuration) const { url, init } = await request(contract) - const response = await globalThis.fetch(url, { - ...init, - signal: controller.signal, - }) - clearTimeout(timeout) + await withRetry( + async () => { + if (aborted) return + const response = await globalThis.fetch(url, { + ...init, + signal: controller.signal, + }) + clearTimeout(timeout) - abi = await parse({ response }) + abi = await parse({ response }) + }, + { delay: 1000, retryCount: 2 }, + ) await writeFile( cacheFilePath, `${JSON.stringify({ abi, timestamp }, undefined, 2)}\n`,