@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,163 +0,0 @@
1
- import type { AbiFunction, AbiParameter, AbiStateMutability } from 'viem';
2
-
3
- import type { ContractFunction, ContractSchema, FunctionParameter } from '@openzeppelin/ui-types';
4
- import { logger } from '@openzeppelin/ui-utils';
5
-
6
- import type { AbiItem } from '../types';
7
- import { formatInputName, formatMethodName } from '../utils';
8
-
9
- /**
10
- * Transforms a standard ABI array (typically from an EVM-compatible chain)
11
- * into the project's internal `ContractSchema` format.
12
- * This schema is used by the builder app and renderer to represent contract interactions
13
- * in a chain-agnostic way (though this specific transformer is for EVM ABIs).
14
- *
15
- * @param abi The raw ABI array (e.g., parsed from a JSON ABI file or fetched from Etherscan).
16
- * It's expected to be an array of `AbiItem` (from viem types or a compatible structure).
17
- * @param contractName A name to assign to the contract within the schema. This might be derived
18
- * from a file name, user input, or a default if not otherwise available.
19
- * @param address Optional address of the deployed contract. If provided, it's included in the schema.
20
- * @returns A `ContractSchema` object representing the contract's interface.
21
- */
22
- export function transformAbiToSchema(
23
- abi: readonly AbiItem[],
24
- contractName: string,
25
- address?: string
26
- ): ContractSchema {
27
- logger.info('transformAbiToSchema', `Transforming ABI to ContractSchema for: ${contractName}`);
28
- const functions: ContractFunction[] = [];
29
-
30
- for (const item of abi) {
31
- // We are only interested in 'function' type items from the ABI
32
- // to map them to our ContractFunction interface.
33
- if (item.type === 'function') {
34
- // After confirming item.type is 'function', we can safely cast it to AbiFunction
35
- // to access function-specific properties like `stateMutability`, `inputs`, `outputs`.
36
- const abiFunctionItem = item as AbiFunction;
37
- functions.push({
38
- // Generate a unique ID for the function within the schema.
39
- // This often combines name and input types to handle overloads.
40
- id: `${abiFunctionItem.name}_${abiFunctionItem.inputs?.map((i) => i.type).join('_') || ''}`,
41
- name: abiFunctionItem.name || '', // Fallback for unnamed functions (though rare).
42
- displayName: formatMethodName(abiFunctionItem.name || ''), // Create a more readable name for UI.
43
- // Recursively map ABI inputs and outputs to our FunctionParameter structure.
44
- // This ensures that any non-standard properties (like 'internalType') are stripped.
45
- inputs: mapAbiParametersToSchemaParameters(abiFunctionItem.inputs),
46
- outputs: mapAbiParametersToSchemaParameters(abiFunctionItem.outputs),
47
- type: 'function', // Explicitly set, as we filtered for this type.
48
- stateMutability: abiFunctionItem.stateMutability, // Preserve EVM-specific state mutability.
49
- // Determine if the function modifies blockchain state based on its `stateMutability`.
50
- // This is a crucial piece of information for the UI (e.g., to differentiate read vs. write calls).
51
- modifiesState:
52
- !abiFunctionItem.stateMutability || // If undefined, assume it modifies state (safer default)
53
- !['view', 'pure'].includes(abiFunctionItem.stateMutability),
54
- });
55
- }
56
- }
57
-
58
- const contractSchema: ContractSchema = {
59
- ecosystem: 'evm', // This transformer is specific to EVM.
60
- name: contractName,
61
- address,
62
- functions,
63
- };
64
- logger.info(
65
- 'transformAbiToSchema',
66
- `Transformation complete. Found ${contractSchema.functions.length} functions.`
67
- );
68
- return contractSchema;
69
- }
70
-
71
- /**
72
- * Recursively maps an array of ABI parameters (from viem's `AbiParameter` type or compatible)
73
- * to an array of `FunctionParameter` objects, which is our internal representation.
74
- * This function is crucial for stripping any properties not defined in `FunctionParameter`
75
- * (e.g., `internalType` from the raw ABI) and for handling nested components (structs/tuples).
76
- *
77
- * @param abiParams An array of ABI parameter objects. Can be undefined (e.g., if a function has no inputs/outputs).
78
- * @returns An array of `FunctionParameter` objects, or an empty array if `abiParams` is undefined.
79
- */
80
- function mapAbiParametersToSchemaParameters(
81
- abiParams: readonly AbiParameter[] | undefined
82
- ): FunctionParameter[] {
83
- if (!abiParams) {
84
- return [];
85
- }
86
- return abiParams.map((param): FunctionParameter => {
87
- // Create the base FunctionParameter object, picking only defined properties.
88
- const schemaParam: FunctionParameter = {
89
- name: param.name || '', // Ensure name is a string, fallback if undefined in ABI.
90
- type: param.type, // The raw type string from the ABI (e.g., 'uint256', 'address', 'tuple').
91
- displayName: formatInputName(param.name || '', param.type), // Generate a user-friendly name.
92
- // `description` is not a standard part of an ABI parameter, so it's not mapped here.
93
- // It can be added later by the user in the builder app UI.
94
- };
95
- // Check for nested components (structs/tuples).
96
- // `param.type.startsWith('tuple')` checks if it's a tuple or tuple array.
97
- // `'components' in param` is a type guard for discriminated unions.
98
- // `param.components && param.components.length > 0` ensures components exist and are not empty.
99
- if (
100
- param.type.startsWith('tuple') &&
101
- 'components' in param && // Type guard for discriminated union (AbiParameter)
102
- param.components &&
103
- param.components.length > 0
104
- ) {
105
- // If components exist, recursively call this function to map them.
106
- // This ensures that nested structures also conform to `FunctionParameter` and strip extra fields.
107
- // Cast `param.components` because TypeScript might not fully infer its type after the `in` check within the map.
108
- schemaParam.components = mapAbiParametersToSchemaParameters(
109
- param.components as readonly AbiParameter[]
110
- );
111
- }
112
- return schemaParam;
113
- });
114
- }
115
-
116
- /**
117
- * Helper function to convert one of our internal `FunctionParameter` objects
118
- * back into a format compatible with viem's `AbiParameter` type.
119
- * This is primarily used by `createAbiFunctionItem` when constructing an `AbiFunction`
120
- * for interactions with viem or other ABI-consuming libraries.
121
- * It ensures that only properties expected by `AbiParameter` are included.
122
- *
123
- * @param param The internal `FunctionParameter` object.
124
- * @returns An `AbiParameter` object compatible with viem.
125
- */
126
- function mapSchemaParameterToAbiParameter(param: FunctionParameter): AbiParameter {
127
- // Handle tuple types specifically, as `AbiParameter` for tuples requires a `components` array.
128
- if (param.type.startsWith('tuple') && param.components && param.components.length > 0) {
129
- return {
130
- name: param.name || undefined, // ABI parameter names can be undefined (e.g., for return values).
131
- type: param.type as `tuple${string}`, // Cast to satisfy viem's specific tuple type string.
132
- // Recursively map nested components back to AbiParameter format.
133
- components: param.components.map(mapSchemaParameterToAbiParameter),
134
- };
135
- }
136
- // For non-tuple types, return a simpler AbiParameter structure.
137
- return {
138
- name: param.name || undefined,
139
- type: param.type,
140
- // `internalType` is not part of our `FunctionParameter` model, so it's not added back here.
141
- // Other ABI-specific fields like `indexed` (for events) are also not relevant here as
142
- // this function is focused on function parameters for `AbiFunction`.
143
- };
144
- }
145
-
146
- /**
147
- * Private helper to convert internal `ContractFunction` details (our model)
148
- * back into a viem `AbiFunction` object.
149
- * This is useful when interacting with libraries like viem that expect a standard ABI format.
150
- * Ensures that the generated AbiFunction conforms to viem's type definitions.
151
- *
152
- * @param functionDetails The `ContractFunction` object from our internal schema.
153
- * @returns An `AbiFunction` object.
154
- */
155
- export function createAbiFunctionItem(functionDetails: ContractFunction): AbiFunction {
156
- return {
157
- name: functionDetails.name,
158
- type: 'function',
159
- inputs: functionDetails.inputs.map(mapSchemaParameterToAbiParameter),
160
- outputs: functionDetails.outputs?.map(mapSchemaParameterToAbiParameter) || [],
161
- stateMutability: (functionDetails.stateMutability ?? 'view') as AbiStateMutability,
162
- };
163
- }
package/src/abi/types.ts DELETED
@@ -1,101 +0,0 @@
1
- /**
2
- * EVM-specific ABI types for comparison and validation
3
- * Uses viem's Abi type as the foundation for type safety
4
- */
5
-
6
- import type { Abi } from 'viem';
7
-
8
- /**
9
- * Result of comparing two ABIs
10
- */
11
- export interface AbiComparisonResult {
12
- /** Whether the ABIs are identical after normalization */
13
- identical: boolean;
14
- /** List of differences found between the ABIs */
15
- differences: AbiDifference[];
16
- /** Overall severity of the changes */
17
- severity: 'none' | 'minor' | 'major' | 'breaking';
18
- /** Human-readable summary of the comparison */
19
- summary: string;
20
- }
21
-
22
- /**
23
- * Represents a single difference between two ABIs
24
- */
25
- export interface AbiDifference {
26
- /** Type of change */
27
- type: 'added' | 'removed' | 'modified';
28
- /** Which section of the ABI was affected */
29
- section: 'function' | 'event' | 'constructor' | 'error' | 'fallback' | 'receive';
30
- /** Name of the affected item (or type if no name) */
31
- name: string;
32
- /** Detailed description of the change */
33
- details: string;
34
- /** Impact level of this change */
35
- impact: 'low' | 'medium' | 'high';
36
- /** Signature before the change (for removed/modified) */
37
- oldSignature?: string;
38
- /** Signature after the change (for added/modified) */
39
- newSignature?: string;
40
- }
41
-
42
- /**
43
- * Result of validating an ABI structure
44
- */
45
- export interface AbiValidationResult {
46
- /** Whether the ABI is structurally valid */
47
- valid: boolean;
48
- /** List of validation errors found */
49
- errors: string[];
50
- /** List of validation warnings */
51
- warnings: string[];
52
- /** Normalized ABI if validation passed */
53
- normalizedAbi?: Abi;
54
- }
55
-
56
- /**
57
- * Type guard to check if a value is a valid ABI array
58
- */
59
- export function isValidAbiArray(value: unknown): value is Abi {
60
- return Array.isArray(value) && value.every(isValidAbiItem);
61
- }
62
-
63
- /**
64
- * Type guard to check if a value is a valid ABI item
65
- */
66
- export function isValidAbiItem(item: unknown): boolean {
67
- if (typeof item !== 'object' || item === null) {
68
- return false;
69
- }
70
-
71
- const abiItem = item as Record<string, unknown>;
72
-
73
- // Must have a valid type
74
- if (typeof abiItem.type !== 'string') {
75
- return false;
76
- }
77
-
78
- const validTypes = ['function', 'event', 'constructor', 'error', 'fallback', 'receive'];
79
- if (!validTypes.includes(abiItem.type)) {
80
- return false;
81
- }
82
-
83
- // Functions and events must have a name
84
- if (
85
- (abiItem.type === 'function' || abiItem.type === 'event') &&
86
- typeof abiItem.name !== 'string'
87
- ) {
88
- return false;
89
- }
90
-
91
- // Functions, events, and constructors should have inputs array
92
- if (
93
- (abiItem.type === 'function' || abiItem.type === 'event' || abiItem.type === 'constructor') &&
94
- abiItem.inputs !== undefined &&
95
- !Array.isArray(abiItem.inputs)
96
- ) {
97
- return false;
98
- }
99
-
100
- return true;
101
- }
@@ -1,174 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import { EvmNetworkConfig, UserExplorerConfig } from '@openzeppelin/ui-types';
4
-
5
- import { testEvmExplorerConnection, validateEvmExplorerConfig } from '../../configuration/explorer';
6
-
7
- describe('validateEvmExplorerConfig', () => {
8
- it('should return true for valid configuration with all fields', () => {
9
- const config: UserExplorerConfig = {
10
- explorerUrl: 'https://etherscan.io',
11
- apiUrl: 'https://api.etherscan.io/api',
12
- apiKey: 'valid-key',
13
- name: 'Test',
14
- isCustom: true,
15
- };
16
- expect(validateEvmExplorerConfig(config)).toBe(true);
17
- });
18
-
19
- it('should return false for invalid explorerUrl', () => {
20
- const config: UserExplorerConfig = {
21
- explorerUrl: 'invalid-url',
22
- apiKey: 'valid-key',
23
- isCustom: true,
24
- };
25
- expect(validateEvmExplorerConfig(config)).toBe(false);
26
- });
27
-
28
- it('should return false for invalid apiUrl', () => {
29
- const config: UserExplorerConfig = {
30
- apiUrl: 'invalid-url',
31
- apiKey: 'valid-key',
32
- isCustom: true,
33
- };
34
- expect(validateEvmExplorerConfig(config)).toBe(false);
35
- });
36
-
37
- it('should return false for empty apiKey', () => {
38
- const config: UserExplorerConfig = {
39
- apiKey: '',
40
- isCustom: true,
41
- };
42
- expect(validateEvmExplorerConfig(config)).toBe(false);
43
- });
44
-
45
- it('should return true for configuration without optional fields', () => {
46
- const config: UserExplorerConfig = {
47
- apiKey: 'valid-key',
48
- isCustom: true,
49
- };
50
- expect(validateEvmExplorerConfig(config)).toBe(true);
51
- });
52
- });
53
-
54
- describe('testEvmExplorerConnection', () => {
55
- const mockFetch = vi.fn();
56
-
57
- beforeEach(() => {
58
- vi.stubGlobal('fetch', mockFetch);
59
- });
60
-
61
- afterEach(() => {
62
- vi.restoreAllMocks();
63
- });
64
-
65
- it('should fail without apiKey', async () => {
66
- const config: UserExplorerConfig = {
67
- apiUrl: 'https://api.etherscan.io/api',
68
- isCustom: true,
69
- };
70
- const result = await testEvmExplorerConnection(config);
71
- expect(result.success).toBe(false);
72
- expect(result.error).toBe('API key is required for testing connection to this explorer');
73
- });
74
-
75
- it('should fail without apiUrl and no networkConfig', async () => {
76
- const config: UserExplorerConfig = {
77
- apiKey: 'test-key',
78
- isCustom: true,
79
- };
80
- const result = await testEvmExplorerConnection(config);
81
- expect(result.success).toBe(false);
82
- expect(result.error).toContain('API URL is required');
83
- });
84
-
85
- it('should use networkConfig apiUrl if not provided', async () => {
86
- const config: UserExplorerConfig = {
87
- apiKey: 'test-key',
88
- isCustom: true,
89
- };
90
- const networkConfig: EvmNetworkConfig = {
91
- id: 'test',
92
- name: 'Test',
93
- network: 'test',
94
- type: 'testnet',
95
- isTestnet: true,
96
- apiUrl: 'https://api.etherscan.io/api',
97
- } as EvmNetworkConfig;
98
-
99
- mockFetch.mockResolvedValueOnce({
100
- ok: true,
101
- json: async () => ({ status: '1', result: '123' }),
102
- });
103
-
104
- const result = await testEvmExplorerConnection(config, networkConfig);
105
- expect(result.success).toBe(true);
106
- expect(result.latency).toBeDefined();
107
- });
108
-
109
- it('should succeed with valid response', async () => {
110
- const config: UserExplorerConfig = {
111
- apiUrl: 'https://api.etherscan.io/api',
112
- apiKey: 'test-key',
113
- isCustom: true,
114
- };
115
-
116
- mockFetch.mockResolvedValueOnce({
117
- ok: true,
118
- json: async () => ({ status: '1', result: '123' }),
119
- });
120
-
121
- const result = await testEvmExplorerConnection(config);
122
- expect(result.success).toBe(true);
123
- expect(result.latency).toBeDefined();
124
- });
125
-
126
- it('should fail on HTTP error', async () => {
127
- const config: UserExplorerConfig = {
128
- apiUrl: 'https://api.etherscan.io/api',
129
- apiKey: 'test-key',
130
- isCustom: true,
131
- };
132
-
133
- mockFetch.mockResolvedValueOnce({
134
- ok: false,
135
- status: 403,
136
- statusText: 'Forbidden',
137
- });
138
-
139
- const result = await testEvmExplorerConnection(config);
140
- expect(result.success).toBe(false);
141
- expect(result.error).toContain('HTTP 403');
142
- });
143
-
144
- it('should fail on API error in response', async () => {
145
- const config: UserExplorerConfig = {
146
- apiUrl: 'https://api.etherscan.io/api',
147
- apiKey: 'invalid-key',
148
- isCustom: true,
149
- };
150
-
151
- mockFetch.mockResolvedValueOnce({
152
- ok: true,
153
- json: async () => ({ status: '0', message: 'Invalid API Key' }),
154
- });
155
-
156
- const result = await testEvmExplorerConnection(config);
157
- expect(result.success).toBe(false);
158
- expect(result.error).toBe('Invalid API Key');
159
- });
160
-
161
- it('should handle fetch errors', async () => {
162
- const config: UserExplorerConfig = {
163
- apiUrl: 'https://api.etherscan.io/api',
164
- apiKey: 'test-key',
165
- isCustom: true,
166
- };
167
-
168
- mockFetch.mockRejectedValueOnce(new Error('Network error'));
169
-
170
- const result = await testEvmExplorerConnection(config);
171
- expect(result.success).toBe(false);
172
- expect(result.error).toBe('Network error');
173
- });
174
- });
@@ -1,176 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import type { EvmNetworkConfig } from '@openzeppelin/ui-types';
4
- import { appConfigService } from '@openzeppelin/ui-utils';
5
-
6
- import { resolveRpcUrl } from '../rpc';
7
-
8
- // Adjust path as needed
9
-
10
- // Helper to create a mock EvmNetworkConfig
11
- const createMockConfig = (id: string, rpcUrl?: string, name?: string): EvmNetworkConfig => ({
12
- id,
13
- name: name || `Test ${id}`,
14
- ecosystem: 'evm',
15
- network: 'test-network',
16
- type: 'testnet',
17
- isTestnet: true,
18
- exportConstName: id.replace(/-/g, ''),
19
- chainId: 12345, // Arbitrary chainId for testing
20
- rpcUrl: rpcUrl || '', // Allow undefined or empty for testing error cases
21
- nativeCurrency: { name: 'TestETH', symbol: 'TETH', decimals: 18 },
22
- primaryExplorerApiIdentifier: `${id}-explorer`,
23
- apiUrl: `https://api.${id}.com`,
24
- });
25
-
26
- // Mock the appConfigService from the correct package
27
- vi.mock('@openzeppelin/ui-utils', async (importOriginal) => {
28
- const original = await importOriginal<typeof import('@openzeppelin/ui-utils')>(); // Ensure correct type for original
29
- return {
30
- ...original,
31
- logger: {
32
- // Provide mock implementations for all logger methods used or default them
33
- debug: vi.fn(),
34
- info: vi.fn(),
35
- warn: vi.fn(),
36
- error: vi.fn(),
37
- // Add other methods if your code uses them, or a more generic mock
38
- },
39
- appConfigService: {
40
- getRpcEndpointOverride: vi.fn(),
41
- // Mock other methods of appConfigService if they were to be called by resolveRpcUrl or its dependencies
42
- // For resolveRpcUrl, only getRpcEndpointOverride is directly relevant.
43
- getConfig: vi.fn().mockReturnValue({ rpcEndpoints: {} }), // Provide a minimal getConfig mock
44
- },
45
- };
46
- });
47
-
48
- describe('resolveRpcUrl', () => {
49
- beforeEach(() => {
50
- // Reset mocks before each test to ensure test isolation
51
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReset();
52
- // Reset getConfig mock if it needs to change per test, or set a default good enough for all
53
- vi.mocked(appConfigService.getConfig).mockReturnValue({ rpcEndpoints: {} });
54
- });
55
-
56
- it('should use RPC override from AppConfigService if available (string)', () => {
57
- const networkId = 'mainnet-test-override';
58
- const overrideRpcUrl = 'https://appconfig-override.rpc.com';
59
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReturnValue(overrideRpcUrl);
60
-
61
- const config = createMockConfig(networkId, 'https://default.rpc.com');
62
- expect(resolveRpcUrl(config)).toBe(overrideRpcUrl);
63
- });
64
-
65
- it('should use RPC override from AppConfigService if available (object with http)', () => {
66
- const networkId = 'mainnet-test-object-override';
67
- const overrideRpcUrl = 'https://appconfig-object.rpc.com';
68
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReturnValue({ http: overrideRpcUrl });
69
-
70
- const config = createMockConfig(networkId, 'https://default.rpc.com');
71
- expect(resolveRpcUrl(config)).toBe(overrideRpcUrl);
72
- });
73
-
74
- it('should fall back to networkConfig.rpcUrl if no override is available', () => {
75
- const networkId = 'mainnet-test-fallback';
76
- const defaultRpcUrl = 'https://default-from-config.rpc.com';
77
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReturnValue(undefined);
78
-
79
- const config = createMockConfig(networkId, defaultRpcUrl);
80
- expect(resolveRpcUrl(config)).toBe(defaultRpcUrl);
81
- });
82
-
83
- it('should fall back to networkConfig.rpcUrl if override is invalid URL', () => {
84
- const networkId = 'mainnet-test-invalid-override';
85
- const defaultRpcUrl = 'https://default-valid.rpc.com';
86
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReturnValue('invalid-url');
87
-
88
- const config = createMockConfig(networkId, defaultRpcUrl);
89
- expect(resolveRpcUrl(config)).toBe(defaultRpcUrl);
90
- // Optionally, check if logger.warn was called (requires logger mock setup)
91
- });
92
-
93
- it('should throw an error if no valid RPC URL (override or default) is found', () => {
94
- const networkId = 'no-valid-rpc';
95
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReturnValue(undefined);
96
- const config = createMockConfig(networkId, undefined); // No default RPC
97
-
98
- expect(() => resolveRpcUrl(config)).toThrowError(
99
- `No valid RPC URL configured for network Test ${networkId} (ID: ${networkId}).`
100
- );
101
- });
102
-
103
- it('should throw an error if default RPC is invalid and no override is found', () => {
104
- const networkId = 'invalid-default-rpc';
105
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReturnValue(undefined);
106
- const config = createMockConfig(networkId, 'not-a-url');
107
-
108
- expect(() => resolveRpcUrl(config)).toThrowError(
109
- `No valid RPC URL configured for network Test ${networkId} (ID: ${networkId}).`
110
- );
111
- });
112
-
113
- it('should use override even if default is invalid', () => {
114
- const networkId = 'override-wins-over-invalid-default';
115
- const overrideRpcUrl = 'https://good-override.rpc.com';
116
- vi.mocked(appConfigService.getRpcEndpointOverride).mockReturnValue(overrideRpcUrl);
117
-
118
- const config = createMockConfig(networkId, 'bad-default-url');
119
- expect(resolveRpcUrl(config)).toBe(overrideRpcUrl);
120
- });
121
-
122
- // The following tests are no longer valid as resolveRpcUrl does not directly access env vars.
123
- // Environment variable overrides are handled by AppConfigService.
124
- /*
125
- it('should use VITE_RPC_URL_<NETWORK_ID> if set', () => {
126
- const networkId = 'ethereum-mainnet';
127
- const envRpcUrl = 'https://env-override.rpc.com';
128
- vi.stubEnv(`VITE_RPC_URL_ETHEREUM_MAINNET`, envRpcUrl);
129
-
130
- const config = createMockConfig(networkId, 'https://config.rpc.com');
131
- expect(resolveRpcUrl(config)).toBe(envRpcUrl);
132
- });
133
-
134
- it('should correctly format NETWORK_ID with hyphens for env var lookup', () => {
135
- const networkId = 'some-test-network';
136
- const envRpcUrl = 'https://hyphen-test.rpc.com';
137
- vi.stubEnv(`VITE_RPC_URL_SOME_TEST_NETWORK`, envRpcUrl);
138
-
139
- const config = createMockConfig(networkId, 'https://config.rpc.com');
140
- expect(resolveRpcUrl(config)).toBe(envRpcUrl);
141
- });
142
-
143
- it('should use networkConfig.rpcUrl if no specific environment variable is set', () => {
144
- const networkId = 'ethereum-sepolia';
145
- const configRpcUrl = 'https://sepolia-public.rpc.com';
146
- const config = createMockConfig(networkId, configRpcUrl);
147
-
148
- // No need to delete, unstubAllEnvs handles cleanup
149
- expect(resolveRpcUrl(config)).toBe(configRpcUrl);
150
- });
151
-
152
- it('should throw an error if rpcUrl is missing in config and no env var is set', () => {
153
- const networkId = 'missing-rpc-config';
154
- // Create a config where rpcUrl is explicitly undefined, cast to bypass type check for test
155
- const config = {
156
- ...createMockConfig(networkId, 'http://dummy.com'), // provide a dummy for base object creation
157
- rpcUrl: undefined as unknown as string, // Then force undefined
158
- };
159
-
160
- expect(() => resolveRpcUrl(config)).toThrowError(
161
- `Could not resolve RPC URL for network: ${config.name}. Please ensure networkConfig.rpcUrl is set or provide the VITE_RPC_URL_MISSING_RPC_CONFIG environment variable.`
162
- );
163
- });
164
-
165
- it('should handle network IDs with different casings for env var lookup (e.g. all caps)', () => {
166
- const networkIdInConfig = 'allcapsnet-lower'; // e.g., from a file
167
- const networkIdForEnv = 'ALLCAPSNET_LOWER'; // The key format
168
- const envRpcUrl = 'https://allcaps.rpc.com';
169
- vi.stubEnv(`VITE_RPC_URL_${networkIdForEnv}`, envRpcUrl);
170
-
171
- // networkConfig.id is used to derive the env var key, so it should match the intended lookup pattern
172
- const config = createMockConfig(networkIdInConfig, 'https://config.rpc.com');
173
- expect(resolveRpcUrl(config)).toBe(envRpcUrl);
174
- });
175
- */
176
- });