@openzeppelin/ui-builder-adapter-evm 1.2.0 → 1.4.0
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.
- package/README.md +33 -18
- package/dist/index.cjs +4651 -4448
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -226
- package/dist/index.d.ts +12 -226
- package/dist/index.js +4737 -4535
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/adapter-parsing.test.ts +4 -3
- package/src/__tests__/getDefaultServiceConfig.test.ts +185 -0
- package/src/__tests__/mocks/mock-network-configs.ts +1 -1
- package/src/__tests__/provenanceLinks.test.ts +6 -4
- package/src/__tests__/providerSelection.test.ts +5 -4
- package/src/__tests__/timeouts.test.ts +5 -3
- package/src/__tests__/wallet-connect.test.ts +2 -2
- package/src/adapter.ts +61 -107
- package/src/configuration/execution.ts +1 -52
- package/src/configuration/index.ts +2 -3
- package/src/configuration/network-services.ts +47 -60
- package/src/index.ts +22 -13
- package/src/networks/index.ts +2 -1
- package/src/networks/mainnet.ts +1 -1
- package/src/networks/testnet.ts +1 -1
- package/src/query/adapter-query.ts +72 -0
- package/src/query/index.ts +2 -2
- package/src/transaction/components/useEvmRelayerOptions.ts +5 -3
- package/src/transaction/index.ts +1 -5
- package/src/types/artifacts.ts +5 -30
- package/src/types/providers.ts +7 -18
- package/src/wallet/components/EvmWalletUiRoot.tsx +1 -1
- package/src/wallet/evmUiKitManager.ts +26 -129
- package/src/wallet/hooks/index.ts +0 -1
- package/src/wallet/implementation/wagmi-implementation.ts +45 -577
- package/src/wallet/index.ts +2 -3
- package/src/wallet/rainbowkit/__tests__/export-service.test.ts +1 -2
- package/src/wallet/rainbowkit/componentFactory.ts +10 -8
- package/src/wallet/rainbowkit/components.tsx +16 -133
- package/src/wallet/rainbowkit/index.ts +27 -5
- package/src/wallet/utils/__tests__/uiKitService.test.ts +5 -1
- package/src/wallet/utils/connection.ts +8 -52
- package/src/wallet/utils/index.ts +0 -2
- package/src/wallet/utils/uiKitService.ts +7 -3
- package/src/wallet/utils/walletImplementationManager.ts +5 -4
- package/src/wallet/utils.ts +1 -65
- package/src/abi/__tests__/etherscan-v2.test.ts +0 -117
- package/src/abi/__tests__/transformer.test.ts +0 -342
- package/src/abi/comparison.ts +0 -389
- package/src/abi/etherscan-v2.ts +0 -243
- package/src/abi/etherscan.ts +0 -158
- package/src/abi/index.ts +0 -7
- package/src/abi/loader.ts +0 -415
- package/src/abi/sourcify.ts +0 -75
- package/src/abi/transformer.ts +0 -163
- package/src/abi/types.ts +0 -101
- package/src/configuration/__tests__/explorer.test.ts +0 -174
- package/src/configuration/__tests__/rpc.test.ts +0 -176
- package/src/configuration/explorer.ts +0 -243
- package/src/configuration/rpc.ts +0 -257
- package/src/mapping/__tests__/field-generator.test.ts +0 -137
- package/src/mapping/__tests__/type-mapper.test.ts +0 -139
- package/src/mapping/constants.ts +0 -57
- package/src/mapping/field-generator.ts +0 -115
- package/src/mapping/index.ts +0 -4
- package/src/mapping/type-mapper.ts +0 -80
- package/src/proxy/detection.ts +0 -465
- package/src/query/handler.ts +0 -227
- package/src/query/view-checker.ts +0 -10
- package/src/transaction/eoa.ts +0 -98
- package/src/transaction/execution-strategy.ts +0 -33
- package/src/transaction/formatter.ts +0 -101
- package/src/transaction/relayer.ts +0 -380
- package/src/transaction/sender.ts +0 -185
- package/src/transform/index.ts +0 -3
- package/src/transform/input-parser.ts +0 -177
- package/src/transform/output-formatter.ts +0 -64
- package/src/types/__tests__/artifacts.test.ts +0 -105
- package/src/types.ts +0 -92
- package/src/utils/__tests__/artifacts.test.ts +0 -81
- package/src/utils/artifacts.ts +0 -30
- package/src/utils/formatting.ts +0 -25
- package/src/utils/gas.ts +0 -17
- package/src/utils/index.ts +0 -6
- package/src/utils/json.ts +0 -19
- package/src/utils/validation.ts +0 -10
- package/src/validation/eoa.ts +0 -33
- package/src/validation/index.ts +0 -2
- package/src/validation/relayer.ts +0 -13
- package/src/wallet/__tests__/utils.test.ts +0 -149
- package/src/wallet/components/account/AccountDisplay.tsx +0 -52
- package/src/wallet/components/connect/ConnectButton.tsx +0 -125
- package/src/wallet/components/connect/ConnectorDialog.tsx +0 -140
- package/src/wallet/components/index.ts +0 -4
- package/src/wallet/components/network/NetworkSwitcher.tsx +0 -90
- package/src/wallet/context/index.ts +0 -1
- package/src/wallet/context/wagmi-context.tsx +0 -7
- package/src/wallet/hooks/useIsWagmiProviderInitialized.ts +0 -11
- package/src/wallet/rainbowkit/config-generator.ts +0 -56
- package/src/wallet/rainbowkit/config-service.ts +0 -169
- package/src/wallet/rainbowkit/export-service.ts +0 -18
- package/src/wallet/rainbowkit/rainbowkitAssetManager.ts +0 -74
- package/src/wallet/rainbowkit/types.ts +0 -74
- package/src/wallet/rainbowkit/utils.ts +0 -96
- package/src/wallet/services/configResolutionService.ts +0 -65
- package/src/wallet/utils/SafeWagmiComponent.tsx +0 -72
- package/src/wallet/utils/filterWalletComponents.ts +0 -89
package/src/abi/comparison.ts
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ABI comparison service for EVM contracts
|
|
3
|
-
* Provides detailed analysis of differences between ABIs
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Abi } from 'viem';
|
|
7
|
-
|
|
8
|
-
import { logger, simpleHash } from '@openzeppelin/ui-utils';
|
|
9
|
-
|
|
10
|
-
import type { AbiComparisonResult, AbiDifference, AbiValidationResult } from './types';
|
|
11
|
-
import { isValidAbiArray } from './types';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Service for comparing and validating EVM ABIs
|
|
15
|
-
*/
|
|
16
|
-
export class AbiComparisonService {
|
|
17
|
-
/**
|
|
18
|
-
* Compares two ABIs and returns detailed difference analysis
|
|
19
|
-
*/
|
|
20
|
-
public compareAbis(abi1: string, abi2: string): AbiComparisonResult {
|
|
21
|
-
try {
|
|
22
|
-
const validation1 = this.validateAbi(abi1);
|
|
23
|
-
const validation2 = this.validateAbi(abi2);
|
|
24
|
-
|
|
25
|
-
if (!validation1.valid || !validation2.valid) {
|
|
26
|
-
return {
|
|
27
|
-
identical: false,
|
|
28
|
-
differences: [],
|
|
29
|
-
severity: 'breaking',
|
|
30
|
-
summary: 'One or both ABIs are invalid and cannot be compared',
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const normalized1 = this.normalizeAbi(validation1.normalizedAbi!);
|
|
35
|
-
const normalized2 = this.normalizeAbi(validation2.normalizedAbi!);
|
|
36
|
-
|
|
37
|
-
const hash1 = simpleHash(JSON.stringify(normalized1));
|
|
38
|
-
const hash2 = simpleHash(JSON.stringify(normalized2));
|
|
39
|
-
|
|
40
|
-
if (hash1 === hash2) {
|
|
41
|
-
return {
|
|
42
|
-
identical: true,
|
|
43
|
-
differences: [],
|
|
44
|
-
severity: 'none',
|
|
45
|
-
summary: 'ABIs are identical',
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const differences = this.findDifferences(normalized1, normalized2);
|
|
50
|
-
const severity = this.calculateSeverity(differences);
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
identical: false,
|
|
54
|
-
differences,
|
|
55
|
-
severity,
|
|
56
|
-
summary: this.generateSummary(differences),
|
|
57
|
-
};
|
|
58
|
-
} catch (error) {
|
|
59
|
-
logger.error('ABI comparison failed:', (error as Error).message);
|
|
60
|
-
return {
|
|
61
|
-
identical: false,
|
|
62
|
-
differences: [],
|
|
63
|
-
severity: 'breaking',
|
|
64
|
-
summary: `Comparison failed: ${(error as Error).message}`,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Validates ABI structure and format
|
|
71
|
-
*/
|
|
72
|
-
public validateAbi(abiString: string): AbiValidationResult {
|
|
73
|
-
const errors: string[] = [];
|
|
74
|
-
const warnings: string[] = [];
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
// Parse JSON
|
|
78
|
-
const abi = JSON.parse(abiString);
|
|
79
|
-
|
|
80
|
-
if (!Array.isArray(abi)) {
|
|
81
|
-
errors.push('ABI must be an array');
|
|
82
|
-
return { valid: false, errors, warnings };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Empty ABI arrays are not valid contract definitions
|
|
86
|
-
if (abi.length === 0) {
|
|
87
|
-
errors.push(
|
|
88
|
-
'ABI array cannot be empty - contract must have at least one function, event, or constructor'
|
|
89
|
-
);
|
|
90
|
-
return { valid: false, errors, warnings };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Validate each ABI item
|
|
94
|
-
for (let i = 0; i < abi.length; i++) {
|
|
95
|
-
const item = abi[i];
|
|
96
|
-
|
|
97
|
-
if (!item.type) {
|
|
98
|
-
errors.push(`Item ${i}: Missing 'type' field`);
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
!['function', 'event', 'constructor', 'error', 'fallback', 'receive'].includes(item.type)
|
|
104
|
-
) {
|
|
105
|
-
errors.push(`Item ${i}: Invalid type '${item.type}'`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (item.type === 'function' && !item.name) {
|
|
109
|
-
errors.push(`Item ${i}: Function missing 'name' field`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if ((item.type === 'function' || item.type === 'event') && !Array.isArray(item.inputs)) {
|
|
113
|
-
errors.push(`Item ${i}: Missing or invalid 'inputs' array`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (item.type === 'function' && !Array.isArray(item.outputs)) {
|
|
117
|
-
warnings.push(`Item ${i}: Function missing 'outputs' array`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Additional viem-specific validation
|
|
122
|
-
if (errors.length === 0 && !isValidAbiArray(abi)) {
|
|
123
|
-
errors.push('ABI does not conform to expected format');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
valid: errors.length === 0,
|
|
128
|
-
errors,
|
|
129
|
-
warnings,
|
|
130
|
-
normalizedAbi: errors.length === 0 ? (abi as Abi) : undefined,
|
|
131
|
-
};
|
|
132
|
-
} catch (parseError) {
|
|
133
|
-
errors.push(`Invalid JSON: ${(parseError as Error).message}`);
|
|
134
|
-
return { valid: false, errors, warnings };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Creates deterministic hash of ABI for quick comparison
|
|
140
|
-
*/
|
|
141
|
-
public hashAbi(abiString: string): string {
|
|
142
|
-
try {
|
|
143
|
-
const validation = this.validateAbi(abiString);
|
|
144
|
-
if (!validation.valid || !validation.normalizedAbi) {
|
|
145
|
-
throw new Error('Cannot hash invalid ABI');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const normalized = this.normalizeAbi(validation.normalizedAbi);
|
|
149
|
-
const normalizedString = JSON.stringify(normalized);
|
|
150
|
-
|
|
151
|
-
return simpleHash(normalizedString);
|
|
152
|
-
} catch (error) {
|
|
153
|
-
logger.error('ABI hashing failed:', (error as Error).message);
|
|
154
|
-
throw new Error(`Failed to hash ABI: ${(error as Error).message}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Normalizes ABI for consistent comparison
|
|
160
|
-
*/
|
|
161
|
-
private normalizeAbi(abi: Abi): Abi {
|
|
162
|
-
return abi
|
|
163
|
-
.map((item) => {
|
|
164
|
-
// Remove ordering-dependent fields and sort inputs/outputs
|
|
165
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
-
const normalized: any = { ...item };
|
|
167
|
-
|
|
168
|
-
if (normalized.inputs) {
|
|
169
|
-
normalized.inputs = [...normalized.inputs].sort((a, b) =>
|
|
170
|
-
(a.name || '').localeCompare(b.name || '')
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (normalized.outputs) {
|
|
175
|
-
normalized.outputs = [...normalized.outputs].sort((a, b) =>
|
|
176
|
-
(a.name || '').localeCompare(b.name || '')
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return normalized;
|
|
181
|
-
})
|
|
182
|
-
.sort((a, b) => {
|
|
183
|
-
// Sort by type first, then by name
|
|
184
|
-
const typeOrder = {
|
|
185
|
-
constructor: 0,
|
|
186
|
-
fallback: 1,
|
|
187
|
-
receive: 2,
|
|
188
|
-
function: 3,
|
|
189
|
-
event: 4,
|
|
190
|
-
error: 5,
|
|
191
|
-
};
|
|
192
|
-
const aOrder = typeOrder[a.type as keyof typeof typeOrder] ?? 99;
|
|
193
|
-
const bOrder = typeOrder[b.type as keyof typeof typeOrder] ?? 99;
|
|
194
|
-
|
|
195
|
-
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
196
|
-
|
|
197
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
|
-
const aName = (a as any).name || '';
|
|
199
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
|
-
const bName = (b as any).name || '';
|
|
201
|
-
return aName.localeCompare(bName);
|
|
202
|
-
}) as Abi;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Finds detailed differences between two normalized ABIs
|
|
207
|
-
*/
|
|
208
|
-
private findDifferences(abi1: Abi, abi2: Abi): AbiDifference[] {
|
|
209
|
-
const differences: AbiDifference[] = [];
|
|
210
|
-
|
|
211
|
-
// Create maps for efficient lookup
|
|
212
|
-
const map1 = this.createAbiMap(abi1);
|
|
213
|
-
const map2 = this.createAbiMap(abi2);
|
|
214
|
-
|
|
215
|
-
// Find removed items
|
|
216
|
-
for (const [key, item] of map1) {
|
|
217
|
-
if (!map2.has(key)) {
|
|
218
|
-
differences.push({
|
|
219
|
-
type: 'removed',
|
|
220
|
-
section: item.type as AbiDifference['section'],
|
|
221
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
|
-
name: (item as any).name || item.type,
|
|
223
|
-
details: `${item.type} was removed`,
|
|
224
|
-
impact: this.calculateImpact(item.type, 'removed'),
|
|
225
|
-
oldSignature: this.generateSignature(item),
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Find added items
|
|
231
|
-
for (const [key, item] of map2) {
|
|
232
|
-
if (!map1.has(key)) {
|
|
233
|
-
differences.push({
|
|
234
|
-
type: 'added',
|
|
235
|
-
section: item.type as AbiDifference['section'],
|
|
236
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
237
|
-
name: (item as any).name || item.type,
|
|
238
|
-
details: `${item.type} was added`,
|
|
239
|
-
impact: this.calculateImpact(item.type, 'added'),
|
|
240
|
-
newSignature: this.generateSignature(item),
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Find modified items
|
|
246
|
-
for (const [key, item1] of map1) {
|
|
247
|
-
const item2 = map2.get(key);
|
|
248
|
-
if (item2 && !this.itemsEqual(item1, item2)) {
|
|
249
|
-
differences.push({
|
|
250
|
-
type: 'modified',
|
|
251
|
-
section: item1.type as AbiDifference['section'],
|
|
252
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
-
name: (item1 as any).name || item1.type,
|
|
254
|
-
details: `${item1.type} signature changed`,
|
|
255
|
-
impact: this.calculateImpact(item1.type, 'modified'),
|
|
256
|
-
oldSignature: this.generateSignature(item1),
|
|
257
|
-
newSignature: this.generateSignature(item2),
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return differences;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
266
|
-
private createAbiMap(abi: Abi): Map<string, any> {
|
|
267
|
-
const map = new Map();
|
|
268
|
-
|
|
269
|
-
for (const item of abi) {
|
|
270
|
-
const key = this.generateItemKey(item);
|
|
271
|
-
map.set(key, item);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return map;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
278
|
-
private generateItemKey(item: any): string {
|
|
279
|
-
if (item.type === 'constructor' || item.type === 'fallback' || item.type === 'receive') {
|
|
280
|
-
return item.type;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const name = item.name || '';
|
|
284
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
|
-
const inputs = item.inputs?.map((input: any) => input.type).join(',') || '';
|
|
286
|
-
return `${item.type}:${name}(${inputs})`;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
290
|
-
private generateSignature(item: any): string {
|
|
291
|
-
if (item.type === 'constructor') {
|
|
292
|
-
const inputs =
|
|
293
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
294
|
-
item.inputs?.map((input: any) => `${input.type} ${input.name || ''}`).join(', ') || '';
|
|
295
|
-
return `constructor(${inputs})`;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (item.type === 'fallback' || item.type === 'receive') {
|
|
299
|
-
return item.type + '()';
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (item.type === 'function') {
|
|
303
|
-
const inputs =
|
|
304
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
305
|
-
item.inputs?.map((input: any) => `${input.type} ${input.name || ''}`).join(', ') || '';
|
|
306
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
307
|
-
const outputs = item.outputs?.map((output: any) => output.type).join(', ') || '';
|
|
308
|
-
const mutability = item.stateMutability ? ` ${item.stateMutability}` : '';
|
|
309
|
-
return `function ${item.name}(${inputs})${mutability}${outputs ? ` returns (${outputs})` : ''}`;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (item.type === 'event') {
|
|
313
|
-
const inputs =
|
|
314
|
-
item.inputs
|
|
315
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
316
|
-
?.map((input: any) => {
|
|
317
|
-
const indexed = input.indexed ? ' indexed' : '';
|
|
318
|
-
return `${input.type}${indexed} ${input.name || ''}`;
|
|
319
|
-
})
|
|
320
|
-
.join(', ') || '';
|
|
321
|
-
return `event ${item.name}(${inputs})`;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return JSON.stringify(item);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
328
|
-
private itemsEqual(item1: any, item2: any): boolean {
|
|
329
|
-
return JSON.stringify(item1) === JSON.stringify(item2);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private calculateImpact(
|
|
333
|
-
type: string,
|
|
334
|
-
changeType: 'added' | 'removed' | 'modified'
|
|
335
|
-
): 'low' | 'medium' | 'high' {
|
|
336
|
-
if (type === 'constructor' || type === 'fallback' || type === 'receive') {
|
|
337
|
-
return changeType === 'modified' ? 'high' : 'medium';
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (type === 'function') {
|
|
341
|
-
if (changeType === 'removed') return 'high'; // Breaking: removes functionality
|
|
342
|
-
if (changeType === 'modified') return 'medium'; // Major: changes behavior
|
|
343
|
-
if (changeType === 'added') return 'low'; // Minor: adds functionality
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (type === 'event') {
|
|
347
|
-
return 'low'; // Events don't break existing functionality
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (type === 'error') {
|
|
351
|
-
return 'low'; // Custom errors don't break existing functionality
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return 'medium';
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private calculateSeverity(differences: AbiDifference[]): 'none' | 'minor' | 'major' | 'breaking' {
|
|
358
|
-
if (differences.length === 0) return 'none';
|
|
359
|
-
|
|
360
|
-
const hasHighImpact = differences.some((d) => d.impact === 'high');
|
|
361
|
-
const hasMediumImpact = differences.some((d) => d.impact === 'medium');
|
|
362
|
-
const hasRemovedFunctions = differences.some(
|
|
363
|
-
(d) => d.type === 'removed' && d.section === 'function'
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
if (hasRemovedFunctions || hasHighImpact) return 'breaking';
|
|
367
|
-
if (hasMediumImpact) return 'major';
|
|
368
|
-
return 'minor';
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private generateSummary(differences: AbiDifference[]): string {
|
|
372
|
-
const counts = {
|
|
373
|
-
added: differences.filter((d) => d.type === 'added').length,
|
|
374
|
-
removed: differences.filter((d) => d.type === 'removed').length,
|
|
375
|
-
modified: differences.filter((d) => d.type === 'modified').length,
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const parts: string[] = [];
|
|
379
|
-
if (counts.added > 0) parts.push(`${counts.added} added`);
|
|
380
|
-
if (counts.removed > 0) parts.push(`${counts.removed} removed`);
|
|
381
|
-
if (counts.modified > 0) parts.push(`${counts.modified} modified`);
|
|
382
|
-
|
|
383
|
-
const summary = parts.join(', ');
|
|
384
|
-
return `${summary}`;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Export singleton instance
|
|
389
|
-
export const abiComparisonService = new AbiComparisonService();
|
package/src/abi/etherscan-v2.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import type { ContractSchema } from '@openzeppelin/ui-types';
|
|
2
|
-
import { logger } from '@openzeppelin/ui-utils';
|
|
3
|
-
|
|
4
|
-
import { resolveExplorerConfig } from '../configuration/explorer';
|
|
5
|
-
import type { AbiItem, TypedEvmNetworkConfig } from '../types';
|
|
6
|
-
import { transformAbiToSchema } from './transformer';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Result type for Etherscan ABI loading that includes the original ABI string
|
|
10
|
-
*/
|
|
11
|
-
export interface EtherscanAbiResult {
|
|
12
|
-
schema: ContractSchema;
|
|
13
|
-
originalAbi: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Etherscan V2 unified API base URL
|
|
17
|
-
const ETHERSCAN_V2_BASE_URL = 'https://api.etherscan.io/v2/api';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Builds a V2 API URL for Etherscan-compatible explorers
|
|
21
|
-
*/
|
|
22
|
-
function buildV2ApiUrl(
|
|
23
|
-
chainId: number,
|
|
24
|
-
module: string,
|
|
25
|
-
action: string,
|
|
26
|
-
params: Record<string, string>,
|
|
27
|
-
apiKey?: string // Some explorers don't require an API key (routescan.io)
|
|
28
|
-
): string {
|
|
29
|
-
const url = new URL(ETHERSCAN_V2_BASE_URL);
|
|
30
|
-
url.searchParams.append('chainid', chainId.toString());
|
|
31
|
-
url.searchParams.append('module', module);
|
|
32
|
-
url.searchParams.append('action', action);
|
|
33
|
-
|
|
34
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
35
|
-
url.searchParams.append(key, value);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
if (apiKey) {
|
|
39
|
-
url.searchParams.append('apikey', apiKey);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return url.toString();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Checks if the network supports V2 API
|
|
47
|
-
*/
|
|
48
|
-
export function shouldUseV2Api(networkConfig: TypedEvmNetworkConfig): boolean {
|
|
49
|
-
if (!networkConfig.supportsEtherscanV2) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Fetches and parses an ABI from Etherscan V2 API using a contract address and network config.
|
|
58
|
-
*/
|
|
59
|
-
export async function loadAbiFromEtherscanV2(
|
|
60
|
-
address: string,
|
|
61
|
-
networkConfig: TypedEvmNetworkConfig
|
|
62
|
-
): Promise<EtherscanAbiResult> {
|
|
63
|
-
const explorerConfig = resolveExplorerConfig(networkConfig);
|
|
64
|
-
|
|
65
|
-
const url = buildV2ApiUrl(
|
|
66
|
-
networkConfig.chainId,
|
|
67
|
-
'contract',
|
|
68
|
-
'getabi',
|
|
69
|
-
{ address },
|
|
70
|
-
explorerConfig.apiKey
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
let response: Response;
|
|
74
|
-
try {
|
|
75
|
-
logger.info(
|
|
76
|
-
'loadAbiFromEtherscanV2',
|
|
77
|
-
`Fetching ABI from Etherscan V2 API for address: ${address} on chain ${networkConfig.chainId}`
|
|
78
|
-
);
|
|
79
|
-
response = await fetch(url);
|
|
80
|
-
} catch (networkError) {
|
|
81
|
-
logger.error(
|
|
82
|
-
'loadAbiFromEtherscanV2',
|
|
83
|
-
`Network error fetching ABI from Explorer V2 API: ${networkError}`
|
|
84
|
-
);
|
|
85
|
-
throw new Error(`Network error fetching ABI: ${(networkError as Error).message}`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!response.ok) {
|
|
89
|
-
logger.error(
|
|
90
|
-
'loadAbiFromEtherscanV2',
|
|
91
|
-
`Explorer V2 API request failed with status: ${response.status}`
|
|
92
|
-
);
|
|
93
|
-
throw new Error(`Explorer V2 API request failed: ${response.status} ${response.statusText}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let apiResult: { status: string; message: string; result: string };
|
|
97
|
-
try {
|
|
98
|
-
apiResult = await response.json();
|
|
99
|
-
} catch (jsonError) {
|
|
100
|
-
logger.error(
|
|
101
|
-
'loadAbiFromEtherscanV2',
|
|
102
|
-
`Failed to parse Explorer V2 API response as JSON: ${jsonError}`
|
|
103
|
-
);
|
|
104
|
-
throw new Error('Invalid JSON response received from Explorer V2 API.');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (apiResult.status !== '1') {
|
|
108
|
-
logger.warn(
|
|
109
|
-
'loadAbiFromEtherscanV2',
|
|
110
|
-
`Explorer V2 API error: Status ${apiResult.status}, Message: ${apiResult.message}, Result: ${apiResult.result}`
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// Handle specific V2 API error messages
|
|
114
|
-
if (apiResult.message?.includes('NOTOK')) {
|
|
115
|
-
if (apiResult.result?.includes('Invalid API Key')) {
|
|
116
|
-
throw new Error(
|
|
117
|
-
`Invalid API key for Etherscan V2. Please check your API key configuration.`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
if (apiResult.result?.includes('Contract source code not verified')) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
`Contract not verified on ${networkConfig.name} explorer (address: ${address}). ABI not available. You can provide the contract's ABI manually.`
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
if (apiResult.result?.includes('Invalid chain')) {
|
|
126
|
-
throw new Error(
|
|
127
|
-
`Chain ID ${networkConfig.chainId} is not supported by Etherscan V2 API. Please check if this chain is available.`
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
throw new Error(`Explorer V2 API Error: ${apiResult.result || apiResult.message}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Store the original raw ABI string before parsing
|
|
136
|
-
const originalAbiString = apiResult.result;
|
|
137
|
-
|
|
138
|
-
let abi: AbiItem[];
|
|
139
|
-
try {
|
|
140
|
-
abi = JSON.parse(originalAbiString);
|
|
141
|
-
if (!Array.isArray(abi)) {
|
|
142
|
-
throw new Error('Parsed ABI from Explorer V2 API is not an array.');
|
|
143
|
-
}
|
|
144
|
-
} catch (error) {
|
|
145
|
-
logger.error(
|
|
146
|
-
'loadAbiFromEtherscanV2',
|
|
147
|
-
`Failed to parse ABI JSON string from Explorer V2 API result: ${error}`
|
|
148
|
-
);
|
|
149
|
-
throw new Error(`Invalid ABI JSON received from Explorer V2 API: ${(error as Error).message}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
logger.info(
|
|
153
|
-
'loadAbiFromEtherscanV2',
|
|
154
|
-
`Successfully parsed ABI for ${networkConfig.name} with ${abi.length} items using V2 API.`
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// TODO: Fetch contract name?
|
|
158
|
-
const contractName = `Contract_${address.substring(0, 6)}`;
|
|
159
|
-
const schema = transformAbiToSchema(abi, contractName, address);
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
schema,
|
|
163
|
-
originalAbi: originalAbiString,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Test connection to Etherscan V2 API
|
|
169
|
-
*/
|
|
170
|
-
export async function testEtherscanV2Connection(
|
|
171
|
-
networkConfig: TypedEvmNetworkConfig,
|
|
172
|
-
apiKey?: string
|
|
173
|
-
): Promise<{
|
|
174
|
-
success: boolean;
|
|
175
|
-
latency?: number;
|
|
176
|
-
error?: string;
|
|
177
|
-
}> {
|
|
178
|
-
const startTime = Date.now();
|
|
179
|
-
|
|
180
|
-
const requiresApiKey = networkConfig.requiresExplorerApiKey ?? true;
|
|
181
|
-
|
|
182
|
-
if (requiresApiKey && !apiKey) {
|
|
183
|
-
return {
|
|
184
|
-
success: false,
|
|
185
|
-
error: 'API key is required for testing connection to this explorer',
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
// Test with a simple API call - get the latest block number
|
|
191
|
-
const url = buildV2ApiUrl(networkConfig.chainId, 'proxy', 'eth_blockNumber', {}, apiKey);
|
|
192
|
-
|
|
193
|
-
const response = await fetch(url);
|
|
194
|
-
const latency = Date.now() - startTime;
|
|
195
|
-
|
|
196
|
-
if (!response.ok) {
|
|
197
|
-
return {
|
|
198
|
-
success: false,
|
|
199
|
-
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
200
|
-
latency,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const data = await response.json();
|
|
205
|
-
|
|
206
|
-
// Check for API errors in the response
|
|
207
|
-
if (data.status === '0' && data.message) {
|
|
208
|
-
// Handle specific V2 API error messages
|
|
209
|
-
if (data.result?.includes('Invalid API Key')) {
|
|
210
|
-
return {
|
|
211
|
-
success: false,
|
|
212
|
-
error: 'Invalid API key. Please check your Etherscan API key.',
|
|
213
|
-
latency,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
if (data.result?.includes('Invalid chain')) {
|
|
217
|
-
return {
|
|
218
|
-
success: false,
|
|
219
|
-
error: `Chain ID ${networkConfig.chainId} is not supported by Etherscan V2 API.`,
|
|
220
|
-
latency,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
success: false,
|
|
226
|
-
error: data.result || data.message,
|
|
227
|
-
latency,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Success if we got a valid response
|
|
232
|
-
return {
|
|
233
|
-
success: true,
|
|
234
|
-
latency,
|
|
235
|
-
};
|
|
236
|
-
} catch (error) {
|
|
237
|
-
return {
|
|
238
|
-
success: false,
|
|
239
|
-
error: error instanceof Error ? error.message : 'Connection test failed',
|
|
240
|
-
latency: Date.now() - startTime,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
}
|