@sparkdotfi/abi-cli 0.2.0-20260129.3777b30a → 0.2.0-20260317.e49bd3f1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -26,12 +26,22 @@ export class AbiFetcherClient {
|
|
|
26
26
|
return result.abi;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
function createAbiLoader(domain, etherscanApiKey) {
|
|
30
|
+
// Etherscan v2 API's contract/getabi module is broken for Gnosis (returns "Unknown Exception"),
|
|
31
|
+
// so we use Blockscout directly for Gnosis.
|
|
32
|
+
if (domain === 'gnosis') {
|
|
33
|
+
return new whatsabi.loaders.BlockscoutABILoader({
|
|
34
|
+
baseURL: 'https://gnosis.blockscout.com/api',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return new whatsabi.loaders.EtherscanV2ABILoader({
|
|
32
38
|
apiKey: etherscanApiKey,
|
|
33
39
|
chainId: domainToChainId[domain],
|
|
34
40
|
});
|
|
41
|
+
}
|
|
42
|
+
async function loadAbiUsingWhatsAbi(args) {
|
|
43
|
+
const { address, domain, client, etherscanApiKey } = args;
|
|
44
|
+
const loader = createAbiLoader(domain, etherscanApiKey);
|
|
35
45
|
const data = await lazyPipe(() => whatsabi.autoload(address, {
|
|
36
46
|
provider: client,
|
|
37
47
|
followProxies: true,
|
|
@@ -44,7 +54,8 @@ async function loadAbiUsingWhatsAbi(args) {
|
|
|
44
54
|
if (data.abi.length === 0) {
|
|
45
55
|
throw new Error(`Abi not found for ${domain}:${address}`);
|
|
46
56
|
}
|
|
47
|
-
|
|
57
|
+
const loaderName = data.abiLoadedFrom?.name.toLowerCase() ?? '';
|
|
58
|
+
if (!data.abiLoadedFrom || !(loaderName.includes('etherscan') || loaderName.includes('blockscout'))) {
|
|
48
59
|
throw new Error(`Abi not found for ${domain}:${address}`);
|
|
49
60
|
}
|
|
50
61
|
return data;
|
|
@@ -12,10 +12,49 @@ export function getAbiForContract(abiRegistry, config, name) {
|
|
|
12
12
|
}));
|
|
13
13
|
assert(instances.length > 0);
|
|
14
14
|
const rootAbi = instances[0].abi;
|
|
15
|
-
const
|
|
15
|
+
const rootNormalized = normalizeAbiForComparison(rootAbi);
|
|
16
16
|
const notMatchingAbi = instances.find(({ abi }) => {
|
|
17
|
-
return
|
|
17
|
+
return normalizeAbiForComparison(abi) !== rootNormalized;
|
|
18
18
|
});
|
|
19
19
|
assert(!notMatchingAbi, `Found not matching abis: ${notMatchingAbi?.name}`);
|
|
20
20
|
return rootAbi;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes an ABI for comparison by removing source-specific differences:
|
|
24
|
+
* - Strips `internalType` fields (compiler metadata, not part of ABI spec)
|
|
25
|
+
* - Sorts entries by type + name for consistent ordering
|
|
26
|
+
* Different ABI sources (Etherscan, Blockscout) may return the same ABI with cosmetic differences.
|
|
27
|
+
*/
|
|
28
|
+
function normalizeAbiForComparison(abi) {
|
|
29
|
+
const normalized = abi.map((entry) => {
|
|
30
|
+
const clone = structuredClone(entry);
|
|
31
|
+
// Constructors and fallback/receive without stateMutability are implicitly "nonpayable"
|
|
32
|
+
if ((clone.type === 'constructor' || clone.type === 'fallback' || clone.type === 'receive') &&
|
|
33
|
+
!clone.stateMutability) {
|
|
34
|
+
clone.stateMutability = 'nonpayable';
|
|
35
|
+
}
|
|
36
|
+
return normalizeAbiEntry(clone);
|
|
37
|
+
});
|
|
38
|
+
normalized.sort((a, b) => {
|
|
39
|
+
const typeCmp = (a.type ?? '').localeCompare(b.type ?? '');
|
|
40
|
+
if (typeCmp !== 0)
|
|
41
|
+
return typeCmp;
|
|
42
|
+
return (a.name ?? '').localeCompare(b.name ?? '');
|
|
43
|
+
});
|
|
44
|
+
return JSON.stringify(normalized);
|
|
45
|
+
}
|
|
46
|
+
function normalizeAbiEntry(obj) {
|
|
47
|
+
if (Array.isArray(obj)) {
|
|
48
|
+
return obj.map(normalizeAbiEntry);
|
|
49
|
+
}
|
|
50
|
+
if (obj !== null && typeof obj === 'object') {
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key of Object.keys(obj).sort()) {
|
|
53
|
+
if (key === 'internalType')
|
|
54
|
+
continue;
|
|
55
|
+
result[key] = normalizeAbiEntry(obj[key]);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
return obj;
|
|
60
|
+
}
|