@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.
Files changed (105) hide show
  1. package/README.md +33 -18
  2. package/dist/index.cjs +4651 -4448
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +12 -226
  5. package/dist/index.d.ts +12 -226
  6. package/dist/index.js +4737 -4535
  7. package/dist/index.js.map +1 -1
  8. package/package.json +7 -6
  9. package/src/__tests__/adapter-parsing.test.ts +4 -3
  10. package/src/__tests__/getDefaultServiceConfig.test.ts +185 -0
  11. package/src/__tests__/mocks/mock-network-configs.ts +1 -1
  12. package/src/__tests__/provenanceLinks.test.ts +6 -4
  13. package/src/__tests__/providerSelection.test.ts +5 -4
  14. package/src/__tests__/timeouts.test.ts +5 -3
  15. package/src/__tests__/wallet-connect.test.ts +2 -2
  16. package/src/adapter.ts +61 -107
  17. package/src/configuration/execution.ts +1 -52
  18. package/src/configuration/index.ts +2 -3
  19. package/src/configuration/network-services.ts +47 -60
  20. package/src/index.ts +22 -13
  21. package/src/networks/index.ts +2 -1
  22. package/src/networks/mainnet.ts +1 -1
  23. package/src/networks/testnet.ts +1 -1
  24. package/src/query/adapter-query.ts +72 -0
  25. package/src/query/index.ts +2 -2
  26. package/src/transaction/components/useEvmRelayerOptions.ts +5 -3
  27. package/src/transaction/index.ts +1 -5
  28. package/src/types/artifacts.ts +5 -30
  29. package/src/types/providers.ts +7 -18
  30. package/src/wallet/components/EvmWalletUiRoot.tsx +1 -1
  31. package/src/wallet/evmUiKitManager.ts +26 -129
  32. package/src/wallet/hooks/index.ts +0 -1
  33. package/src/wallet/implementation/wagmi-implementation.ts +45 -577
  34. package/src/wallet/index.ts +2 -3
  35. package/src/wallet/rainbowkit/__tests__/export-service.test.ts +1 -2
  36. package/src/wallet/rainbowkit/componentFactory.ts +10 -8
  37. package/src/wallet/rainbowkit/components.tsx +16 -133
  38. package/src/wallet/rainbowkit/index.ts +27 -5
  39. package/src/wallet/utils/__tests__/uiKitService.test.ts +5 -1
  40. package/src/wallet/utils/connection.ts +8 -52
  41. package/src/wallet/utils/index.ts +0 -2
  42. package/src/wallet/utils/uiKitService.ts +7 -3
  43. package/src/wallet/utils/walletImplementationManager.ts +5 -4
  44. package/src/wallet/utils.ts +1 -65
  45. package/src/abi/__tests__/etherscan-v2.test.ts +0 -117
  46. package/src/abi/__tests__/transformer.test.ts +0 -342
  47. package/src/abi/comparison.ts +0 -389
  48. package/src/abi/etherscan-v2.ts +0 -243
  49. package/src/abi/etherscan.ts +0 -158
  50. package/src/abi/index.ts +0 -7
  51. package/src/abi/loader.ts +0 -415
  52. package/src/abi/sourcify.ts +0 -75
  53. package/src/abi/transformer.ts +0 -163
  54. package/src/abi/types.ts +0 -101
  55. package/src/configuration/__tests__/explorer.test.ts +0 -174
  56. package/src/configuration/__tests__/rpc.test.ts +0 -176
  57. package/src/configuration/explorer.ts +0 -243
  58. package/src/configuration/rpc.ts +0 -257
  59. package/src/mapping/__tests__/field-generator.test.ts +0 -137
  60. package/src/mapping/__tests__/type-mapper.test.ts +0 -139
  61. package/src/mapping/constants.ts +0 -57
  62. package/src/mapping/field-generator.ts +0 -115
  63. package/src/mapping/index.ts +0 -4
  64. package/src/mapping/type-mapper.ts +0 -80
  65. package/src/proxy/detection.ts +0 -465
  66. package/src/query/handler.ts +0 -227
  67. package/src/query/view-checker.ts +0 -10
  68. package/src/transaction/eoa.ts +0 -98
  69. package/src/transaction/execution-strategy.ts +0 -33
  70. package/src/transaction/formatter.ts +0 -101
  71. package/src/transaction/relayer.ts +0 -380
  72. package/src/transaction/sender.ts +0 -185
  73. package/src/transform/index.ts +0 -3
  74. package/src/transform/input-parser.ts +0 -177
  75. package/src/transform/output-formatter.ts +0 -64
  76. package/src/types/__tests__/artifacts.test.ts +0 -105
  77. package/src/types.ts +0 -92
  78. package/src/utils/__tests__/artifacts.test.ts +0 -81
  79. package/src/utils/artifacts.ts +0 -30
  80. package/src/utils/formatting.ts +0 -25
  81. package/src/utils/gas.ts +0 -17
  82. package/src/utils/index.ts +0 -6
  83. package/src/utils/json.ts +0 -19
  84. package/src/utils/validation.ts +0 -10
  85. package/src/validation/eoa.ts +0 -33
  86. package/src/validation/index.ts +0 -2
  87. package/src/validation/relayer.ts +0 -13
  88. package/src/wallet/__tests__/utils.test.ts +0 -149
  89. package/src/wallet/components/account/AccountDisplay.tsx +0 -52
  90. package/src/wallet/components/connect/ConnectButton.tsx +0 -125
  91. package/src/wallet/components/connect/ConnectorDialog.tsx +0 -140
  92. package/src/wallet/components/index.ts +0 -4
  93. package/src/wallet/components/network/NetworkSwitcher.tsx +0 -90
  94. package/src/wallet/context/index.ts +0 -1
  95. package/src/wallet/context/wagmi-context.tsx +0 -7
  96. package/src/wallet/hooks/useIsWagmiProviderInitialized.ts +0 -11
  97. package/src/wallet/rainbowkit/config-generator.ts +0 -56
  98. package/src/wallet/rainbowkit/config-service.ts +0 -169
  99. package/src/wallet/rainbowkit/export-service.ts +0 -18
  100. package/src/wallet/rainbowkit/rainbowkitAssetManager.ts +0 -74
  101. package/src/wallet/rainbowkit/types.ts +0 -74
  102. package/src/wallet/rainbowkit/utils.ts +0 -96
  103. package/src/wallet/services/configResolutionService.ts +0 -65
  104. package/src/wallet/utils/SafeWagmiComponent.tsx +0 -72
  105. package/src/wallet/utils/filterWalletComponents.ts +0 -89
