@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,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
- }
@@ -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
- }