@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/etherscan.ts
DELETED
|
@@ -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
|
-
}
|
package/src/abi/sourcify.ts
DELETED
|
@@ -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
|
-
}
|