@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/transaction/eoa.ts
DELETED
|
@@ -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
|
-
}
|