UNPKG

@abstraxn/relayer

Version:

Abstraxn Relayer package for handling gas-less transactions, facilitating smart contract interactions, and relaying user transactions efficiently.

324 lines 13.5 kB
import { Contract, ethers } from "ethers"; import { HttpMethod, sendRequest } from "./lib/httpRequests.js"; import { getTimestampInSeconds } from "./utils/HelperFunction.js"; import { WebSocketManager } from "./lib/WebSocketManager.js"; export class Relayer { constructor(relayerConfig) { var _a; this.relayerConfig = relayerConfig; this.isSafeTx = relayerConfig.isSafeTx === true; // If isSafeTx is true, provider, signer, and chainId are not required if (!this.isSafeTx) { if (!relayerConfig.provider || !relayerConfig.signer || !relayerConfig.chainId) { throw new Error("RelayerConfig must include a valid provider, signer, and chainId when isSafeTx is not set to true."); } this.chainId = relayerConfig.chainId; this.signer = relayerConfig.signer; this.provider = relayerConfig.provider; } this.relayerUrl = relayerConfig.relayerUrl; // Initialize WebSocket manager this.webSocketManager = new WebSocketManager(this.relayerUrl, relayerConfig.webSocket); // Auto-connect if enabled if (((_a = relayerConfig.webSocket) === null || _a === void 0 ? void 0 : _a.autoConnect) !== false) { this.webSocketManager.connect(); } } getRelayerUrl() { return `${this.relayerUrl}`; } async buildRelayerTx(params) { try { if (this.isSafeTx) { throw new Error("buildRelayerTx is not available when isSafeTx is true. Use sendSafeRelayerTx instead."); } if (!this.provider || !this.signer || !this.chainId) { throw new Error("Provider, signer, and chainId are required for buildRelayerTx."); } const { abi, contractAddress, method, args } = params; if (!abi || !contractAddress || !method) { throw new Error("Invalid parameters: 'abi', 'contractAddress', and 'method' are required."); } // Create contract instance const contract = new Contract(contractAddress, abi, this.provider); // Encode function signature const functionSignature = contract.interface.encodeFunctionData(method, args); // Get user address const userAddress = await this.signer.getAddress(); // Fetch nonce from contract const nonce = await contract.getNonce(userAddress); // Generate meta-transaction hash const metaTxHash = ethers.solidityPackedKeccak256(["uint256", "address", "uint256", "bytes"], [nonce, contractAddress, this.chainId, functionSignature]); // Sign the transaction hash const signature = await this.signer.signMessage(ethers.getBytes(metaTxHash)); const sig = ethers.Signature.from(signature); const { r, s, v } = sig; const executeMetaData = contract.interface.encodeFunctionData("executeMetaTransaction", [userAddress, functionSignature, r, s, v]); return { userAddress, functionSignature, signature, data: executeMetaData, chainId: this.chainId, contractAddress, }; } catch (error) { console.error("Error in buildRelayerTx:", error); throw new Error(`Failed to build relayer transaction: ${error instanceof Error ? error.message : error}`); } } async buildRelayerTxEIP712(params) { try { if (this.isSafeTx) { throw new Error("buildRelayerTxEIP712 is not available when isSafeTx is true. Use sendSafeRelayerTx instead."); } if (!this.provider || !this.signer || !this.chainId) { throw new Error("Provider, signer, and chainId are required for buildRelayerTxEIP712."); } const { abi, contractAddress, method, args } = params; if (!abi || !contractAddress || !method) { throw new Error("Invalid parameters: 'abi', 'contractAddress', and 'method' are required."); } const contract = new Contract(contractAddress, abi, this.provider); const userAddress = await this.signer.getAddress(); const functionSignature = contract.interface.encodeFunctionData(method, args); const nonce = await contract.getNonce(userAddress); const domain = { name: typeof contract.name === "function" ? await contract.name() : "MetaTx", version: "1", verifyingContract: contractAddress, salt: ethers.zeroPadValue(ethers.toBeHex(this.chainId), 32), }; const types = { MetaTransaction: [ { name: "nonce", type: "uint256" }, { name: "from", type: "address" }, { name: "functionSignature", type: "bytes" }, ], }; const message = { nonce: nonce.toString(), from: userAddress, functionSignature, }; let signature; let r, s; let v; if (typeof this.signer.signTypedData === "function") { signature = await this.signer.signTypedData(domain, types, message); const sig = ethers.Signature.from(signature); ({ r, s, v } = sig); } else if (typeof this.signer._signTypedData === "function") { signature = await this.signer._signTypedData(domain, types, message); const sig = ethers.Signature.from(signature); ({ r, s, v } = sig); } else { throw new Error("Signer does not support EIP-712 signing."); } const executeMetaData = contract.interface.encodeFunctionData("executeMetaTransaction", [ userAddress, functionSignature, r, s, v, ]); return { userAddress, functionSignature, signature, data: executeMetaData, chainId: this.chainId, contractAddress, }; } catch (error) { console.error("Error in buildRelayerTxEIP712:", error); throw new Error(`Failed to build EIP-712 relayer transaction: ${error instanceof Error ? error.message : error}`); } } async sendRelayerTx(txParams) { const relayerUrl = this.getRelayerUrl(); const sendRelayerResponse = await sendRequest({ url: relayerUrl, method: HttpMethod.Post, body: { method: "eth_sendTransactionToRelayer", params: txParams, id: getTimestampInSeconds(), jsonrpc: "2.0", }, }); const response = { message: sendRelayerResponse.message, transactionId: sendRelayerResponse.result, }; return response; } async sendSafeRelayerTx(txParams) { const relayerUrl = this.getRelayerUrl(); // Serialize BigInt values to strings for JSON serialization // JSON.stringify cannot serialize BigInt, so we convert them to strings const serializedParams = { ...txParams, safeExecTxPayload: { ...txParams.safeExecTxPayload, safeTx: { ...txParams.safeExecTxPayload.safeTx, value: txParams.safeExecTxPayload.safeTx.value.toString(), safeTxGas: txParams.safeExecTxPayload.safeTx.safeTxGas.toString(), baseGas: txParams.safeExecTxPayload.safeTx.baseGas.toString(), gasPrice: txParams.safeExecTxPayload.safeTx.gasPrice.toString(), nonce: txParams.safeExecTxPayload.safeTx.nonce.toString(), }, }, }; const sendRelayerResponse = await sendRequest({ url: relayerUrl, method: HttpMethod.Post, body: { method: "eth_sendSafeTransactionToRelayer", params: serializedParams, id: getTimestampInSeconds(), jsonrpc: "2.0", }, }); const response = { message: sendRelayerResponse.message, transactionId: sendRelayerResponse.result, }; return response; } async getRelayerTxStatus(transactionId) { const relayerUrl = this.getRelayerUrl(); const response = await sendRequest({ url: relayerUrl, method: HttpMethod.Post, body: { method: "abstraxn_getRelayerTxStatus", params: [transactionId], id: getTimestampInSeconds(), jsonrpc: "2.0", }, }); const txStatus = response.result; return txStatus; } /** * Extract key transaction information from the full transaction status */ extractTransactionInfo(txStatus) { var _a, _b, _c, _d, _e, _f, _g; return { hash: txStatus.hash, status: txStatus.status, blockNumber: (_a = txStatus.receipt) === null || _a === void 0 ? void 0 : _a.blockNumber, gasUsed: (_c = (_b = txStatus.receipt) === null || _b === void 0 ? void 0 : _b.gasUsed) === null || _c === void 0 ? void 0 : _c.toString(), effectiveGasPrice: (_e = (_d = txStatus.receipt) === null || _d === void 0 ? void 0 : _d.effectiveGasPrice) === null || _e === void 0 ? void 0 : _e.toString(), from: ((_f = txStatus.receipt) === null || _f === void 0 ? void 0 : _f.from) || null, to: ((_g = txStatus.receipt) === null || _g === void 0 ? void 0 : _g.to) || null, createdAt: txStatus.createdAt, reason: txStatus.reason }; } /** * Check if transaction is confirmed (has hash and receipt) */ isTransactionConfirmed(txStatus) { return txStatus.hash !== null && txStatus.receipt !== null && txStatus.status === 'confirmed'; } /** * Check if transaction is pending (no hash yet) */ isTransactionPending(txStatus) { return txStatus.hash === null && txStatus.status === 'pending'; } /** * Check if transaction failed */ isTransactionFailed(txStatus) { return txStatus.status === 'failed' || txStatus.reason !== null; } /** * Get transaction hash safely (returns null if not available) */ getTransactionHash(txStatus) { return txStatus.hash; } // WebSocket Methods /** * Send relayer transaction with real-time updates via WebSocket */ async sendRelayerTxWithRealTimeUpdates(params) { // Send the transaction first const response = await this.sendRelayerTx(params); // If real-time updates are enabled, subscribe to transaction updates if (params.enableRealTimeUpdates && response.transactionId) { try { await this.subscribeToTransaction(response.transactionId, params.webSocketEvents); } catch (error) { console.warn('Failed to subscribe to transaction updates:', error); // Don't throw error here as the transaction was still sent successfully } } return response; } async sendSafeRelayerTxWithRealTimeUpdates(params) { // Send the transaction first const response = await this.sendSafeRelayerTx(params); // If real-time updates are enabled, subscribe to transaction updates if (params.enableRealTimeUpdates && response.transactionId) { try { await this.subscribeToTransaction(response.transactionId, params.webSocketEvents); } catch (error) { console.warn('Failed to subscribe to transaction updates:', error); // Don't throw error here as the transaction was still sent successfully } } return response; } /** * Subscribe to real-time transaction updates */ async subscribeToTransaction(transactionId, events) { if (!this.webSocketManager.connected()) { throw new Error('WebSocket not connected. Call connectWebSocket() first.'); } return this.webSocketManager.subscribeToTransaction(transactionId, events || {}); } /** * Unsubscribe from transaction updates */ unsubscribeFromTransaction(transactionId) { this.webSocketManager.unsubscribeFromTransaction(transactionId); } /** * Connect to WebSocket server */ connectWebSocket() { this.webSocketManager.connect(); } /** * Disconnect from WebSocket server */ disconnectWebSocket() { this.webSocketManager.disconnect(); } /** * Check if WebSocket is connected */ isWebSocketConnected() { return this.webSocketManager.connected(); } /** * Get WebSocket connection information */ getWebSocketInfo() { return this.webSocketManager.getConnectionInfo(); } } //# sourceMappingURL=Relayer.js.map