Skip to content

Commit 8fa7f93

Browse files
committed
Replace Etherscan ABI lookups with Sourcify
Etherscan requires an API key for ABI lookup and other operations. Sourcify (https://sourcify.dev) is an open-source decentralized alternative.
1 parent d13bc5e commit 8fa7f93

File tree

2 files changed

+48
-25
lines changed

2 files changed

+48
-25
lines changed

packages/cli/src/command-helpers/abi.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ import { withSpinner } from './spinner';
77

88
const logger = debugFactory('graph-cli:abi-helpers');
99

10+
export const loadAbiFromSourcify = async (
11+
ABICtor: typeof ABI,
12+
network: string,
13+
address: string,
14+
): Promise<ABI> =>
15+
await withSpinner(
16+
`Fetching ABI from Sourcify`,
17+
`Failed to fetch ABI from Sourcify`,
18+
`Warnings while fetching ABI from Sourcify`,
19+
async () => {
20+
const chainId = await getSourcifyChainId(network);
21+
const result = await fetch(`https://repo.sourcify.dev/contracts/full_match/${chainId}/${address}/metadata.json`);
22+
const json = await result.json();
23+
24+
// Etherscan returns a JSON object that has a `status`, a `message` and
25+
// a `result` field. The `status` is '0' in case of errors and '1' in
26+
// case of success
27+
if (result.ok) {
28+
return new ABICtor('Contract', undefined, immutable.fromJS(json.output.abi));
29+
}
30+
throw new Error('ABI not found, try loading it from a local file');
31+
},
32+
);
33+
1034
export const loadAbiFromEtherscan = async (
1135
ABICtor: typeof ABI,
1236
network: string,
@@ -200,6 +224,19 @@ export const loadAbiFromBlockScout = async (
200224
},
201225
);
202226

227+
const getSourcifyChainId = async (network: string) => {
228+
const result = await fetch('https://sourcify.dev/server/chains');
229+
const json = await result.json();
230+
231+
// Can fail if network name doesn't follow https://chainlist.org name convention
232+
const match = json.find((e: any) => e.name.toLowerCase().includes(network.replace('-', ' ')));
233+
234+
if (match)
235+
return match.chainId;
236+
else
237+
throw new Error(`Could not find chain id for "${network}"`);
238+
};
239+
203240
const getEtherscanLikeAPIUrl = (network: string) => {
204241
switch (network) {
205242
case 'mainnet':
@@ -334,8 +371,6 @@ const getEtherscanLikeAPIUrl = (network: string) => {
334371
return 'https://api.routescan.io/v2/network/testnet/evm/9728/etherscan/api';
335372
case 'fuse-testnet':
336373
return 'https://explorer.fusespark.io/api';
337-
case 'rootstock-testnet':
338-
return 'https://rootstock-testnet.blockscout.com/api';
339374
default:
340375
return `https://api-${network}.etherscan.io/api`;
341376
}
@@ -484,12 +519,6 @@ const getPublicRPCEndpoint = (network: string) => {
484519
return 'https://testnet.bnb.boba.network';
485520
case 'fuse-testnet':
486521
return 'https://rpc.fusespark.io';
487-
case 'rootstock-testnet':
488-
return 'https://public-node.testnet.rsk.co';
489-
case 'kaia':
490-
return 'https://public-en.node.kaia.io';
491-
case 'kaia-testnet':
492-
return 'https://public-en.kairos.node.kaia.io';
493522
default:
494523
throw new Error(`Unknown network: ${network}`);
495524
}

packages/cli/src/commands/init.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { filesystem, prompt, system } from 'gluegun';
66
import { Args, Command, Flags, ux } from '@oclif/core';
77
import {
88
loadAbiFromBlockScout,
9-
loadAbiFromEtherscan,
9+
loadAbiFromSourcify,
1010
loadContractNameForAddress,
1111
loadStartBlockForContract,
1212
} from '../command-helpers/abi';
@@ -292,7 +292,7 @@ export default class InitCommand extends Command {
292292
if (network === 'poa-core') {
293293
abi = await loadAbiFromBlockScout(ABI, network, fromContract);
294294
} else {
295-
abi = await loadAbiFromEtherscan(ABI, network, fromContract);
295+
abi = await loadAbiFromSourcify(ABI, network, fromContract);
296296
}
297297
} catch (e) {
298298
process.exitCode = 1;
@@ -544,7 +544,7 @@ async function processInitForm(
544544
}
545545
| undefined
546546
> {
547-
let abiFromEtherscan: EthereumABI | undefined = undefined;
547+
let abiFromSourcify: EthereumABI | undefined = undefined;
548548

549549
try {
550550
const { protocol } = await prompt.ask<{ protocol: ProtocolName }>({
@@ -705,17 +705,11 @@ async function processInitForm(
705705

706706
const ABI = protocolInstance.getABI();
707707

708-
// Try loading the ABI from Etherscan, if none was provided
708+
// Try loading the ABI from Sourcify, if none was provided
709709
if (protocolInstance.hasABIs() && !initAbi) {
710-
if (network === 'poa-core') {
711-
abiFromEtherscan = await retryWithPrompt(() =>
712-
loadAbiFromBlockScout(ABI, network, value),
713-
);
714-
} else {
715-
abiFromEtherscan = await retryWithPrompt(() =>
716-
loadAbiFromEtherscan(ABI, network, value),
717-
);
718-
}
710+
abiFromSourcify = await retryWithPrompt(() =>
711+
loadAbiFromSourcify(ABI, network, value),
712+
);
719713
}
720714
// If startBlock is not set, try to load it.
721715
if (!initStartBlock) {
@@ -765,11 +759,11 @@ async function processInitForm(
765759
skip: () =>
766760
!protocolInstance.hasABIs() ||
767761
initFromExample !== undefined ||
768-
abiFromEtherscan !== undefined ||
762+
abiFromSourcify !== undefined ||
769763
isSubstreams ||
770764
!!initAbiPath,
771765
validate: async (value: string) => {
772-
if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs()) {
766+
if (initFromExample || abiFromSourcify || !protocolInstance.hasABIs()) {
773767
return true;
774768
}
775769

@@ -791,7 +785,7 @@ async function processInitForm(
791785
}
792786
},
793787
result: async (value: string) => {
794-
if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs()) {
788+
if (initFromExample || abiFromSourcify || !protocolInstance.hasABIs()) {
795789
return null;
796790
}
797791
const ABI = protocolInstance.getABI();
@@ -853,7 +847,7 @@ async function processInitForm(
853847
]);
854848

855849
return {
856-
abi: abiFromEtherscan || abiFromFile,
850+
abi: abiFromSourcify || abiFromFile,
857851
protocolInstance,
858852
subgraphName,
859853
directory,

0 commit comments

Comments
 (0)