@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/proxy/detection.ts
DELETED
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Proxy Contract Detection Utilities
|
|
3
|
-
*
|
|
4
|
-
* Automatically detects proxy contracts and resolves implementation addresses
|
|
5
|
-
* for UUPS, Transparent, Beacon, and other proxy patterns.
|
|
6
|
-
*/
|
|
7
|
-
import { createPublicClient, http, keccak256, parseAbi, toHex } from 'viem';
|
|
8
|
-
|
|
9
|
-
import { logger } from '@openzeppelin/ui-utils';
|
|
10
|
-
|
|
11
|
-
import { resolveRpcUrl } from '../configuration';
|
|
12
|
-
import { AbiItem, TypedEvmNetworkConfig } from '../types';
|
|
13
|
-
|
|
14
|
-
export interface ProxyDetectionResult {
|
|
15
|
-
isProxy: boolean;
|
|
16
|
-
proxyType: 'uups' | 'transparent' | 'beacon' | 'diamond' | 'minimal' | 'unknown' | null;
|
|
17
|
-
confidence: 'high' | 'medium' | 'low';
|
|
18
|
-
indicators: string[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Analyzes an ABI to determine if it belongs to a proxy contract
|
|
23
|
-
*/
|
|
24
|
-
export function detectProxyFromAbi(abi: AbiItem[]): ProxyDetectionResult {
|
|
25
|
-
const functions = abi.filter((item) => item.type === 'function');
|
|
26
|
-
const events = abi.filter((item) => item.type === 'event');
|
|
27
|
-
const errors = abi.filter((item) => item.type === 'error');
|
|
28
|
-
|
|
29
|
-
const indicators: string[] = [];
|
|
30
|
-
let proxyType: ProxyDetectionResult['proxyType'] = null;
|
|
31
|
-
let confidence: ProxyDetectionResult['confidence'] = 'low';
|
|
32
|
-
|
|
33
|
-
// Check for UUPS proxy indicators
|
|
34
|
-
const hasUpgradeEvent = events.some((e) => e.name === 'Upgraded');
|
|
35
|
-
const hasImplementationFunction = functions.some((f) => f.name === 'implementation');
|
|
36
|
-
const hasUUPSErrors = errors.some((e) => e.name?.includes('ERC1967'));
|
|
37
|
-
const hasUpgradeToFunction = functions.some(
|
|
38
|
-
(f) => f.name === 'upgradeToAndCall' || f.name === 'upgradeTo'
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
if (hasUpgradeEvent || hasUUPSErrors) {
|
|
42
|
-
indicators.push('ERC1967 upgrade pattern detected');
|
|
43
|
-
proxyType = 'uups';
|
|
44
|
-
confidence = 'high';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (hasImplementationFunction) {
|
|
48
|
-
indicators.push('implementation() function found');
|
|
49
|
-
if (proxyType === 'uups') {
|
|
50
|
-
confidence = 'high';
|
|
51
|
-
} else {
|
|
52
|
-
proxyType = 'transparent';
|
|
53
|
-
confidence = 'medium';
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (hasUpgradeToFunction && proxyType === 'uups') {
|
|
58
|
-
indicators.push('UUPS upgrade functions found');
|
|
59
|
-
confidence = 'high';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Check for Transparent proxy indicators
|
|
63
|
-
const hasAdminFunction = functions.some((f) => f.name === 'admin');
|
|
64
|
-
const hasProxyAdminErrors = errors.some((e) => e.name?.includes('ProxyDenied'));
|
|
65
|
-
const hasChangeAdminFunction = functions.some((f) => f.name === 'changeAdmin');
|
|
66
|
-
|
|
67
|
-
if (hasAdminFunction || hasProxyAdminErrors || hasChangeAdminFunction) {
|
|
68
|
-
indicators.push('Transparent proxy admin pattern detected');
|
|
69
|
-
if (proxyType === null) {
|
|
70
|
-
proxyType = 'transparent';
|
|
71
|
-
confidence = 'medium';
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check for Beacon proxy indicators
|
|
76
|
-
const hasBeaconFunction = functions.some((f) => f.name === 'beacon');
|
|
77
|
-
const hasBeaconUpgrade = events.some((e) => e.name === 'BeaconUpgraded');
|
|
78
|
-
|
|
79
|
-
if (hasBeaconFunction || hasBeaconUpgrade) {
|
|
80
|
-
indicators.push('Beacon proxy pattern detected');
|
|
81
|
-
proxyType = 'beacon';
|
|
82
|
-
confidence = 'high';
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Check for Diamond proxy indicators
|
|
86
|
-
const hasDiamondCut = functions.some((f) => f.name === 'diamondCut');
|
|
87
|
-
const hasFacets = functions.some((f) => f.name === 'facets');
|
|
88
|
-
const hasFacetFunctionSelectors = functions.some((f) => f.name === 'facetFunctionSelectors');
|
|
89
|
-
|
|
90
|
-
if (hasDiamondCut || (hasFacets && hasFacetFunctionSelectors)) {
|
|
91
|
-
indicators.push('Diamond (EIP-2535) proxy pattern detected');
|
|
92
|
-
proxyType = 'diamond';
|
|
93
|
-
confidence = 'high';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// General proxy indicators
|
|
97
|
-
const hasFallback = abi.some((item) => item.type === 'fallback');
|
|
98
|
-
const hasProxyConstructor = abi.some(
|
|
99
|
-
(item) =>
|
|
100
|
-
item.type === 'constructor' &&
|
|
101
|
-
item.inputs?.some(
|
|
102
|
-
(input: AbiItem) =>
|
|
103
|
-
input.name === 'implementation' || input.name === '_logic' || input.name === '_data'
|
|
104
|
-
)
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
if (hasFallback) {
|
|
108
|
-
indicators.push('Fallback function present');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (hasProxyConstructor) {
|
|
112
|
-
indicators.push('Proxy-style constructor detected');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Minimal proxy (EIP-1167) detection
|
|
116
|
-
const hasMinimalFunctions = functions.length <= 1; // Usually no functions except maybe implementation()
|
|
117
|
-
const hasNoEvents = events.length === 0;
|
|
118
|
-
|
|
119
|
-
if (hasMinimalFunctions && hasNoEvents && hasFallback && proxyType === null) {
|
|
120
|
-
indicators.push('Minimal proxy pattern detected');
|
|
121
|
-
proxyType = 'minimal';
|
|
122
|
-
confidence = 'medium';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Final proxy determination
|
|
126
|
-
const isProxy =
|
|
127
|
-
proxyType !== null ||
|
|
128
|
-
(hasFallback && hasMinimalFunctions && (hasProxyConstructor || functions.length === 0));
|
|
129
|
-
|
|
130
|
-
if (isProxy && proxyType === null) {
|
|
131
|
-
proxyType = 'unknown';
|
|
132
|
-
indicators.push('Generic proxy pattern detected');
|
|
133
|
-
confidence = 'low';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
isProxy,
|
|
138
|
-
proxyType,
|
|
139
|
-
confidence,
|
|
140
|
-
indicators,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Attempts to resolve the implementation address for a proxy contract
|
|
146
|
-
*/
|
|
147
|
-
export async function getImplementationAddress(
|
|
148
|
-
proxyAddress: string,
|
|
149
|
-
networkConfig: TypedEvmNetworkConfig,
|
|
150
|
-
proxyType: string
|
|
151
|
-
): Promise<string | null> {
|
|
152
|
-
logger.info(
|
|
153
|
-
'getImplementationAddress',
|
|
154
|
-
`Resolving implementation for ${proxyType} proxy: ${proxyAddress}`
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
switch (proxyType) {
|
|
159
|
-
case 'uups':
|
|
160
|
-
case 'transparent': {
|
|
161
|
-
// Try modern EIP-1967 slot first
|
|
162
|
-
const eip1967Impl = await getEIP1967Implementation(proxyAddress, networkConfig);
|
|
163
|
-
if (eip1967Impl) return eip1967Impl;
|
|
164
|
-
|
|
165
|
-
// Fall back to legacy OZ Unstructured Storage slot used by older proxies
|
|
166
|
-
const legacyImpl = await getLegacyOZImplementation(proxyAddress, networkConfig);
|
|
167
|
-
if (legacyImpl) return legacyImpl;
|
|
168
|
-
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
case 'beacon':
|
|
173
|
-
return await getBeaconImplementation(proxyAddress, networkConfig);
|
|
174
|
-
|
|
175
|
-
case 'diamond':
|
|
176
|
-
// Diamond proxies don't have a single implementation
|
|
177
|
-
// Would need to handle facets separately
|
|
178
|
-
logger.info('getImplementationAddress', 'Diamond proxies not fully supported yet');
|
|
179
|
-
return null;
|
|
180
|
-
|
|
181
|
-
case 'minimal':
|
|
182
|
-
return await getMinimalProxyImplementation(proxyAddress, networkConfig);
|
|
183
|
-
|
|
184
|
-
default:
|
|
185
|
-
// Try common methods for unknown proxy types
|
|
186
|
-
return await tryCommonImplementationMethods(proxyAddress, networkConfig);
|
|
187
|
-
}
|
|
188
|
-
} catch (error) {
|
|
189
|
-
logger.warn('getImplementationAddress', `Failed to resolve implementation: ${error}`);
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Attempts to resolve the admin address for a proxy contract
|
|
196
|
-
* Tries EIP-1967 admin slot first, then legacy OZ slot
|
|
197
|
-
*/
|
|
198
|
-
export async function getAdminAddress(
|
|
199
|
-
proxyAddress: string,
|
|
200
|
-
networkConfig: TypedEvmNetworkConfig
|
|
201
|
-
): Promise<string | null> {
|
|
202
|
-
try {
|
|
203
|
-
const eip1967Admin = await getEIP1967Admin(proxyAddress, networkConfig);
|
|
204
|
-
if (eip1967Admin) return eip1967Admin;
|
|
205
|
-
|
|
206
|
-
const legacyAdmin = await getLegacyOZAdmin(proxyAddress, networkConfig);
|
|
207
|
-
if (legacyAdmin) return legacyAdmin;
|
|
208
|
-
|
|
209
|
-
return null;
|
|
210
|
-
} catch (error) {
|
|
211
|
-
logger.warn('getAdminAddress', `Failed to resolve admin: ${error}`);
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Reads implementation address from EIP-1967 storage slot
|
|
218
|
-
*/
|
|
219
|
-
async function getEIP1967Implementation(
|
|
220
|
-
proxyAddress: string,
|
|
221
|
-
networkConfig: TypedEvmNetworkConfig
|
|
222
|
-
): Promise<string | null> {
|
|
223
|
-
// EIP-1967 implementation storage slot
|
|
224
|
-
const implementationSlot = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
|
|
225
|
-
|
|
226
|
-
return await readStorageSlot(proxyAddress, implementationSlot, networkConfig);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Reads admin address from EIP-1967 admin storage slot
|
|
231
|
-
* Slot: bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
|
|
232
|
-
*/
|
|
233
|
-
async function getEIP1967Admin(
|
|
234
|
-
proxyAddress: string,
|
|
235
|
-
networkConfig: TypedEvmNetworkConfig
|
|
236
|
-
): Promise<string | null> {
|
|
237
|
-
const adminSlot = '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103';
|
|
238
|
-
return await readStorageSlot(proxyAddress, adminSlot, networkConfig);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Reads admin address from legacy OpenZeppelin Unstructured Storage slot
|
|
243
|
-
* Slot: keccak256("org.zeppelinos.proxy.admin")
|
|
244
|
-
*/
|
|
245
|
-
async function getLegacyOZAdmin(
|
|
246
|
-
proxyAddress: string,
|
|
247
|
-
networkConfig: TypedEvmNetworkConfig
|
|
248
|
-
): Promise<string | null> {
|
|
249
|
-
try {
|
|
250
|
-
const slot = keccak256(toHex('org.zeppelinos.proxy.admin'));
|
|
251
|
-
logger.info('getLegacyOZAdmin', `Trying legacy OZ admin slot: ${slot}`);
|
|
252
|
-
return await readStorageSlot(proxyAddress, slot, networkConfig);
|
|
253
|
-
} catch (error) {
|
|
254
|
-
logger.warn('getLegacyOZAdmin', `Failed computing or reading legacy admin slot: ${error}`);
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Reads implementation address from legacy OpenZeppelin Unstructured Storage slot
|
|
261
|
-
* Slot: keccak256("org.zeppelinos.proxy.implementation")
|
|
262
|
-
*/
|
|
263
|
-
async function getLegacyOZImplementation(
|
|
264
|
-
proxyAddress: string,
|
|
265
|
-
networkConfig: TypedEvmNetworkConfig
|
|
266
|
-
): Promise<string | null> {
|
|
267
|
-
try {
|
|
268
|
-
// Compute slot deterministically at runtime to avoid hardcoding
|
|
269
|
-
const slot = keccak256(toHex('org.zeppelinos.proxy.implementation'));
|
|
270
|
-
logger.info('getLegacyOZImplementation', `Trying legacy OZ slot: ${slot}`);
|
|
271
|
-
return await readStorageSlot(proxyAddress, slot, networkConfig);
|
|
272
|
-
} catch (error) {
|
|
273
|
-
logger.warn('getLegacyOZImplementation', `Failed computing or reading legacy slot: ${error}`);
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Resolves implementation through beacon proxy pattern
|
|
280
|
-
*/
|
|
281
|
-
async function getBeaconImplementation(
|
|
282
|
-
proxyAddress: string,
|
|
283
|
-
networkConfig: TypedEvmNetworkConfig
|
|
284
|
-
): Promise<string | null> {
|
|
285
|
-
// EIP-1967 beacon storage slot
|
|
286
|
-
const beaconSlot = '0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50';
|
|
287
|
-
|
|
288
|
-
const beaconAddress = await readStorageSlot(proxyAddress, beaconSlot, networkConfig);
|
|
289
|
-
if (!beaconAddress) {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Call implementation() on the beacon contract
|
|
294
|
-
return await callContractFunction(beaconAddress, 'implementation()', [], networkConfig);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Extracts implementation from minimal proxy bytecode
|
|
299
|
-
*/
|
|
300
|
-
async function getMinimalProxyImplementation(
|
|
301
|
-
proxyAddress: string,
|
|
302
|
-
networkConfig: TypedEvmNetworkConfig
|
|
303
|
-
): Promise<string | null> {
|
|
304
|
-
try {
|
|
305
|
-
// Get the contract bytecode
|
|
306
|
-
const bytecode = await getContractBytecode(proxyAddress, networkConfig);
|
|
307
|
-
|
|
308
|
-
if (!bytecode || bytecode.length < 42) {
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Minimal proxy (EIP-1167) has a specific bytecode pattern
|
|
313
|
-
// 0x363d3d373d3d3d363d73{implementation}5af43d82803e903d91602b57fd5bf3
|
|
314
|
-
if (
|
|
315
|
-
bytecode.startsWith('0x363d3d373d3d3d363d73') &&
|
|
316
|
-
bytecode.includes('5af43d82803e903d91602b57fd5bf3')
|
|
317
|
-
) {
|
|
318
|
-
// Extract the 20-byte implementation address
|
|
319
|
-
const implementationHex = bytecode.slice(22, 62); // Skip prefix, take 20 bytes (40 hex chars)
|
|
320
|
-
return '0x' + implementationHex;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return null;
|
|
324
|
-
} catch (error) {
|
|
325
|
-
logger.warn('getMinimalProxyImplementation', `Error reading bytecode: ${error}`);
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Tries common proxy implementation methods
|
|
332
|
-
*/
|
|
333
|
-
async function tryCommonImplementationMethods(
|
|
334
|
-
proxyAddress: string,
|
|
335
|
-
networkConfig: TypedEvmNetworkConfig
|
|
336
|
-
): Promise<string | null> {
|
|
337
|
-
const commonMethods = [
|
|
338
|
-
'implementation()',
|
|
339
|
-
'getImplementation()',
|
|
340
|
-
'_implementation()',
|
|
341
|
-
'target()',
|
|
342
|
-
];
|
|
343
|
-
|
|
344
|
-
for (const method of commonMethods) {
|
|
345
|
-
try {
|
|
346
|
-
const result = await callContractFunction(proxyAddress, method, [], networkConfig);
|
|
347
|
-
if (result && result !== '0x0000000000000000000000000000000000000000') {
|
|
348
|
-
logger.info(
|
|
349
|
-
'tryCommonImplementationMethods',
|
|
350
|
-
`Found implementation via ${method}: ${result}`
|
|
351
|
-
);
|
|
352
|
-
return result;
|
|
353
|
-
}
|
|
354
|
-
} catch {
|
|
355
|
-
// Continue to next method
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Try EIP-1967 storage as last resort
|
|
361
|
-
return await getEIP1967Implementation(proxyAddress, networkConfig);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Creates a viem public client for the given network configuration
|
|
366
|
-
*/
|
|
367
|
-
function createViemClient(networkConfig: TypedEvmNetworkConfig) {
|
|
368
|
-
// Honor user/app RPC overrides
|
|
369
|
-
const rpcUrl = resolveRpcUrl(networkConfig);
|
|
370
|
-
return createPublicClient({
|
|
371
|
-
transport: http(rpcUrl),
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Reads a storage slot from a contract using viem
|
|
377
|
-
*/
|
|
378
|
-
async function readStorageSlot(
|
|
379
|
-
address: string,
|
|
380
|
-
slot: string,
|
|
381
|
-
networkConfig: TypedEvmNetworkConfig
|
|
382
|
-
): Promise<string | null> {
|
|
383
|
-
try {
|
|
384
|
-
const client = createViemClient(networkConfig);
|
|
385
|
-
|
|
386
|
-
const storageValue = await client.getStorageAt({
|
|
387
|
-
address: address as `0x${string}`,
|
|
388
|
-
slot: slot as `0x${string}`,
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// Convert from 32-byte storage format to 20-byte address
|
|
392
|
-
if (
|
|
393
|
-
storageValue &&
|
|
394
|
-
storageValue !== '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
395
|
-
) {
|
|
396
|
-
logger.info('readStorageSlot', `Found non-zero value at slot ${slot}: ${storageValue}`);
|
|
397
|
-
const implAddress = '0x' + storageValue.slice(-40); // Last 20 bytes
|
|
398
|
-
return implAddress;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return null;
|
|
402
|
-
} catch (error) {
|
|
403
|
-
logger.warn('readStorageSlot', `Failed to read storage slot ${slot}: ${error}`);
|
|
404
|
-
return null;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Calls a function on a contract using viem's readContract
|
|
410
|
-
* Supports functions with parameters and proper return value decoding
|
|
411
|
-
*/
|
|
412
|
-
async function callContractFunction(
|
|
413
|
-
address: string,
|
|
414
|
-
signature: string,
|
|
415
|
-
params: unknown[],
|
|
416
|
-
networkConfig: TypedEvmNetworkConfig
|
|
417
|
-
): Promise<string | null> {
|
|
418
|
-
try {
|
|
419
|
-
const client = createViemClient(networkConfig);
|
|
420
|
-
|
|
421
|
-
// Parse the function signature to get proper ABI format
|
|
422
|
-
const abi = parseAbi([signature]);
|
|
423
|
-
const func = abi[0] as { name: string; type: 'function' };
|
|
424
|
-
|
|
425
|
-
// Use viem's readContract for cleaner, more robust contract calls
|
|
426
|
-
const result = await client.readContract({
|
|
427
|
-
address: address as `0x${string}`,
|
|
428
|
-
abi,
|
|
429
|
-
functionName: func.name,
|
|
430
|
-
args: params as readonly unknown[],
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
// For proxy functions, we expect an address return value
|
|
434
|
-
const addressResult = result as string;
|
|
435
|
-
if (addressResult && addressResult !== '0x0000000000000000000000000000000000000000') {
|
|
436
|
-
return addressResult;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return null;
|
|
440
|
-
} catch (error) {
|
|
441
|
-
logger.warn('callContractFunction', `Failed to call ${signature}: ${error}`);
|
|
442
|
-
return null;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Gets contract bytecode using viem
|
|
448
|
-
*/
|
|
449
|
-
async function getContractBytecode(
|
|
450
|
-
address: string,
|
|
451
|
-
networkConfig: TypedEvmNetworkConfig
|
|
452
|
-
): Promise<string | null> {
|
|
453
|
-
try {
|
|
454
|
-
const client = createViemClient(networkConfig);
|
|
455
|
-
|
|
456
|
-
const bytecode = await client.getCode({
|
|
457
|
-
address: address as `0x${string}`,
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
return bytecode || null;
|
|
461
|
-
} catch (error) {
|
|
462
|
-
logger.warn('getContractBytecode', `Failed to get bytecode: ${error}`);
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
465
|
-
}
|
package/src/query/handler.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { createPublicClient, http, isAddress, type Chain, type PublicClient } from 'viem';
|
|
2
|
-
|
|
3
|
-
import type { ContractSchema, FunctionParameter } from '@openzeppelin/ui-types';
|
|
4
|
-
import { logger } from '@openzeppelin/ui-utils';
|
|
5
|
-
|
|
6
|
-
import { createAbiFunctionItem } from '../abi';
|
|
7
|
-
import { getUserRpcUrl, resolveRpcUrl } from '../configuration';
|
|
8
|
-
import { parseEvmInput } from '../transform';
|
|
9
|
-
import type { TypedEvmNetworkConfig } from '../types';
|
|
10
|
-
import type { WagmiWalletImplementation } from '../wallet/implementation/wagmi-implementation';
|
|
11
|
-
import { isEvmViewFunction } from './view-checker';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Private helper to get a PublicClient instance for view queries.
|
|
15
|
-
* Prioritizes custom RPC configuration, then connected wallet client if on the correct chain.
|
|
16
|
-
* Otherwise, creates a dedicated client using the resolved RPC URL for the target network.
|
|
17
|
-
*/
|
|
18
|
-
async function getPublicClientForQuery(
|
|
19
|
-
walletImplementation: WagmiWalletImplementation,
|
|
20
|
-
networkConfig: TypedEvmNetworkConfig
|
|
21
|
-
): Promise<PublicClient> {
|
|
22
|
-
// First check if there's a custom RPC configuration via generic service
|
|
23
|
-
if (getUserRpcUrl(networkConfig.id)) {
|
|
24
|
-
// Always create a new client with custom RPC when configured
|
|
25
|
-
const resolvedRpc = resolveRpcUrl(networkConfig);
|
|
26
|
-
return createPublicClientWithRpc(networkConfig, resolvedRpc);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// If no custom RPC, check if wallet is connected to correct chain
|
|
30
|
-
const accountStatus = walletImplementation.getWalletConnectionStatus();
|
|
31
|
-
const walletChainId = accountStatus.chainId ? Number(accountStatus.chainId) : undefined;
|
|
32
|
-
const isConnectedToCorrectChain =
|
|
33
|
-
accountStatus.isConnected && walletChainId === networkConfig.chainId;
|
|
34
|
-
|
|
35
|
-
if (isConnectedToCorrectChain) {
|
|
36
|
-
const clientFromWallet = await walletImplementation.getPublicClient();
|
|
37
|
-
if (clientFromWallet) {
|
|
38
|
-
return clientFromWallet;
|
|
39
|
-
} else {
|
|
40
|
-
logger.warn(
|
|
41
|
-
'getPublicClientForQuery',
|
|
42
|
-
`Could not get public client from connected wallet for chain ${walletChainId}. Falling back.`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Fallback: Create a dedicated client using the resolved RPC URL
|
|
48
|
-
const resolvedRpc = resolveRpcUrl(networkConfig);
|
|
49
|
-
logger.info(
|
|
50
|
-
'getPublicClientForQuery',
|
|
51
|
-
`Wallet not connected/on wrong chain OR failed to get wallet client. Creating dedicated public client for query on ${networkConfig.name} using RPC: ${resolvedRpc}`
|
|
52
|
-
);
|
|
53
|
-
return createPublicClientWithRpc(networkConfig, resolvedRpc);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Helper to create a public client with a specific RPC URL
|
|
58
|
-
*/
|
|
59
|
-
function createPublicClientWithRpc(
|
|
60
|
-
networkConfig: TypedEvmNetworkConfig,
|
|
61
|
-
rpcUrl: string
|
|
62
|
-
): PublicClient {
|
|
63
|
-
let chainForViem: Chain;
|
|
64
|
-
if (networkConfig.viemChain) {
|
|
65
|
-
chainForViem = networkConfig.viemChain;
|
|
66
|
-
} else {
|
|
67
|
-
logger.warn(
|
|
68
|
-
'createPublicClientWithRpc',
|
|
69
|
-
`Viem chain object (viemChain) not provided in EvmNetworkConfig for ${networkConfig.name} (query). Creating a minimal one.`
|
|
70
|
-
);
|
|
71
|
-
if (!networkConfig.rpcUrl) {
|
|
72
|
-
// Used for minimal object
|
|
73
|
-
throw new Error(
|
|
74
|
-
`RPC URL is missing in networkConfig for ${networkConfig.name} and viemChain is not set for query client.`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
chainForViem = {
|
|
78
|
-
id: networkConfig.chainId,
|
|
79
|
-
name: networkConfig.name,
|
|
80
|
-
nativeCurrency: networkConfig.nativeCurrency,
|
|
81
|
-
rpcUrls: {
|
|
82
|
-
default: { http: [networkConfig.rpcUrl] },
|
|
83
|
-
public: { http: [networkConfig.rpcUrl] },
|
|
84
|
-
},
|
|
85
|
-
blockExplorers: networkConfig.explorerUrl
|
|
86
|
-
? { default: { name: `${networkConfig.name} Explorer`, url: networkConfig.explorerUrl } }
|
|
87
|
-
: undefined,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const publicClient = createPublicClient({
|
|
93
|
-
chain: chainForViem,
|
|
94
|
-
transport: http(rpcUrl),
|
|
95
|
-
});
|
|
96
|
-
return publicClient;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
logger.error(
|
|
99
|
-
'createPublicClientWithRpc',
|
|
100
|
-
'Failed to create network-specific public client for query:',
|
|
101
|
-
error
|
|
102
|
-
);
|
|
103
|
-
throw new Error(
|
|
104
|
-
`Failed to create network-specific public client for query: ${(error as Error).message}`
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Core logic for querying an EVM view function.
|
|
111
|
-
*
|
|
112
|
-
* @param contractAddress Address of the contract.
|
|
113
|
-
* @param functionId ID of the function to query.
|
|
114
|
-
* @param networkConfig The specific network configuration.
|
|
115
|
-
* @param params Raw parameters for the function call.
|
|
116
|
-
* @param contractSchema Optional pre-loaded contract schema.
|
|
117
|
-
* @param walletImplementation Wallet implementation instance.
|
|
118
|
-
* @param loadContractFn Function reference to load contract schema if not provided.
|
|
119
|
-
* @returns The decoded result of the view function call.
|
|
120
|
-
*/
|
|
121
|
-
export async function queryEvmViewFunction(
|
|
122
|
-
contractAddress: string,
|
|
123
|
-
functionId: string,
|
|
124
|
-
networkConfig: TypedEvmNetworkConfig,
|
|
125
|
-
params: unknown[],
|
|
126
|
-
contractSchema: ContractSchema | undefined,
|
|
127
|
-
walletImplementation: WagmiWalletImplementation,
|
|
128
|
-
loadContractFn: (source: string) => Promise<ContractSchema>
|
|
129
|
-
): Promise<unknown> {
|
|
130
|
-
logger.info(
|
|
131
|
-
'queryEvmViewFunction',
|
|
132
|
-
`Querying view function: ${functionId} on ${contractAddress} (${networkConfig.name})`,
|
|
133
|
-
{ params }
|
|
134
|
-
);
|
|
135
|
-
try {
|
|
136
|
-
// --- Validate Address --- //
|
|
137
|
-
if (!contractAddress || !isAddress(contractAddress)) {
|
|
138
|
-
throw new Error(`Invalid contract address provided: ${contractAddress}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// --- Get Public Client --- //
|
|
142
|
-
const publicClient = await getPublicClientForQuery(walletImplementation, networkConfig);
|
|
143
|
-
|
|
144
|
-
// --- Get Schema & Function Details --- //
|
|
145
|
-
// loadContractFn (bound to adapter instance) uses internal networkConfig
|
|
146
|
-
const schema = contractSchema || (await loadContractFn(contractAddress));
|
|
147
|
-
const functionDetails = schema.functions.find((fn) => fn.id === functionId);
|
|
148
|
-
if (!functionDetails) {
|
|
149
|
-
throw new Error(`Function with ID ${functionId} not found in contract schema.`);
|
|
150
|
-
}
|
|
151
|
-
if (!isEvmViewFunction(functionDetails)) {
|
|
152
|
-
throw new Error(`Function ${functionDetails.name} is not a view function.`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// --- Parse Input Parameters --- //
|
|
156
|
-
const expectedInputs: readonly FunctionParameter[] = functionDetails.inputs;
|
|
157
|
-
if (params.length !== expectedInputs.length) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
`Incorrect number of parameters provided for ${functionDetails.name}. Expected ${expectedInputs.length}, got ${params.length}.`
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
const args = expectedInputs.map((inputParam: FunctionParameter, index: number) => {
|
|
163
|
-
let rawValue = params[index];
|
|
164
|
-
// If the ABI parameter type is an array (e.g., 'tuple[]', 'address[]') and
|
|
165
|
-
// the incoming raw value is an actual array (from programmatic usage),
|
|
166
|
-
// stringify it to align with parseEvmInput expectations for top-level arrays.
|
|
167
|
-
if (
|
|
168
|
-
typeof inputParam.type === 'string' &&
|
|
169
|
-
inputParam.type.endsWith('[]') &&
|
|
170
|
-
Array.isArray(rawValue)
|
|
171
|
-
) {
|
|
172
|
-
rawValue = JSON.stringify(rawValue);
|
|
173
|
-
}
|
|
174
|
-
return parseEvmInput(inputParam, rawValue, false);
|
|
175
|
-
});
|
|
176
|
-
logger.debug('queryEvmViewFunction', 'Parsed Args for readContract:', args);
|
|
177
|
-
|
|
178
|
-
// --- Construct ABI Item --- //
|
|
179
|
-
const functionAbiItem = createAbiFunctionItem(functionDetails);
|
|
180
|
-
|
|
181
|
-
logger.debug(
|
|
182
|
-
'queryEvmViewFunction',
|
|
183
|
-
`[Query ${functionDetails.name}] Calling readContract with ABI:`,
|
|
184
|
-
functionAbiItem,
|
|
185
|
-
'Args:',
|
|
186
|
-
args
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// --- Call readContract --- //
|
|
190
|
-
let decodedResult: unknown;
|
|
191
|
-
try {
|
|
192
|
-
decodedResult = await publicClient.readContract({
|
|
193
|
-
address: contractAddress as `0x${string}`,
|
|
194
|
-
abi: [functionAbiItem],
|
|
195
|
-
functionName: functionDetails.name,
|
|
196
|
-
args: args,
|
|
197
|
-
});
|
|
198
|
-
} catch (readError) {
|
|
199
|
-
logger.error(
|
|
200
|
-
'queryEvmViewFunction',
|
|
201
|
-
`[Query ${functionDetails.name}] publicClient.readContract specific error:`,
|
|
202
|
-
readError
|
|
203
|
-
);
|
|
204
|
-
throw new Error(
|
|
205
|
-
`Viem readContract failed for ${functionDetails.name}: ${(readError as Error).message}`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
logger.debug(
|
|
210
|
-
'queryEvmViewFunction',
|
|
211
|
-
`[Query ${functionDetails.name}] Raw decoded result:`,
|
|
212
|
-
decodedResult
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
return decodedResult;
|
|
216
|
-
} catch (error) {
|
|
217
|
-
const errorMessage = `Failed to query view function ${functionId} on network ${networkConfig.name}: ${(error as Error).message}`;
|
|
218
|
-
logger.error('queryEvmViewFunction', errorMessage, {
|
|
219
|
-
contractAddress,
|
|
220
|
-
functionId,
|
|
221
|
-
params,
|
|
222
|
-
networkConfig,
|
|
223
|
-
error,
|
|
224
|
-
});
|
|
225
|
-
throw new Error(errorMessage);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ContractFunction } from '@openzeppelin/ui-types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Determines if a function is a view/pure function (read-only).
|
|
5
|
-
* @param functionDetails The function details from the contract schema.
|
|
6
|
-
* @returns True if the function is read-only, false otherwise.
|
|
7
|
-
*/
|
|
8
|
-
export function isEvmViewFunction(functionDetails: ContractFunction): boolean {
|
|
9
|
-
return functionDetails.stateMutability === 'view' || functionDetails.stateMutability === 'pure';
|
|
10
|
-
}
|