@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
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for EVM contract artifacts utility functions
|
|
3
|
-
*/
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
-
|
|
6
|
-
import { validateAndConvertEvmArtifacts } from '../artifacts';
|
|
7
|
-
|
|
8
|
-
describe('validateAndConvertEvmArtifacts', () => {
|
|
9
|
-
it('should convert string address to artifacts object', () => {
|
|
10
|
-
const address = '0x1234567890123456789012345678901234567890';
|
|
11
|
-
|
|
12
|
-
const result = validateAndConvertEvmArtifacts(address);
|
|
13
|
-
|
|
14
|
-
expect(result).toEqual({
|
|
15
|
-
contractAddress: address,
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should return valid artifacts object as-is', () => {
|
|
20
|
-
const artifacts = {
|
|
21
|
-
contractAddress: '0x1234567890123456789012345678901234567890',
|
|
22
|
-
contractDefinition: '[{"type":"function","name":"test"}]',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const result = validateAndConvertEvmArtifacts(artifacts);
|
|
26
|
-
|
|
27
|
-
expect(result).toBe(artifacts);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should return artifacts with proxy detection options', () => {
|
|
31
|
-
const artifacts = {
|
|
32
|
-
contractAddress: '0x1234567890123456789012345678901234567890',
|
|
33
|
-
__proxyDetectionOptions: {
|
|
34
|
-
skipProxyDetection: true,
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const result = validateAndConvertEvmArtifacts(artifacts);
|
|
39
|
-
|
|
40
|
-
expect(result).toBe(artifacts);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should throw error for invalid artifacts object', () => {
|
|
44
|
-
const invalidArtifacts = {
|
|
45
|
-
someOtherProperty: 'value',
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
expect(() => validateAndConvertEvmArtifacts(invalidArtifacts)).toThrow(
|
|
49
|
-
'Invalid contract artifacts provided. Expected an object with contractAddress property.'
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should throw error for artifacts with non-string contractAddress', () => {
|
|
54
|
-
const invalidArtifacts = {
|
|
55
|
-
contractAddress: 123,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
expect(() => validateAndConvertEvmArtifacts(invalidArtifacts)).toThrow(
|
|
59
|
-
'Invalid contract artifacts provided. Expected an object with contractAddress property.'
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should accept artifacts with extra properties', () => {
|
|
64
|
-
const artifacts = {
|
|
65
|
-
contractAddress: '0x1234567890123456789012345678901234567890',
|
|
66
|
-
extraProperty: 'should be ignored',
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const result = validateAndConvertEvmArtifacts(artifacts);
|
|
70
|
-
|
|
71
|
-
expect(result).toEqual(artifacts);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should handle empty string address', () => {
|
|
75
|
-
const result = validateAndConvertEvmArtifacts('');
|
|
76
|
-
|
|
77
|
-
expect(result).toEqual({
|
|
78
|
-
contractAddress: '',
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
});
|
package/src/utils/artifacts.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility functions for EVM contract artifacts validation and conversion
|
|
3
|
-
*/
|
|
4
|
-
import type { EvmContractArtifacts } from '../types/artifacts';
|
|
5
|
-
import { isEvmContractArtifacts } from '../types/artifacts';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Validates and converts generic source input to EvmContractArtifacts
|
|
9
|
-
*
|
|
10
|
-
* @param source - Generic contract source (string address or artifacts object)
|
|
11
|
-
* @returns Validated EvmContractArtifacts
|
|
12
|
-
* @throws Error if the source is invalid
|
|
13
|
-
*/
|
|
14
|
-
export function validateAndConvertEvmArtifacts(
|
|
15
|
-
source: string | Record<string, unknown>
|
|
16
|
-
): EvmContractArtifacts {
|
|
17
|
-
if (typeof source === 'string') {
|
|
18
|
-
// If source is a string, assume it's a contract address
|
|
19
|
-
return { contractAddress: source };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Validate that the object has the required structure
|
|
23
|
-
if (!isEvmContractArtifacts(source)) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
'Invalid contract artifacts provided. Expected an object with contractAddress property.'
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return source;
|
|
30
|
-
}
|
package/src/utils/formatting.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format a method name for display (e.g., from camelCase to Title Case).
|
|
3
|
-
*/
|
|
4
|
-
export function formatMethodName(name: string): string {
|
|
5
|
-
if (!name) return '';
|
|
6
|
-
return name
|
|
7
|
-
.replace(/([A-Z])/g, ' $1')
|
|
8
|
-
.replace(/^./, (str) => str.toUpperCase())
|
|
9
|
-
.trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Format an input name for display (e.g., from camelCase or snake_case to Title Case).
|
|
14
|
-
* Provides a default name based on type if the original name is empty.
|
|
15
|
-
*/
|
|
16
|
-
export function formatInputName(name: string, type: string): string {
|
|
17
|
-
if (!name || name === '') {
|
|
18
|
-
return `Parameter (${type})`; // Use type if name is missing
|
|
19
|
-
}
|
|
20
|
-
return name
|
|
21
|
-
.replace(/([A-Z])/g, ' $1') // Add space before capitals
|
|
22
|
-
.replace(/_/g, ' ') // Replace underscores with spaces
|
|
23
|
-
.replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter
|
|
24
|
-
.trim();
|
|
25
|
-
}
|
package/src/utils/gas.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { formatGwei, parseGwei } from 'viem';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Convert wei values to gwei for display using viem
|
|
5
|
-
*/
|
|
6
|
-
export const weiToGwei = (wei?: number): number | undefined => {
|
|
7
|
-
if (!wei) return undefined;
|
|
8
|
-
return parseFloat(formatGwei(BigInt(wei)));
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Convert gwei values to wei using viem
|
|
13
|
-
*/
|
|
14
|
-
export const gweiToWei = (gwei?: number): number | undefined => {
|
|
15
|
-
if (!gwei) return undefined;
|
|
16
|
-
return Number(parseGwei(gwei.toString()));
|
|
17
|
-
};
|
package/src/utils/index.ts
DELETED
package/src/utils/json.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom JSON stringifier that handles BigInt values by converting them to strings.
|
|
3
|
-
* @param value The value to stringify.
|
|
4
|
-
* @param space Adds indentation, white space, and line break characters to the return-value JSON text for readability.
|
|
5
|
-
* @returns A JSON string representing the given value.
|
|
6
|
-
*/
|
|
7
|
-
export function stringifyWithBigInt(value: unknown, space?: number | string): string {
|
|
8
|
-
const replacer = (_key: string, val: unknown) => {
|
|
9
|
-
// Check if the value is a BigInt
|
|
10
|
-
if (typeof val === 'bigint') {
|
|
11
|
-
// Convert BigInt to string
|
|
12
|
-
return val.toString();
|
|
13
|
-
}
|
|
14
|
-
// Return the value unchanged for other types
|
|
15
|
-
return val;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
return JSON.stringify(value, replacer, space);
|
|
19
|
-
}
|
package/src/utils/validation.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { isAddress } from 'viem';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validates if a string is a valid EVM address.
|
|
5
|
-
* @param address The address string to validate.
|
|
6
|
-
* @returns True if the address is valid, false otherwise.
|
|
7
|
-
*/
|
|
8
|
-
export function isValidEvmAddress(address: string): boolean {
|
|
9
|
-
return isAddress(address);
|
|
10
|
-
}
|
package/src/validation/eoa.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { EoaExecutionConfig } from '@openzeppelin/ui-types';
|
|
2
|
-
import { logger } from '@openzeppelin/ui-utils';
|
|
3
|
-
|
|
4
|
-
import { isValidEvmAddress } from '../utils';
|
|
5
|
-
import { EvmWalletConnectionStatus } from '../wallet/types';
|
|
6
|
-
|
|
7
|
-
const SYSTEM_LOG_TAG = 'EoaValidator';
|
|
8
|
-
|
|
9
|
-
export async function validateEoaConfig(
|
|
10
|
-
config: EoaExecutionConfig,
|
|
11
|
-
walletStatus: EvmWalletConnectionStatus
|
|
12
|
-
): Promise<true | string> {
|
|
13
|
-
if (!config.allowAny) {
|
|
14
|
-
if (!config.specificAddress) {
|
|
15
|
-
return "EOA execution selected, but no specific address was provided when 'allowAny' is false.";
|
|
16
|
-
}
|
|
17
|
-
if (!isValidEvmAddress(config.specificAddress)) {
|
|
18
|
-
return `Invalid specific EOA address format: ${config.specificAddress}`;
|
|
19
|
-
}
|
|
20
|
-
if (walletStatus.isConnected && walletStatus.address) {
|
|
21
|
-
if (walletStatus.address.toLowerCase() !== config.specificAddress.toLowerCase()) {
|
|
22
|
-
return `Connected wallet address (${walletStatus.address}) does not match the required specific EOA address (${config.specificAddress}). Please connect the correct wallet.`;
|
|
23
|
-
}
|
|
24
|
-
} else if (walletStatus.isConnected && !walletStatus.address) {
|
|
25
|
-
logger.warn(
|
|
26
|
-
SYSTEM_LOG_TAG,
|
|
27
|
-
'Wallet is connected but address is unavailable for EOA validation.'
|
|
28
|
-
);
|
|
29
|
-
return 'Connected wallet address is not available for validation against specific EOA.';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
33
|
-
}
|
package/src/validation/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { RelayerExecutionConfig } from '@openzeppelin/ui-types';
|
|
2
|
-
|
|
3
|
-
export async function validateRelayerConfig(
|
|
4
|
-
config: RelayerExecutionConfig
|
|
5
|
-
): Promise<true | string> {
|
|
6
|
-
if (!config.serviceUrl) {
|
|
7
|
-
return 'Relayer execution selected, but no service URL was provided.';
|
|
8
|
-
}
|
|
9
|
-
if (!config.relayer?.relayerId) {
|
|
10
|
-
return 'Relayer execution selected, but no relayer was chosen from the list.';
|
|
11
|
-
}
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { logger } from '@openzeppelin/ui-utils';
|
|
4
|
-
|
|
5
|
-
import { resolveAndInitializeKitConfig } from '../utils';
|
|
6
|
-
|
|
7
|
-
// Mock the logger to prevent console output during tests and allow spying
|
|
8
|
-
vi.mock('@openzeppelin/ui-utils', () => ({
|
|
9
|
-
logger: {
|
|
10
|
-
debug: vi.fn(),
|
|
11
|
-
info: vi.fn(),
|
|
12
|
-
warn: vi.fn(),
|
|
13
|
-
error: vi.fn(),
|
|
14
|
-
},
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
describe('resolveAndInitializeKitConfig', () => {
|
|
18
|
-
const mockLoadConfigModule = vi.fn();
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
// Reset mocks before each test
|
|
22
|
-
mockLoadConfigModule.mockReset();
|
|
23
|
-
vi.clearAllMocks();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should return null if no kitName, programmaticConfig, or loadConfigModule is provided', async () => {
|
|
27
|
-
const result = await resolveAndInitializeKitConfig(undefined, undefined, undefined);
|
|
28
|
-
expect(result).toBeNull();
|
|
29
|
-
expect(logger.debug).toHaveBeenCalledWith(
|
|
30
|
-
'resolveAndInitializeKitConfig',
|
|
31
|
-
'Resolving native config for kit: none',
|
|
32
|
-
{ hasProgrammaticKitConfig: false, hasLoadConfigModule: false }
|
|
33
|
-
);
|
|
34
|
-
expect(logger.debug).toHaveBeenCalledWith(
|
|
35
|
-
'resolveAndInitializeKitConfig',
|
|
36
|
-
'No native or programmatic kitConfig provided for none. Returning null.'
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should return programmaticKitConfig if kitName is provided but loadConfigModule is not', async () => {
|
|
41
|
-
const programmaticConfig = { settingA: 'valueA' };
|
|
42
|
-
const result = await resolveAndInitializeKitConfig('rainbowkit', programmaticConfig, undefined);
|
|
43
|
-
expect(result).toEqual(programmaticConfig);
|
|
44
|
-
expect(mockLoadConfigModule).not.toHaveBeenCalled();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should return null if kitName is provided, loadConfigModule is not, and no programmaticKitConfig', async () => {
|
|
48
|
-
const result = await resolveAndInitializeKitConfig('rainbowkit', undefined, undefined);
|
|
49
|
-
expect(result).toBeNull();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should not call loadConfigModule if kitName is "custom"', async () => {
|
|
53
|
-
const programmaticConfig = { settingB: 'valueB' };
|
|
54
|
-
const result = await resolveAndInitializeKitConfig(
|
|
55
|
-
'custom',
|
|
56
|
-
programmaticConfig,
|
|
57
|
-
mockLoadConfigModule
|
|
58
|
-
);
|
|
59
|
-
expect(result).toEqual(programmaticConfig);
|
|
60
|
-
expect(mockLoadConfigModule).not.toHaveBeenCalled();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should not call loadConfigModule if kitName is "none"', async () => {
|
|
64
|
-
const result = await resolveAndInitializeKitConfig('none', undefined, mockLoadConfigModule);
|
|
65
|
-
expect(result).toBeNull();
|
|
66
|
-
expect(mockLoadConfigModule).not.toHaveBeenCalled();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('with kitName and loadConfigModule provided', () => {
|
|
70
|
-
const kitName = 'rainbowkit';
|
|
71
|
-
const conventionalPath = `./config/wallet/${kitName}.config.ts`;
|
|
72
|
-
|
|
73
|
-
it('should return userNativeConfig if loadConfigModule succeeds and no programmaticConfig', async () => {
|
|
74
|
-
const nativeConfig = { nativeSetting: 'nativeValue' };
|
|
75
|
-
mockLoadConfigModule.mockResolvedValue(nativeConfig);
|
|
76
|
-
const result = await resolveAndInitializeKitConfig(kitName, undefined, mockLoadConfigModule);
|
|
77
|
-
expect(result).toEqual(nativeConfig);
|
|
78
|
-
expect(mockLoadConfigModule).toHaveBeenCalledWith(conventionalPath);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should merge userNativeConfig and programmaticKitConfig, with programmatic taking precedence', async () => {
|
|
82
|
-
const nativeConfig = { common: 'native', nativeOnly: 'present' };
|
|
83
|
-
const programmaticConfig = { common: 'programmatic', progOnly: 'here' };
|
|
84
|
-
mockLoadConfigModule.mockResolvedValue(nativeConfig);
|
|
85
|
-
const result = await resolveAndInitializeKitConfig(
|
|
86
|
-
kitName,
|
|
87
|
-
programmaticConfig,
|
|
88
|
-
mockLoadConfigModule
|
|
89
|
-
);
|
|
90
|
-
expect(result).toEqual({
|
|
91
|
-
common: 'programmatic',
|
|
92
|
-
nativeOnly: 'present',
|
|
93
|
-
progOnly: 'here',
|
|
94
|
-
});
|
|
95
|
-
expect(mockLoadConfigModule).toHaveBeenCalledWith(conventionalPath);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should return programmaticKitConfig if loadConfigModule returns null', async () => {
|
|
99
|
-
const programmaticConfig = { settingC: 'valueC' };
|
|
100
|
-
mockLoadConfigModule.mockResolvedValue(null);
|
|
101
|
-
const result = await resolveAndInitializeKitConfig(
|
|
102
|
-
kitName,
|
|
103
|
-
programmaticConfig,
|
|
104
|
-
mockLoadConfigModule
|
|
105
|
-
);
|
|
106
|
-
expect(result).toEqual(programmaticConfig);
|
|
107
|
-
expect(mockLoadConfigModule).toHaveBeenCalledWith(conventionalPath);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should return null if loadConfigModule returns null and no programmaticConfig', async () => {
|
|
111
|
-
mockLoadConfigModule.mockResolvedValue(null);
|
|
112
|
-
const result = await resolveAndInitializeKitConfig(kitName, undefined, mockLoadConfigModule);
|
|
113
|
-
expect(result).toBeNull();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should return programmaticKitConfig if loadConfigModule throws an error', async () => {
|
|
117
|
-
const programmaticConfig = { settingD: 'valueD' };
|
|
118
|
-
mockLoadConfigModule.mockImplementation(async () => {
|
|
119
|
-
throw new Error('File system error');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
await expect(
|
|
123
|
-
resolveAndInitializeKitConfig(kitName, programmaticConfig, mockLoadConfigModule)
|
|
124
|
-
).resolves.toEqual(programmaticConfig);
|
|
125
|
-
|
|
126
|
-
expect(mockLoadConfigModule).toHaveBeenCalledWith(conventionalPath);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should return null if loadConfigModule throws and no programmaticConfig', async () => {
|
|
130
|
-
mockLoadConfigModule.mockImplementation(async () => {
|
|
131
|
-
throw new Error('Another error');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
await expect(
|
|
135
|
-
resolveAndInitializeKitConfig(kitName, undefined, mockLoadConfigModule)
|
|
136
|
-
).resolves.toBeNull();
|
|
137
|
-
|
|
138
|
-
expect(mockLoadConfigModule).toHaveBeenCalledWith(conventionalPath);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should correctly use kitName in conventional path for loading', async () => {
|
|
143
|
-
const kitName = 'anotherkit';
|
|
144
|
-
const expectedPath = `./config/wallet/${kitName}.config.ts`;
|
|
145
|
-
mockLoadConfigModule.mockResolvedValue({ some: 'data' });
|
|
146
|
-
await resolveAndInitializeKitConfig(kitName, undefined, mockLoadConfigModule);
|
|
147
|
-
expect(mockLoadConfigModule).toHaveBeenCalledWith(expectedPath);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { LogOut } from 'lucide-react';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
import { Button } from '@openzeppelin/ui-components';
|
|
5
|
-
import { useDerivedAccountStatus, useDerivedDisconnect } from '@openzeppelin/ui-react';
|
|
6
|
-
import type { BaseComponentProps } from '@openzeppelin/ui-types';
|
|
7
|
-
import { cn, truncateMiddle } from '@openzeppelin/ui-utils';
|
|
8
|
-
|
|
9
|
-
import { SafeWagmiComponent } from '../../utils/SafeWagmiComponent';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* A component that displays the connected account address and chain ID.
|
|
13
|
-
* Also includes a disconnect button.
|
|
14
|
-
*/
|
|
15
|
-
export const CustomAccountDisplay: React.FC<BaseComponentProps> = ({ className }) => {
|
|
16
|
-
// Use the SafeWagmiComponent with null fallback
|
|
17
|
-
return (
|
|
18
|
-
<SafeWagmiComponent fallback={null}>
|
|
19
|
-
<AccountDisplayContent className={className} />
|
|
20
|
-
</SafeWagmiComponent>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Inner component that uses derived hooks
|
|
25
|
-
const AccountDisplayContent: React.FC<{ className?: string }> = ({ className }) => {
|
|
26
|
-
const { isConnected, address, chainId } = useDerivedAccountStatus();
|
|
27
|
-
const { disconnect } = useDerivedDisconnect();
|
|
28
|
-
|
|
29
|
-
if (!isConnected || !address || !disconnect) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div className={cn('flex items-center gap-2', className)}>
|
|
35
|
-
<div className="flex flex-col">
|
|
36
|
-
<span className="text-xs font-medium">{truncateMiddle(address, 4, 4)}</span>
|
|
37
|
-
<span className="text-[9px] text-muted-foreground -mt-0.5">
|
|
38
|
-
{chainId ? `Chain ID: ${chainId}` : 'Chain ID: N/A'}
|
|
39
|
-
</span>
|
|
40
|
-
</div>
|
|
41
|
-
<Button
|
|
42
|
-
onClick={() => disconnect()}
|
|
43
|
-
variant="ghost"
|
|
44
|
-
size="icon"
|
|
45
|
-
className="size-6 p-0"
|
|
46
|
-
title="Disconnect wallet"
|
|
47
|
-
>
|
|
48
|
-
<LogOut className="size-3.5" />
|
|
49
|
-
</Button>
|
|
50
|
-
</div>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { Loader2, Wallet } from 'lucide-react';
|
|
2
|
-
import React, { useEffect, useState } from 'react';
|
|
3
|
-
|
|
4
|
-
import { Button } from '@openzeppelin/ui-components';
|
|
5
|
-
import { useDerivedAccountStatus, useDerivedConnectStatus } from '@openzeppelin/ui-react';
|
|
6
|
-
import type { BaseComponentProps } from '@openzeppelin/ui-types';
|
|
7
|
-
import { cn } from '@openzeppelin/ui-utils';
|
|
8
|
-
|
|
9
|
-
import { SafeWagmiComponent } from '../../utils/SafeWagmiComponent';
|
|
10
|
-
import { ConnectorDialog } from './ConnectorDialog';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A button that allows users to connect their wallet.
|
|
14
|
-
* Opens a dialog to select from available connectors.
|
|
15
|
-
* @param hideWhenConnected - Whether to hide the button when wallet is connected (default: true)
|
|
16
|
-
*/
|
|
17
|
-
export interface ConnectButtonProps extends BaseComponentProps {
|
|
18
|
-
hideWhenConnected?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const CustomConnectButton: React.FC<ConnectButtonProps> = ({
|
|
22
|
-
className,
|
|
23
|
-
hideWhenConnected = true,
|
|
24
|
-
}) => {
|
|
25
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
26
|
-
|
|
27
|
-
const unavailableButton = (
|
|
28
|
-
<div className={cn('flex items-center', className)}>
|
|
29
|
-
<Button disabled={true} variant="outline" size="sm" className="h-8 px-2 text-xs">
|
|
30
|
-
<Wallet className="size-3.5 mr-1" />
|
|
31
|
-
Wallet Unavailable
|
|
32
|
-
</Button>
|
|
33
|
-
</div>
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<SafeWagmiComponent fallback={unavailableButton}>
|
|
38
|
-
<ConnectButtonContent
|
|
39
|
-
className={className}
|
|
40
|
-
dialogOpen={dialogOpen}
|
|
41
|
-
setDialogOpen={setDialogOpen}
|
|
42
|
-
hideWhenConnected={hideWhenConnected}
|
|
43
|
-
/>
|
|
44
|
-
</SafeWagmiComponent>
|
|
45
|
-
);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const ConnectButtonContent: React.FC<{
|
|
49
|
-
className?: string;
|
|
50
|
-
dialogOpen: boolean;
|
|
51
|
-
setDialogOpen: (open: boolean) => void;
|
|
52
|
-
hideWhenConnected: boolean;
|
|
53
|
-
}> = ({ className, dialogOpen, setDialogOpen, hideWhenConnected }) => {
|
|
54
|
-
const { isConnected } = useDerivedAccountStatus();
|
|
55
|
-
const { isConnecting: isHookConnecting, error: connectError } = useDerivedConnectStatus();
|
|
56
|
-
|
|
57
|
-
// Local state to indicate the button has been clicked and dialog is open, awaiting user selection
|
|
58
|
-
const [isManuallyInitiated, setIsManuallyInitiated] = useState(false);
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (isConnected && hideWhenConnected) {
|
|
62
|
-
setDialogOpen(false);
|
|
63
|
-
setIsManuallyInitiated(false); // Reset if dialog closes due to connection
|
|
64
|
-
}
|
|
65
|
-
}, [isConnected, hideWhenConnected, setDialogOpen]);
|
|
66
|
-
|
|
67
|
-
// If dialog is closed, reset manual initiation state
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (!dialogOpen) {
|
|
70
|
-
setIsManuallyInitiated(false);
|
|
71
|
-
}
|
|
72
|
-
}, [dialogOpen]);
|
|
73
|
-
|
|
74
|
-
// If wagmi hook reports it's connecting, we no longer need our manual pending state
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (isHookConnecting) {
|
|
77
|
-
setIsManuallyInitiated(false);
|
|
78
|
-
}
|
|
79
|
-
}, [isHookConnecting]);
|
|
80
|
-
|
|
81
|
-
const handleConnectClick = () => {
|
|
82
|
-
if (!isConnected) {
|
|
83
|
-
setIsManuallyInitiated(true); // User clicked, show pending on button
|
|
84
|
-
setDialogOpen(true);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
if (isConnected && hideWhenConnected) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Button shows loading if either hook reports connecting OR if user just clicked to open dialog
|
|
93
|
-
const showButtonLoading = isHookConnecting || isManuallyInitiated;
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<div className={cn('flex items-center', className)}>
|
|
97
|
-
<Button
|
|
98
|
-
onClick={handleConnectClick}
|
|
99
|
-
disabled={showButtonLoading || isConnected}
|
|
100
|
-
variant="outline"
|
|
101
|
-
size="sm"
|
|
102
|
-
className="h-8 px-2 text-xs"
|
|
103
|
-
title={isConnected ? 'Connected' : connectError?.message || 'Connect Wallet'}
|
|
104
|
-
>
|
|
105
|
-
{showButtonLoading ? (
|
|
106
|
-
<Loader2 className="size-3.5 animate-spin mr-1" />
|
|
107
|
-
) : (
|
|
108
|
-
<Wallet className="size-3.5 mr-1" />
|
|
109
|
-
)}
|
|
110
|
-
{showButtonLoading ? 'Connecting...' : 'Connect Wallet'}
|
|
111
|
-
</Button>
|
|
112
|
-
|
|
113
|
-
<ConnectorDialog
|
|
114
|
-
open={dialogOpen}
|
|
115
|
-
onOpenChange={(open) => {
|
|
116
|
-
setDialogOpen(open);
|
|
117
|
-
// If dialog is closed manually by user before selection, reset manual initiation
|
|
118
|
-
if (!open) {
|
|
119
|
-
setIsManuallyInitiated(false);
|
|
120
|
-
}
|
|
121
|
-
}}
|
|
122
|
-
/>
|
|
123
|
-
</div>
|
|
124
|
-
);
|
|
125
|
-
};
|