@openzeppelin/ui-builder-adapter-evm 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -18
- package/dist/index.cjs +4651 -4448
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -226
- package/dist/index.d.ts +12 -226
- package/dist/index.js +4737 -4535
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/adapter-parsing.test.ts +4 -3
- package/src/__tests__/getDefaultServiceConfig.test.ts +185 -0
- package/src/__tests__/mocks/mock-network-configs.ts +1 -1
- package/src/__tests__/provenanceLinks.test.ts +6 -4
- package/src/__tests__/providerSelection.test.ts +5 -4
- package/src/__tests__/timeouts.test.ts +5 -3
- package/src/__tests__/wallet-connect.test.ts +2 -2
- package/src/adapter.ts +61 -107
- package/src/configuration/execution.ts +1 -52
- package/src/configuration/index.ts +2 -3
- package/src/configuration/network-services.ts +47 -60
- package/src/index.ts +22 -13
- package/src/networks/index.ts +2 -1
- package/src/networks/mainnet.ts +1 -1
- package/src/networks/testnet.ts +1 -1
- package/src/query/adapter-query.ts +72 -0
- package/src/query/index.ts +2 -2
- package/src/transaction/components/useEvmRelayerOptions.ts +5 -3
- package/src/transaction/index.ts +1 -5
- package/src/types/artifacts.ts +5 -30
- package/src/types/providers.ts +7 -18
- package/src/wallet/components/EvmWalletUiRoot.tsx +1 -1
- package/src/wallet/evmUiKitManager.ts +26 -129
- package/src/wallet/hooks/index.ts +0 -1
- package/src/wallet/implementation/wagmi-implementation.ts +45 -577
- package/src/wallet/index.ts +2 -3
- package/src/wallet/rainbowkit/__tests__/export-service.test.ts +1 -2
- package/src/wallet/rainbowkit/componentFactory.ts +10 -8
- package/src/wallet/rainbowkit/components.tsx +16 -133
- package/src/wallet/rainbowkit/index.ts +27 -5
- package/src/wallet/utils/__tests__/uiKitService.test.ts +5 -1
- package/src/wallet/utils/connection.ts +8 -52
- package/src/wallet/utils/index.ts +0 -2
- package/src/wallet/utils/uiKitService.ts +7 -3
- package/src/wallet/utils/walletImplementationManager.ts +5 -4
- package/src/wallet/utils.ts +1 -65
- package/src/abi/__tests__/etherscan-v2.test.ts +0 -117
- package/src/abi/__tests__/transformer.test.ts +0 -342
- package/src/abi/comparison.ts +0 -389
- package/src/abi/etherscan-v2.ts +0 -243
- package/src/abi/etherscan.ts +0 -158
- package/src/abi/index.ts +0 -7
- package/src/abi/loader.ts +0 -415
- package/src/abi/sourcify.ts +0 -75
- package/src/abi/transformer.ts +0 -163
- package/src/abi/types.ts +0 -101
- package/src/configuration/__tests__/explorer.test.ts +0 -174
- package/src/configuration/__tests__/rpc.test.ts +0 -176
- package/src/configuration/explorer.ts +0 -243
- package/src/configuration/rpc.ts +0 -257
- package/src/mapping/__tests__/field-generator.test.ts +0 -137
- package/src/mapping/__tests__/type-mapper.test.ts +0 -139
- package/src/mapping/constants.ts +0 -57
- package/src/mapping/field-generator.ts +0 -115
- package/src/mapping/index.ts +0 -4
- package/src/mapping/type-mapper.ts +0 -80
- package/src/proxy/detection.ts +0 -465
- package/src/query/handler.ts +0 -227
- package/src/query/view-checker.ts +0 -10
- package/src/transaction/eoa.ts +0 -98
- package/src/transaction/execution-strategy.ts +0 -33
- package/src/transaction/formatter.ts +0 -101
- package/src/transaction/relayer.ts +0 -380
- package/src/transaction/sender.ts +0 -185
- package/src/transform/index.ts +0 -3
- package/src/transform/input-parser.ts +0 -177
- package/src/transform/output-formatter.ts +0 -64
- package/src/types/__tests__/artifacts.test.ts +0 -105
- package/src/types.ts +0 -92
- package/src/utils/__tests__/artifacts.test.ts +0 -81
- package/src/utils/artifacts.ts +0 -30
- package/src/utils/formatting.ts +0 -25
- package/src/utils/gas.ts +0 -17
- package/src/utils/index.ts +0 -6
- package/src/utils/json.ts +0 -19
- package/src/utils/validation.ts +0 -10
- package/src/validation/eoa.ts +0 -33
- package/src/validation/index.ts +0 -2
- package/src/validation/relayer.ts +0 -13
- package/src/wallet/__tests__/utils.test.ts +0 -149
- package/src/wallet/components/account/AccountDisplay.tsx +0 -52
- package/src/wallet/components/connect/ConnectButton.tsx +0 -125
- package/src/wallet/components/connect/ConnectorDialog.tsx +0 -140
- package/src/wallet/components/index.ts +0 -4
- package/src/wallet/components/network/NetworkSwitcher.tsx +0 -90
- package/src/wallet/context/index.ts +0 -1
- package/src/wallet/context/wagmi-context.tsx +0 -7
- package/src/wallet/hooks/useIsWagmiProviderInitialized.ts +0 -11
- package/src/wallet/rainbowkit/config-generator.ts +0 -56
- package/src/wallet/rainbowkit/config-service.ts +0 -169
- package/src/wallet/rainbowkit/export-service.ts +0 -18
- package/src/wallet/rainbowkit/rainbowkitAssetManager.ts +0 -74
- package/src/wallet/rainbowkit/types.ts +0 -74
- package/src/wallet/rainbowkit/utils.ts +0 -96
- package/src/wallet/services/configResolutionService.ts +0 -65
- package/src/wallet/utils/SafeWagmiComponent.tsx +0 -72
- package/src/wallet/utils/filterWalletComponents.ts +0 -89
package/src/abi/transformer.ts
DELETED
|
@@ -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
|
-
});
|