@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,98 +0,0 @@
1
- import { GetAccountReturnType } from '@wagmi/core';
2
- import { WalletClient } from 'viem';
3
-
4
- import {
5
- EoaExecutionConfig,
6
- ExecutionConfig,
7
- TransactionStatusUpdate,
8
- TxStatus,
9
- } from '@openzeppelin/ui-types';
10
- import { logger } from '@openzeppelin/ui-utils';
11
-
12
- import { WriteContractParameters } from '../types';
13
- import { validateEoaConfig } from '../validation';
14
- import { WagmiWalletImplementation } from '../wallet/implementation/wagmi-implementation';
15
- import { ExecutionStrategy } from './execution-strategy';
16
-
17
- const SYSTEM_LOG_TAG = 'EoaExecutionStrategy';
18
-
19
- /**
20
- * Implements the ExecutionStrategy for a standard Externally Owned Account (EOA).
21
- * This strategy involves signing and broadcasting a transaction directly from the user's
22
- * connected wallet, which is the most common way of interacting with a blockchain.
23
- */
24
- export class EoaExecutionStrategy implements ExecutionStrategy {
25
- public async execute(
26
- transactionData: WriteContractParameters,
27
- executionConfig: ExecutionConfig,
28
- walletImplementation: WagmiWalletImplementation,
29
- onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,
30
- // runtimeApiKey is unused in EOA strategy but required by the interface
31
- _runtimeApiKey?: string
32
- ): Promise<{ txHash: string }> {
33
- const { walletClient, accountStatus } =
34
- await this.getAuthenticatedWalletClient(walletImplementation);
35
-
36
- // Final validation at the point of execution
37
- const eoaConfig = executionConfig as EoaExecutionConfig;
38
- const validationResult = await validateEoaConfig(eoaConfig, {
39
- ...accountStatus,
40
- chainId: accountStatus.chainId?.toString(),
41
- });
42
- if (validationResult !== true) {
43
- throw new Error(validationResult);
44
- }
45
-
46
- logger.info(SYSTEM_LOG_TAG, 'Using EOA execution strategy.');
47
- try {
48
- logger.debug(SYSTEM_LOG_TAG, 'Calling walletClient.writeContract with:', {
49
- account: accountStatus.address,
50
- address: transactionData.address,
51
- abi: transactionData.abi,
52
- functionName: transactionData.functionName,
53
- args: transactionData.args,
54
- value: transactionData.value,
55
- chain: walletClient.chain,
56
- });
57
-
58
- onStatusChange('pendingSignature', {});
59
-
60
- const hash = await walletClient.writeContract({
61
- account: accountStatus.address!,
62
- address: transactionData.address,
63
- abi: transactionData.abi,
64
- functionName: transactionData.functionName,
65
- args: transactionData.args,
66
- value: transactionData.value,
67
- chain: walletClient.chain,
68
- });
69
-
70
- logger.info(SYSTEM_LOG_TAG, 'EOA Transaction initiated. Hash:', hash);
71
- return { txHash: hash };
72
- } catch (error: unknown) {
73
- logger.error(SYSTEM_LOG_TAG, 'Error during EOA writeContract call:', error);
74
- const errorMessage = error instanceof Error ? error.message : 'Unknown EOA transaction error';
75
- throw new Error(`Transaction failed (EOA): ${errorMessage}`);
76
- }
77
- }
78
-
79
- private async getAuthenticatedWalletClient(
80
- walletImplementation: WagmiWalletImplementation
81
- ): Promise<{
82
- walletClient: WalletClient;
83
- accountStatus: GetAccountReturnType;
84
- }> {
85
- const walletClient = await walletImplementation.getWalletClient();
86
- if (!walletClient) {
87
- logger.error(SYSTEM_LOG_TAG, 'Wallet client not available. Is wallet connected?');
88
- throw new Error('Wallet is not connected or client is unavailable.');
89
- }
90
-
91
- const accountStatus = walletImplementation.getWalletConnectionStatus();
92
- if (!accountStatus.isConnected || !accountStatus.address) {
93
- logger.error(SYSTEM_LOG_TAG, 'Account not available. Is wallet connected?');
94
- throw new Error('Wallet is not connected or account address is unavailable.');
95
- }
96
- return { walletClient, accountStatus };
97
- }
98
- }
@@ -1,33 +0,0 @@
1
- import { ExecutionConfig, TransactionStatusUpdate, TxStatus } from '@openzeppelin/ui-types';
2
-
3
- import { WriteContractParameters } from '../types';
4
- import { WagmiWalletImplementation } from '../wallet/implementation/wagmi-implementation';
5
-
6
- /**
7
- * Defines a common interface for different transaction execution strategies (e.g., EOA, Relayer).
8
- * This allows the adapter to remain a lean orchestrator that selects the appropriate strategy
9
- * at runtime based on the user's configuration.
10
- */
11
- export interface ExecutionStrategy {
12
- /**
13
- * Executes a transaction according to the specific strategy.
14
- *
15
- * @param transactionData The contract write parameters, including ABI, function name, and args.
16
- * @param executionConfig The configuration for the selected execution method.
17
- * @param walletImplementation The wallet implementation to use for signing. Even for strategies
18
- * that do not require a direct wallet signature for gas (like some relayers), this is kept
19
- * as a required parameter to support meta-transactions, which still need a user's signature
20
- * on the transaction data itself. This ensures a consistent interface and future-proofs the
21
- * architecture for more advanced relayer patterns.
22
- * @param onStatusChange A callback to report real-time status updates to the UI.
23
- * @param runtimeApiKey Optional session-only API key for methods like Relayer.
24
- * @returns A promise that resolves to an object containing the final transaction hash.
25
- */
26
- execute(
27
- transactionData: WriteContractParameters,
28
- executionConfig: ExecutionConfig,
29
- walletImplementation: WagmiWalletImplementation,
30
- onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,
31
- runtimeApiKey?: string
32
- ): Promise<{ txHash: string }>;
33
- }
@@ -1,101 +0,0 @@
1
- import { isAddress } from 'viem';
2
-
3
- import type { ContractSchema, FormFieldType } from '@openzeppelin/ui-types';
4
- import { logger } from '@openzeppelin/ui-utils';
5
-
6
- import { createAbiFunctionItem } from '../abi';
7
- import { parseEvmInput } from '../transform';
8
- import type { WriteContractParameters } from '../types';
9
-
10
- /**
11
- * Formats transaction data for EVM chains based on parsed inputs.
12
- *
13
- * @param contractSchema The contract schema.
14
- * @param functionId The ID of the function being called.
15
- * @param submittedInputs The raw data submitted from the form.
16
- * @param fields The fields of the form schema.
17
- * @returns The formatted data payload suitable for signAndBroadcast.
18
- */
19
- export function formatEvmTransactionData(
20
- contractSchema: ContractSchema,
21
- functionId: string,
22
- submittedInputs: Record<string, unknown>,
23
- fields: FormFieldType[]
24
- ): WriteContractParameters {
25
- logger.info(
26
- 'formatEvmTransactionData',
27
- `Formatting EVM transaction data for function: ${functionId}`
28
- );
29
-
30
- // --- Step 1: Determine Argument Order --- //
31
- const functionDetails = contractSchema.functions.find((fn) => fn.id === functionId);
32
- if (!functionDetails) {
33
- throw new Error(`Function definition for ${functionId} not found in provided contract schema.`);
34
- }
35
- const expectedArgs = functionDetails.inputs;
36
-
37
- // --- Step 2: Iterate and Select Values --- //
38
- const orderedRawValues: unknown[] = [];
39
- for (const expectedArg of expectedArgs) {
40
- const fieldConfig = fields.find((field: FormFieldType) => field.name === expectedArg.name);
41
- if (!fieldConfig) {
42
- throw new Error(`Configuration missing for argument: ${expectedArg.name} in provided fields`);
43
- }
44
- let value: unknown;
45
- if (fieldConfig.isHardcoded) {
46
- value = fieldConfig.hardcodedValue;
47
- } else if (fieldConfig.isHidden) {
48
- throw new Error(`Field '${fieldConfig.name}' cannot be hidden without being hardcoded.`);
49
- } else {
50
- if (!(fieldConfig.name in submittedInputs)) {
51
- throw new Error(`Missing submitted input for required field: ${fieldConfig.name}`);
52
- }
53
- value = submittedInputs[fieldConfig.name];
54
- }
55
- orderedRawValues.push(value);
56
- }
57
-
58
- // --- Step 3: Parse/Transform Values using the imported parser --- //
59
- const transformedArgs = expectedArgs.map((param, index) => {
60
- let valueToParse = orderedRawValues[index];
61
-
62
- // If the ABI parameter type is an array (e.g., 'tuple[]', 'address[]') and
63
- // the raw value from the form/runtime is an array (not already a string),
64
- // stringify it for parseEvmInput which expects JSON at the top-level.
65
- if (
66
- typeof param.type === 'string' &&
67
- param.type.endsWith('[]') &&
68
- Array.isArray(valueToParse)
69
- ) {
70
- valueToParse = JSON.stringify(valueToParse);
71
- }
72
-
73
- return parseEvmInput(param, valueToParse, false);
74
- });
75
-
76
- // --- Step 4 & 5: Prepare Return Object --- //
77
- const isPayable = functionDetails.stateMutability === 'payable';
78
- let transactionValue = 0n; // Use BigInt zero
79
- if (isPayable) {
80
- logger.warn(
81
- 'formatEvmTransactionData',
82
- 'Payable function detected, but sending 0 ETH. Implement value input.'
83
- );
84
- // TODO: Read value from submittedInputs or config when payable input is implemented
85
- }
86
-
87
- const functionAbiItem = createAbiFunctionItem(functionDetails);
88
-
89
- if (!contractSchema.address || !isAddress(contractSchema.address)) {
90
- throw new Error('Contract address is missing or invalid in the provided schema.');
91
- }
92
-
93
- const paramsForSignAndBroadcast: WriteContractParameters = {
94
- address: contractSchema.address,
95
- abi: [functionAbiItem],
96
- functionName: functionDetails.name,
97
- args: transformedArgs,
98
- value: transactionValue, // Pass BigInt value
99
- };
100
- return paramsForSignAndBroadcast;
101
- }
@@ -1,380 +0,0 @@
1
- // This file will contain the business logic for interacting with the Relayer SDK
2
- import { encodeFunctionData, formatEther } from 'viem';
3
-
4
- import {
5
- Configuration,
6
- RelayersApi,
7
- Speed,
8
- type ApiResponseRelayerResponseData,
9
- type EvmTransactionRequest,
10
- type EvmTransactionResponse,
11
- } from '@openzeppelin/relayer-sdk';
12
- import {
13
- ExecutionConfig,
14
- RelayerDetails,
15
- RelayerDetailsRich,
16
- RelayerExecutionConfig,
17
- TransactionStatusUpdate,
18
- TxStatus,
19
- } from '@openzeppelin/ui-types';
20
- import { logger } from '@openzeppelin/ui-utils';
21
-
22
- import { TypedEvmNetworkConfig, WriteContractParameters } from '../types';
23
- import { WagmiWalletImplementation } from '../wallet/implementation/wagmi-implementation';
24
- import { ExecutionStrategy } from './execution-strategy';
25
-
26
- /**
27
- * EVM-specific transaction options for the OpenZeppelin Relayer.
28
- * These options map directly to the EvmTransactionRequest parameters in the SDK.
29
- */
30
- export interface EvmRelayerTransactionOptions {
31
- // Basic options that most users will want to configure
32
- speed?: Speed;
33
- gasLimit?: number;
34
-
35
- // Advanced options for fine-grained control
36
- gasPrice?: number;
37
- maxFeePerGas?: number;
38
- maxPriorityFeePerGas?: number;
39
-
40
- // Transaction expiration
41
- validUntil?: string; // ISO 8601 date string
42
- }
43
-
44
- /**
45
- * Implements the ExecutionStrategy for the OpenZeppelin Relayer.
46
- * This strategy sends the transaction to the relayer service, which then handles
47
- * gas payment, signing, and broadcasting. It includes a polling mechanism to wait
48
- * for the transaction to be mined and return the final hash.
49
- */
50
- export class RelayerExecutionStrategy implements ExecutionStrategy {
51
- public async execute(
52
- transactionData: WriteContractParameters,
53
- executionConfig: ExecutionConfig,
54
- _walletImplementation: WagmiWalletImplementation,
55
- onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,
56
- runtimeApiKey?: string
57
- ): Promise<{ txHash: string }> {
58
- const relayerConfig = executionConfig as RelayerExecutionConfig;
59
-
60
- if (!runtimeApiKey) {
61
- throw new Error('API Key is required for Relayer execution.');
62
- }
63
-
64
- const { transactionId } = await this.sendTransactionViaRelayer(
65
- transactionData,
66
- relayerConfig,
67
- runtimeApiKey
68
- );
69
-
70
- onStatusChange('pendingRelayer', { transactionId });
71
-
72
- const sdkConfig = new Configuration({
73
- basePath: relayerConfig.serviceUrl,
74
- accessToken: runtimeApiKey,
75
- });
76
-
77
- const txHash = await this.pollForTransactionHash(
78
- relayerConfig.relayer.relayerId,
79
- transactionId,
80
- sdkConfig
81
- );
82
-
83
- return { txHash };
84
- }
85
-
86
- /**
87
- * Fetches and filters relayers for a specific EVM network from the OpenZeppelin Relayer service.
88
- * This function handles pagination to retrieve all available relayers.
89
- *
90
- * @param serviceUrl The base URL of the relayer service.
91
- * @param accessToken The session-based API key for authentication.
92
- * @param networkConfig The EVM network configuration to filter relayers by.
93
- * @returns A promise that resolves to an array of compatible relayer details.
94
- * @throws If the API call fails or returns an unsuccessful response.
95
- */
96
- public async getEvmRelayers(
97
- serviceUrl: string,
98
- accessToken: string,
99
- networkConfig: TypedEvmNetworkConfig
100
- ): Promise<RelayerDetails[]> {
101
- logger.info(
102
- '[Relayer] Getting relayers with access token',
103
- accessToken.slice(0, 5).padEnd(accessToken.length, '*')
104
- );
105
- const sdkConfig = new Configuration({
106
- basePath: serviceUrl,
107
- accessToken,
108
- });
109
- const relayersApi = new RelayersApi(sdkConfig);
110
-
111
- let allRelayers: ApiResponseRelayerResponseData[] = [];
112
- let currentPage = 1;
113
- let totalItems = 0;
114
- let hasMore = true;
115
-
116
- do {
117
- const { data } = await relayersApi.listRelayers(currentPage, 100);
118
-
119
- if (!data.success || !data.data) {
120
- throw new Error(`Failed to fetch relayers on page ${currentPage}.`);
121
- }
122
-
123
- allRelayers = [...allRelayers, ...data.data];
124
- totalItems = data.pagination?.total_items || 0;
125
-
126
- if (allRelayers.length >= totalItems) {
127
- hasMore = false;
128
- } else {
129
- currentPage++;
130
- }
131
- } while (hasMore);
132
-
133
- return allRelayers
134
- .filter(
135
- (r: ApiResponseRelayerResponseData) =>
136
- r.network_type === 'evm' && networkConfig.id.includes(r.network)
137
- )
138
- .map((r: ApiResponseRelayerResponseData) => ({
139
- relayerId: r.id,
140
- name: r.name,
141
- address: r.address || '',
142
- network: r.network,
143
- paused: r.paused || false,
144
- }));
145
- }
146
-
147
- /**
148
- * Fetches comprehensive information about a specific relayer including balance and status.
149
- * This function combines multiple SDK API calls to provide rich relayer details.
150
- *
151
- * @param serviceUrl The base URL of the relayer service.
152
- * @param accessToken The session-based API key for authentication.
153
- * @param relayerId The unique identifier of the relayer.
154
- * @param networkConfig The EVM network configuration to get the native currency symbol.
155
- * @returns A promise that resolves to enhanced relayer details including balance and status.
156
- * @throws If any API call fails or returns an unsuccessful response.
157
- */
158
- public async getEvmRelayer(
159
- serviceUrl: string,
160
- accessToken: string,
161
- relayerId: string,
162
- networkConfig: TypedEvmNetworkConfig
163
- ): Promise<RelayerDetailsRich> {
164
- logger.info('[Relayer] Getting detailed relayer info', relayerId);
165
-
166
- const sdkConfig = new Configuration({
167
- basePath: serviceUrl,
168
- accessToken,
169
- });
170
- const relayersApi = new RelayersApi(sdkConfig);
171
-
172
- try {
173
- // Fetch basic relayer details, balance, and status in parallel
174
- const [relayerResponse, balanceResponse, statusResponse] = await Promise.all([
175
- relayersApi.getRelayer(relayerId),
176
- relayersApi.getRelayerBalance(relayerId).catch((err) => {
177
- logger.warn('[Relayer] Failed to fetch balance', err);
178
- return null;
179
- }),
180
- relayersApi.getRelayerStatus(relayerId).catch((err) => {
181
- logger.warn('[Relayer] Failed to fetch status', err);
182
- return null;
183
- }),
184
- ]);
185
-
186
- if (!relayerResponse.data.success || !relayerResponse.data.data) {
187
- throw new Error(`Failed to fetch relayer details for ID: ${relayerId}`);
188
- }
189
-
190
- const relayerData = relayerResponse.data.data;
191
-
192
- // Build enhanced relayer details object
193
- const enhancedDetails: RelayerDetailsRich = {
194
- relayerId: relayerData.id,
195
- name: relayerData.name,
196
- address: relayerData.address || '',
197
- network: relayerData.network,
198
- paused: relayerData.paused || false,
199
- systemDisabled: relayerData.system_disabled || false,
200
- };
201
-
202
- // Add balance if available
203
- if (balanceResponse?.data?.success && balanceResponse.data.data?.balance) {
204
- try {
205
- // Format balance from wei to native currency
206
- const balanceInWei = BigInt(balanceResponse.data.data.balance);
207
- const balanceInEth = formatEther(balanceInWei);
208
- const currencySymbol = networkConfig.nativeCurrency.symbol;
209
- enhancedDetails.balance = `${balanceInEth} ${currencySymbol}`;
210
- } catch (error) {
211
- logger.warn('[Relayer] Failed to format balance, using raw value', String(error));
212
- enhancedDetails.balance = String(balanceResponse.data.data.balance);
213
- }
214
- }
215
-
216
- // Add status details if available
217
- if (statusResponse?.data?.success && statusResponse.data.data) {
218
- const statusData = statusResponse.data.data;
219
- if (statusData.network_type === 'evm') {
220
- if (statusData.nonce !== undefined && statusData.nonce !== null) {
221
- enhancedDetails.nonce = String(statusData.nonce);
222
- }
223
- if (statusData.pending_transactions_count !== undefined) {
224
- enhancedDetails.pendingTransactionsCount = statusData.pending_transactions_count;
225
- }
226
- if (statusData.last_confirmed_transaction_timestamp) {
227
- enhancedDetails.lastConfirmedTransactionTimestamp =
228
- statusData.last_confirmed_transaction_timestamp;
229
- }
230
- }
231
- }
232
-
233
- logger.info('[Relayer] Retrieved enhanced relayer details', JSON.stringify(enhancedDetails));
234
- return enhancedDetails;
235
- } catch (error) {
236
- logger.error(
237
- '[Relayer] Failed to get relayer details',
238
- error instanceof Error ? error.message : String(error)
239
- );
240
- throw error;
241
- }
242
- }
243
-
244
- /**
245
- * Submits a transaction to the relayer service for asynchronous processing.
246
- * @param transactionData The contract write parameters.
247
- * @param executionConfig The relayer-specific execution configuration.
248
- * @param runtimeApiKey The user's session-only API key.
249
- * @returns A promise that resolves to an object containing the transaction ID assigned by the relayer.
250
- */
251
- private async sendTransactionViaRelayer(
252
- transactionData: WriteContractParameters,
253
- executionConfig: RelayerExecutionConfig,
254
- runtimeApiKey: string
255
- ): Promise<{ transactionId: string }> {
256
- const data = encodeFunctionData({
257
- abi: transactionData.abi,
258
- functionName: transactionData.functionName,
259
- args: transactionData.args,
260
- });
261
-
262
- // Type-safe extraction of EVM-specific options
263
- const evmOptions = executionConfig.transactionOptions as
264
- | EvmRelayerTransactionOptions
265
- | undefined;
266
-
267
- // Compute value for relayer request. The SDK type is number, but JS Number
268
- // cannot safely represent large wei amounts. Prefer passing zero when undefined
269
- // or warn when truncation would occur.
270
- const valueBigint = transactionData.value ?? 0n;
271
- let valueNumber: number = 0;
272
- const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
273
- if (valueBigint > MAX_SAFE) {
274
- logger.warn(
275
- '[Relayer] Value exceeds JS safe integer. Truncating for request.',
276
- valueBigint.toString()
277
- );
278
- valueNumber = Number(MAX_SAFE);
279
- } else {
280
- valueNumber = Number(valueBigint);
281
- }
282
-
283
- const relayerTxRequest: EvmTransactionRequest = {
284
- to: transactionData.address,
285
- data,
286
- value: valueNumber,
287
- // If no explicit gas limit is provided, keep a conservative default but warn.
288
- gas_limit: (() => {
289
- if (typeof evmOptions?.gasLimit === 'number') return evmOptions.gasLimit;
290
- logger.warn(
291
- '[Relayer]',
292
- 'No gasLimit provided; using default 210000. Consider setting explicitly.'
293
- );
294
- return 210000;
295
- })(),
296
- // Note: The OpenZeppelin Relayer API requires exactly one gas pricing strategy to be provided.
297
- // Valid options are: speed, gas_price, or both max_fee_per_gas + max_priority_fee_per_gas.
298
- // If none are provided, the API will return a 400 Bad Request error.
299
- // Only include speed if explicitly set in options
300
- ...(evmOptions?.speed !== undefined && { speed: evmOptions.speed }),
301
- // Include optional parameters only if provided
302
- ...(evmOptions?.gasPrice !== undefined && { gas_price: evmOptions.gasPrice }),
303
- ...(evmOptions?.maxFeePerGas !== undefined && { max_fee_per_gas: evmOptions.maxFeePerGas }),
304
- ...(evmOptions?.maxPriorityFeePerGas !== undefined && {
305
- max_priority_fee_per_gas: evmOptions.maxPriorityFeePerGas,
306
- }),
307
- ...(evmOptions?.validUntil !== undefined && { valid_until: evmOptions.validUntil }),
308
- };
309
-
310
- const sdkConfig = new Configuration({
311
- basePath: executionConfig.serviceUrl,
312
- accessToken: runtimeApiKey,
313
- });
314
- const relayersApi = new RelayersApi(sdkConfig);
315
-
316
- const result = await relayersApi.sendTransaction(
317
- executionConfig.relayer.relayerId,
318
- relayerTxRequest
319
- );
320
-
321
- if (!result.data.success || !result.data.data?.id) {
322
- throw new Error(`Relayer API failed to return a transaction ID. Error: ${result.data.error}`);
323
- }
324
-
325
- return { transactionId: result.data.data.id };
326
- }
327
-
328
- /**
329
- * Polls the relayer for a transaction's status until it is mined and has a hash, or fails.
330
- * @param relayerId The ID of the relayer processing the transaction.
331
- * @param transactionId The ID of the transaction to poll.
332
- * @param sdkConfig The SDK configuration containing the necessary authentication.
333
- * @returns A promise that resolves to the final transaction hash.
334
- * @throws If the transaction fails or polling times out.
335
- */
336
- private async pollForTransactionHash(
337
- relayerId: string,
338
- transactionId: string,
339
- sdkConfig: Configuration
340
- ): Promise<string> {
341
- const relayersApi = new RelayersApi(sdkConfig);
342
- const POLLING_INTERVAL = 2000;
343
- const POLLING_TIMEOUT = 300000; // 5 minutes in milliseconds
344
- const startTime = Date.now();
345
-
346
- while (Date.now() - startTime < POLLING_TIMEOUT) {
347
- const { data } = await relayersApi.getTransactionById(relayerId, transactionId);
348
-
349
- if (!data.success || !data.data) {
350
- throw new Error(`Failed to get transaction status for ID: ${transactionId}`);
351
- }
352
-
353
- const txResponse = data.data as EvmTransactionResponse;
354
-
355
- if (txResponse.status === 'mined' || txResponse.status === 'confirmed') {
356
- if (!txResponse.hash) {
357
- throw new Error(
358
- `Transaction is confirmed but no hash was returned for ID: ${transactionId}`
359
- );
360
- }
361
- return txResponse.hash;
362
- }
363
-
364
- if (
365
- txResponse.status === 'failed' ||
366
- txResponse.status === 'canceled' ||
367
- txResponse.status === 'expired'
368
- ) {
369
- throw new Error(
370
- `Transaction ${txResponse.status}: ${txResponse.status_reason || 'No reason provided.'}`
371
- );
372
- }
373
-
374
- // Continue polling for 'pending' or 'sent' statuses
375
- await new Promise((resolve) => setTimeout(resolve, POLLING_INTERVAL));
376
- }
377
-
378
- throw new Error(`Polling for transaction hash timed out for ID: ${transactionId}`);
379
- }
380
- }