@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
JavaScript
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