@@ -1,158 +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 { loadAbiFromEtherscanV2, shouldUseV2Api } from './etherscan-v2';
7
- import { transformAbiToSchema } from './transformer';
8
-
9
- /**
10
- * Result type for Etherscan ABI loading that includes the original ABI string
11
- */
12
- export interface EtherscanAbiResult {
13
- schema: ContractSchema;
14
- originalAbi: string;
15
- }
16
-
17
- /**
18
- * IMPORTANT: Etherscan V1 API Deprecation Notice
19
- *
20
- * Etherscan has announced the deprecation of their V1 API endpoints in favor of the new V2 unified API.
21
- * The V2 API provides a single endpoint that works across all EVM chains, making it more scalable and
22
- * easier to maintain.
23
- *
24
- * Key differences:
25
- * - V1: Each chain has its own API endpoint (e.g., api.etherscan.io, api.bscscan.com, etc.)
26
- * - V2: Single unified endpoint (api.etherscan.io/v2/api) with chainId parameter
27
- *
28
- * Migration strategy:
29
- * - Networks with `supportsEtherscanV2: true` will automatically use V2 API
30
- * - Legacy networks without V2 support will continue using V1 until they're updated
31
- * - New networks should always be configured with V2 support
32
- *
33
- * The V1 API functions are maintained for backward compatibility but should be considered
34
- * deprecated and will be removed in a future release.
35
- */
36
-
37
- /**
38
- * Fetches and parses an ABI from Etherscan-compatible explorers using a contract address and network config.
39
- * Automatically selects V1 or V2 API based on network support and user configuration.
40
- */
41
- export async function loadAbiFromEtherscan(
42
- address: string,
43
- networkConfig: TypedEvmNetworkConfig
44
- ): Promise<EtherscanAbiResult> {
45
- if (shouldUseV2Api(networkConfig)) {
46
- logger.info('loadAbiFromEtherscan', 'Using V2 API for fetching ABI');
47
- return loadAbiFromEtherscanV2(address, networkConfig);
48
- }
49
-
50
- // Use V1 API (legacy)
51
- logger.info('loadAbiFromEtherscan', 'Using V1 API for fetching ABI');
52
- return loadAbiFromEtherscanV1(address, networkConfig);
53
- }
54
-
55
- /**
56
- * Fetches and parses an ABI from Etherscan V1 API using a contract address and network config.
57
- */
58
- export async function loadAbiFromEtherscanV1(
59
- address: string,
60
- networkConfig: TypedEvmNetworkConfig
61
- ): Promise<EtherscanAbiResult> {
62
- const explorerConfig = resolveExplorerConfig(networkConfig);
63
-
64
- if (!explorerConfig.apiUrl) {
65
- logger.error(
66
- 'loadAbiFromEtherscanV1',
67
- `API URL is missing for ${networkConfig.name} explorer.`
68
- );
69
- throw new Error(`Explorer API URL for ${networkConfig.name} is not configured.`);
70
- }
71
-
72
- const url = new URL(explorerConfig.apiUrl);
73
- url.searchParams.append('module', 'contract');
74
- url.searchParams.append('action', 'getabi');
75
- url.searchParams.append('address', address);
76
-
77
- // Only append API key if provided
78
- if (explorerConfig.apiKey) {
79
- url.searchParams.append('apikey', explorerConfig.apiKey);
80
- }
81
-
82
- let response: Response;
83
- try {
84
- logger.info(
85
- 'loadAbiFromEtherscanV1',
86
- `Fetching ABI from ${explorerConfig.apiUrl} for address: ${address}`
87
- );
88
- response = await fetch(url);
89
- } catch (networkError) {
90
- logger.error(
91
- 'loadAbiFromEtherscanV1',
92
- `Network error fetching ABI from Explorer API: ${networkError}`
93
- );
94
- throw new Error(`Network error fetching ABI: ${(networkError as Error).message}`);
95
- }
96
-
97
- if (!response.ok) {
98
- logger.error(
99
- 'loadAbiFromEtherscanV1',
100
- `Explorer API request failed with status: ${response.status}`
101
- );
102
- throw new Error(`Explorer API request failed: ${response.status} ${response.statusText}`);
103
- }
104
-
105
- let apiResult: { status: string; message: string; result: string };
106
- try {
107
- apiResult = await response.json();
108
- } catch (jsonError) {
109
- logger.error(
110
- 'loadAbiFromEtherscanV1',
111
- `Failed to parse Explorer API response as JSON: ${jsonError}`
112
- );
113
- throw new Error('Invalid JSON response received from Explorer API.');
114
- }
115
-
116
- if (apiResult.status !== '1') {
117
- logger.warn(
118
- 'loadAbiFromEtherscanV1',
119
- `Explorer API error: Status ${apiResult.status}, Message: ${apiResult.message}, Result: ${apiResult.result}`
120
- );
121
- if (apiResult.result?.includes('Contract source code not verified')) {
122
- throw new Error(
123
- `Contract not verified on ${networkConfig.name} explorer (address: ${address}). ABI not available. You can provide the contract's ABI manually.`
124
- );
125
- }
126
- throw new Error(`Explorer API Error: ${apiResult.result || apiResult.message}`);
127
- }
128
-
129
- // Store the original raw ABI string before parsing
130
- const originalAbiString = apiResult.result;
131
-
132
- let abi: AbiItem[];
133
- try {
134
- abi = JSON.parse(originalAbiString);
135
- if (!Array.isArray(abi)) {
136
- throw new Error('Parsed ABI from Explorer API is not an array.');
137
- }
138
- } catch (error) {
139
- logger.error(
140
- 'loadAbiFromEtherscanV1',
141
- `Failed to parse ABI JSON string from Explorer API result: ${error}`
142
- );
143
- throw new Error(`Invalid ABI JSON received from Explorer API: ${(error as Error).message}`);
144
- }
145
-
146
- logger.info(
147
- 'loadAbiFromEtherscanV1',
148
- `Successfully parsed ABI for ${networkConfig.name} with ${abi.length} items.`
149
- );
150
- // TODO: Fetch contract name?
151
- const contractName = `Contract_${address.substring(0, 6)}`;
152
- const schema = transformAbiToSchema(abi, contractName, address);
153
-
154
- return {
155
- schema,
156
- originalAbi: originalAbiString,
157
- };
158
- }
package/src/abi/index.ts DELETED
@@ -1,7 +0,0 @@
1
- // Barrel file for abi module
2
- export * from './transformer';
3
- export { loadAbiFromEtherscan } from './etherscan';
4
- export { loadAbiFromEtherscanV2, shouldUseV2Api } from './etherscan-v2';
5
- export { loadEvmContract } from './loader';
6
- export * from './types';
7
- export { AbiComparisonService, abiComparisonService } from './comparison';
package/src/abi/loader.ts DELETED
@@ -1,415 +0,0 @@
1
- import { isAddress } from 'viem';
2
-
3
- import type {
4
- ContractDefinitionMetadata,
5
- ContractSchema,
6
- EvmNetworkConfig,
7
- ProxyInfo,
8
- } from '@openzeppelin/ui-types';
9
- import {
10
- appConfigService,
11
- logger,
12
- simpleHash,
13
- userNetworkServiceConfigService,
14
- withTimeout,
15
- } from '@openzeppelin/ui-utils';
16
-
17
- import { getEvmExplorerAddressUrl } from '../configuration/explorer';
18
- import { detectProxyFromAbi, getAdminAddress, getImplementationAddress } from '../proxy/detection';
19
- import type { AbiItem, TypedEvmNetworkConfig } from '../types';
20
- import type { EvmContractArtifacts } from '../types/artifacts';
21
- import {
22
- EvmProviderKeys,
23
- isEvmProviderKey,
24
- type EvmContractDefinitionProviderKey,
25
- } from '../types/providers';
26
- import { loadAbiFromEtherscan } from './etherscan';
27
- import { getSourcifyContractAppUrl, loadAbiFromSourcify } from './sourcify';
28
- import { transformAbiToSchema } from './transformer';
29
-
30
- /**
31
- * Loads and parses an ABI directly from a JSON string.
32
- */
33
- async function loadAbiFromJson(abiJsonString: string): Promise<ContractSchema> {
34
- let abi: AbiItem[];
35
- try {
36
- abi = JSON.parse(abiJsonString);
37
- if (!Array.isArray(abi)) {
38
- throw new Error('Parsed JSON is not an array.');
39
- }
40
- } catch (error) {
41
- logger.error('loadAbiFromJson', 'Failed to parse source string as JSON ABI:', error);
42
- throw new Error(`Invalid JSON ABI provided: ${(error as Error).message}`);
43
- }
44
-
45
- logger.info('loadAbiFromJson', `Successfully parsed JSON ABI with ${abi.length} items.`);
46
- const contractName = 'ContractFromABI'; // Default name for direct ABI
47
- return transformAbiToSchema(abi, contractName, undefined);
48
- }
49
-
50
- /**
51
- * Enhanced result type for ABI loading with metadata and proxy information
52
- */
53
- export interface EvmContractLoadResult {
54
- schema: ContractSchema;
55
- source: 'fetched' | 'manual';
56
- contractDefinitionOriginal?: string;
57
- metadata?: ContractDefinitionMetadata;
58
- proxyInfo?: ProxyInfo;
59
- }
60
-
61
- /**
62
- * Options for contract loading behavior
63
- */
64
- export interface ContractLoadOptions {
65
- /** Skip proxy detection and load the contract ABI as-is */
66
- skipProxyDetection?: boolean;
67
- /** Force treating the address as an implementation contract */
68
- treatAsImplementation?: boolean;
69
- }
70
-
71
- const PER_PROVIDER_TIMEOUT_MS = 4000;
72
- const OVERALL_BUDGET_MS = 10000;
73
-
74
- /**
75
- * Loads contract schema from artifacts provided by the UI, prioritizing manual ABI input.
76
- * Returns enhanced result with schema source information and automatic proxy detection.
77
- */
78
- export async function loadEvmContract(
79
- artifacts: EvmContractArtifacts,
80
- networkConfig: EvmNetworkConfig,
81
- options: ContractLoadOptions = {}
82
- ): Promise<EvmContractLoadResult> {
83
- const { contractAddress, contractDefinition, __proxyDetectionOptions } = artifacts;
84
-
85
- // Extract proxy detection options from form data if present
86
- const proxyOptions = __proxyDetectionOptions as { skipProxyDetection?: boolean } | undefined;
87
- if (proxyOptions?.skipProxyDetection) {
88
- options.skipProxyDetection = true;
89
- }
90
-
91
- if (!contractAddress || typeof contractAddress !== 'string' || !isAddress(contractAddress)) {
92
- throw new Error('A valid contract address is required.');
93
- }
94
-
95
- // 1. Prioritize manual contract definition input if provided.
96
- if (
97
- contractDefinition &&
98
- typeof contractDefinition === 'string' &&
99
- contractDefinition.trim().length > 0
100
- ) {
101
- // Try to detect if this looks like JSON
102
- const trimmed = contractDefinition.trim();
103
- const hasJsonContent = trimmed.includes('[') && trimmed.includes(']') && trimmed.includes('{');
104
-
105
- if (hasJsonContent) {
106
- logger.info('loadEvmContract', 'Manual contract definition provided. Attempting to parse...');
107
- try {
108
- const schema = await loadAbiFromJson(contractDefinition);
109
- // Attach the address to the schema from the separate address field.
110
- return {
111
- schema: { ...schema, address: contractAddress },
112
- source: 'manual' as const,
113
- contractDefinitionOriginal: contractDefinition,
114
- metadata: {
115
- contractName: schema.name,
116
- fetchTimestamp: new Date(),
117
- verificationStatus: 'unknown', // Manual ABI - verification status unknown
118
- },
119
- // Note: No proxy detection for manual ABIs - user provides what they want
120
- };
121
- } catch (error) {
122
- logger.error('loadEvmContract', 'Failed to parse manually provided ABI:', error);
123
- // If manual ABI is provided but invalid, it's a hard error.
124
- throw new Error(`The provided ABI JSON is invalid: ${(error as Error).message}`);
125
- }
126
- }
127
- }
128
-
129
- // Extract optional forced provider (from adapter-specific field or generic 'service')
130
- const forcedRaw =
131
- (artifacts as unknown as { __forcedProvider?: string }).__forcedProvider ||
132
- (artifacts as unknown as { service?: string }).service;
133
- const forcedProvider: EvmContractDefinitionProviderKey | null = isEvmProviderKey(forcedRaw)
134
- ? (forcedRaw as EvmContractDefinitionProviderKey)
135
- : null;
136
-
137
- // 2. If no manual ABI, fall back to fetching from provider(s) with proxy detection.
138
- logger.info(
139
- 'loadEvmContract',
140
- `No manual ABI detected. Attempting Etherscan fetch for address: ${contractAddress}...`
141
- );
142
-
143
- return await loadContractWithProxyDetection(
144
- contractAddress,
145
- networkConfig as TypedEvmNetworkConfig,
146
- options,
147
- forcedProvider
148
- );
149
- }
150
-
151
- /**
152
- * Builds a standard contract result with metadata
153
- */
154
- function buildContractResult(
155
- contractAddress: string,
156
- abiResult: { schema: ContractSchema; originalAbi: string },
157
- networkConfig: TypedEvmNetworkConfig,
158
- sourceProvider: EvmContractDefinitionProviderKey | null,
159
- proxyInfo?: ProxyInfo
160
- ): EvmContractLoadResult {
161
- // Determine provenance URL based on the provider that supplied the ABI
162
- let fetchedFrom: string | undefined = undefined;
163
- if (sourceProvider === EvmProviderKeys.Etherscan) {
164
- fetchedFrom = getEvmExplorerAddressUrl(contractAddress, networkConfig) || undefined;
165
- } else if (sourceProvider === EvmProviderKeys.Sourcify) {
166
- fetchedFrom = getSourcifyContractAppUrl(networkConfig.chainId, contractAddress);
167
- } else {
168
- // Fallback to resolved explorer URL when provider is unknown
169
- fetchedFrom = getEvmExplorerAddressUrl(contractAddress, networkConfig) || undefined;
170
- }
171
-
172
- return {
173
- schema: { ...abiResult.schema, address: contractAddress },
174
- source: 'fetched',
175
- contractDefinitionOriginal: abiResult.originalAbi,
176
- metadata: {
177
- fetchedFrom,
178
- contractName: abiResult.schema.name,
179
- verificationStatus: 'verified',
180
- fetchTimestamp: new Date(),
181
- definitionHash: simpleHash(abiResult.originalAbi),
182
- },
183
- proxyInfo,
184
- };
185
- }
186
-
187
- /**
188
- * Attempts to load implementation ABI for a detected proxy
189
- */
190
- async function loadImplementationAbi(
191
- _contractAddress: string,
192
- implementationAddress: string,
193
- networkConfig: TypedEvmNetworkConfig,
194
- _proxyType: string
195
- ): Promise<{ schema: ContractSchema; originalAbi: string } | null> {
196
- try {
197
- const implementationResult = await loadAbiFromEtherscan(implementationAddress, networkConfig);
198
-
199
- logger.info(
200
- 'loadImplementationAbi',
201
- `Successfully fetched implementation ABI with ${implementationResult.schema.functions.length} functions`
202
- );
203
-
204
- return implementationResult;
205
- } catch (implementationError) {
206
- logger.warn(
207
- 'loadImplementationAbi',
208
- `Failed to load implementation ABI: ${implementationError}`
209
- );
210
- return null;
211
- }
212
- }
213
-
214
- /**
215
- * Handles the proxy detection flow and returns appropriate result
216
- */
217
- async function handleProxyDetection(
218
- contractAddress: string,
219
- initialResult: { schema: ContractSchema; originalAbi: string },
220
- networkConfig: TypedEvmNetworkConfig,
221
- initialProvider: EvmContractDefinitionProviderKey | null
222
- ): Promise<EvmContractLoadResult | null> {
223
- // Parse the ABI to check for proxy patterns
224
- const abi: AbiItem[] = JSON.parse(initialResult.originalAbi);
225
- const proxyDetection = detectProxyFromAbi(abi);
226
-
227
- if (!proxyDetection.isProxy) {
228
- return null; // Not a proxy, let caller handle normal flow
229
- }
230
-
231
- logger.info(
232
- 'handleProxyDetection',
233
- `Proxy detected: ${proxyDetection.proxyType} (confidence: ${proxyDetection.confidence})`
234
- );
235
-
236
- const proxyType = proxyDetection.proxyType || 'unknown';
237
- const implementationAddress = await getImplementationAddress(
238
- contractAddress,
239
- networkConfig,
240
- proxyType
241
- );
242
-
243
- // Attempt to resolve admin address as well for display purposes
244
- const adminAddress = await getAdminAddress(contractAddress, networkConfig);
245
-
246
- if (!implementationAddress) {
247
- logger.info('handleProxyDetection', 'Proxy detected but implementation address not found');
248
-
249
- // Return proxy ABI with proxy info indicating detection failure
250
- return buildContractResult(contractAddress, initialResult, networkConfig, initialProvider, {
251
- isProxy: true,
252
- proxyType,
253
- proxyAddress: contractAddress,
254
- detectionMethod: 'automatic',
255
- });
256
- }
257
-
258
- logger.info('handleProxyDetection', `Found implementation at: ${implementationAddress}`);
259
-
260
- // Try to load implementation ABI
261
- const implementationResult = await loadImplementationAbi(
262
- contractAddress,
263
- implementationAddress,
264
- networkConfig,
265
- proxyType
266
- );
267
-
268
- const baseProxyInfo = {
269
- isProxy: true,
270
- proxyType,
271
- implementationAddress,
272
- proxyAddress: contractAddress,
273
- detectionMethod: 'automatic',
274
- ...(adminAddress ? { adminAddress } : {}),
275
- };
276
-
277
- if (implementationResult) {
278
- // Use implementation ABI with proxy metadata
279
- // Implementation ABI was fetched from Etherscan
280
- return buildContractResult(
281
- contractAddress,
282
- implementationResult,
283
- networkConfig,
284
- EvmProviderKeys.Etherscan,
285
- baseProxyInfo
286
- );
287
- } else {
288
- // Fall back to proxy ABI with proxy info (provenance from initial provider)
289
- return buildContractResult(
290
- contractAddress,
291
- initialResult,
292
- networkConfig,
293
- initialProvider,
294
- baseProxyInfo
295
- );
296
- }
297
- }
298
-
299
- /**
300
- * Loads contract with automatic proxy detection and implementation resolution
301
- */
302
- async function loadContractWithProxyDetection(
303
- contractAddress: string,
304
- networkConfig: TypedEvmNetworkConfig,
305
- options: ContractLoadOptions = {},
306
- forcedProvider: EvmContractDefinitionProviderKey | null = null
307
- ): Promise<EvmContractLoadResult> {
308
- try {
309
- // Determine provider precedence based on forced provider and user config
310
- let uiDefault: EvmContractDefinitionProviderKey | null = null;
311
- // 1) New generic per-service config
312
- const svcCfg = userNetworkServiceConfigService.get(networkConfig.id, 'contract-definitions');
313
- if (svcCfg && typeof svcCfg === 'object' && 'defaultProvider' in svcCfg) {
314
- const raw = (svcCfg as Record<string, unknown>).defaultProvider;
315
- if (isEvmProviderKey(raw)) uiDefault = raw as EvmContractDefinitionProviderKey;
316
- }
317
- // App-config default provider (optional)
318
- const appDefaultRaw = appConfigService.getGlobalServiceParam(
319
- 'contractdefinition',
320
- 'defaultProvider'
321
- );
322
- const appDefault: EvmContractDefinitionProviderKey | null =
323
- typeof appDefaultRaw === 'string' && isEvmProviderKey(appDefaultRaw)
324
- ? (appDefaultRaw as EvmContractDefinitionProviderKey)
325
- : null;
326
-
327
- // Helper function to build provider array from primary provider
328
- const buildProviderArray = (
329
- primary: EvmContractDefinitionProviderKey
330
- ): Array<EvmContractDefinitionProviderKey> => [
331
- primary,
332
- primary === EvmProviderKeys.Etherscan ? EvmProviderKeys.Sourcify : EvmProviderKeys.Etherscan,
333
- ];
334
-
335
- const providers: Array<EvmContractDefinitionProviderKey> = forcedProvider
336
- ? [forcedProvider]
337
- : uiDefault
338
- ? buildProviderArray(uiDefault)
339
- : appDefault
340
- ? buildProviderArray(appDefault)
341
- : [EvmProviderKeys.Etherscan, EvmProviderKeys.Sourcify];
342
-
343
- const overallDeadline = Date.now() + OVERALL_BUDGET_MS;
344
- let initialResult: { schema: ContractSchema; originalAbi: string } | null = null;
345
- let lastError: unknown = null;
346
- let usedProvider: EvmContractDefinitionProviderKey | null = null;
347
- for (const provider of providers) {
348
- try {
349
- const remainingOverall = Math.max(100, overallDeadline - Date.now());
350
- const attemptTimeout = Math.min(PER_PROVIDER_TIMEOUT_MS, remainingOverall);
351
-
352
- if (provider === EvmProviderKeys.Etherscan) {
353
- initialResult = await withTimeout(
354
- loadAbiFromEtherscan(contractAddress, networkConfig),
355
- attemptTimeout,
356
- 'etherscan'
357
- );
358
- } else if (provider === EvmProviderKeys.Sourcify) {
359
- initialResult = await withTimeout(
360
- loadAbiFromSourcify(contractAddress, networkConfig, attemptTimeout),
361
- attemptTimeout,
362
- 'sourcify'
363
- );
364
- }
365
- if (initialResult) {
366
- usedProvider = provider;
367
- break;
368
- }
369
- } catch (err) {
370
- lastError = err;
371
- continue;
372
- }
373
- }
374
- if (!initialResult) throw lastError ?? new Error('No provider succeeded');
375
-
376
- logger.info(
377
- 'loadContractWithProxyDetection',
378
- `Successfully fetched initial ABI for ${contractAddress} with ${initialResult.schema.functions.length} functions`
379
- );
380
-
381
- // Step 2: Handle proxy detection if enabled
382
- if (!options.skipProxyDetection && !options.treatAsImplementation) {
383
- const proxyResult = await handleProxyDetection(
384
- contractAddress,
385
- initialResult,
386
- networkConfig,
387
- usedProvider
388
- );
389
- if (proxyResult) {
390
- return proxyResult;
391
- }
392
- }
393
-
394
- // Step 3: Not a proxy or proxy detection skipped - return original ABI
395
- return buildContractResult(contractAddress, initialResult, networkConfig, usedProvider);
396
- } catch (error) {
397
- logger.warn('loadContractWithProxyDetection', `Contract loading failed: ${error}`);
398
-
399
- // If a forced provider was specified, honor it and do NOT fallback automatically
400
- if (forcedProvider) {
401
- throw error;
402
- }
403
-
404
- // Check if this is a "contract not verified" error
405
- const errorMessage = (error as Error).message || '';
406
- if (errorMessage.includes('Contract not verified')) {
407
- throw new Error(
408
- `Contract at ${contractAddress} is not verified on the block explorer. ` +
409
- `Verification status: unverified. Please provide the contract ABI manually.`
410
- );
411
- }
412
- // Otherwise, rethrow the last error from provider attempts
413
- throw error;
414
- }
415
- }
@@ -1,75 +0,0 @@
1
- import type { ContractSchema } from '@openzeppelin/ui-types';
2
- import { logger } from '@openzeppelin/ui-utils';
3
-
4
- import type { AbiItem, TypedEvmNetworkConfig } from '../types';
5
- import { transformAbiToSchema } from './transformer';
6
-
7
- export interface SourcifyAbiResult {
8
- schema: ContractSchema;
9
- originalAbi: string;
10
- }
11
-
12
- const SOURCIFY_APP_BASE = 'https://repo.sourcify.dev';
13
-
14
- export function getSourcifyContractAppUrl(chainId: number, address: string): string {
15
- return `${SOURCIFY_APP_BASE}/${chainId}/${address}`;
16
- }
17
-
18
- const SOURCIFY_API_BASE = 'https://sourcify.dev/server/v2';
19
-
20
- interface SourcifyApiContractResponse {
21
- abi?: AbiItem[];
22
- metadata?: {
23
- contractName?: string;
24
- output?: {
25
- abi?: AbiItem[];
26
- };
27
- };
28
- }
29
-
30
- function buildSourcifyApiUrl(chainId: number, address: string): string {
31
- const normalizedAddress = address.toLowerCase();
32
- const url = new URL(
33
- `${SOURCIFY_API_BASE}/contract/${chainId}/${normalizedAddress}?fields=abi,metadata`
34
- );
35
- return url.toString();
36
- }
37
-
38
- export async function loadAbiFromSourcify(
39
- address: string,
40
- networkConfig: TypedEvmNetworkConfig,
41
- timeoutMs = 4000
42
- ): Promise<SourcifyAbiResult> {
43
- const controller = new AbortController();
44
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
45
-
46
- try {
47
- const url = buildSourcifyApiUrl(networkConfig.chainId, address);
48
- logger.info('loadAbiFromSourcify', `Fetching contract from ${url}`);
49
-
50
- const response = await fetch(url, { signal: controller.signal });
51
- if (!response.ok) {
52
- throw new Error(`Sourcify request failed: ${response.status} ${response.statusText}`);
53
- }
54
-
55
- const payload = (await response.json()) as SourcifyApiContractResponse;
56
- const abi = payload.abi ?? payload.metadata?.output?.abi;
57
-
58
- if (!abi || !Array.isArray(abi)) {
59
- throw new Error('Sourcify metadata did not include a valid ABI array');
60
- }
61
-
62
- const normalizedAddress = address.toLowerCase();
63
- const contractName =
64
- payload.metadata?.contractName ||
65
- `Contract_${normalizedAddress.substring(0, 6).toUpperCase()}`;
66
- const schema = transformAbiToSchema(abi, contractName, address);
67
-
68
- return { schema, originalAbi: JSON.stringify(abi) };
69
- } catch (error) {
70
- logger.warn('loadAbiFromSourcify', `Failed to fetch ABI from Sourcify: ${String(error)}`);
71
- throw error as Error;
72
- } finally {
73
- clearTimeout(timeout);
74
- }
75
- }