Copy class MetaTxGateway {
constructor(config) {
this.contract = new ethers.Contract(
config.contractAddress,
config.abi,
config.provider
);
this.relayerEndpoint = config.relayerEndpoint;
this.domain = {
name: "IXFI MetaTx Gateway",
version: "1",
chainId: config.chainId,
verifyingContract: config.contractAddress
};
this.types = {
MetaTransaction: [
{ name: "user", type: "address" },
{ name: "target", type: "address" },
{ name: "functionSignature", type: "bytes" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
}
async executeGaslessTransaction(userSigner, target, functionSignature, options = {}) {
try {
const userAddress = await userSigner.getAddress();
const nonce = await this.contract.getNonce(userAddress);
const deadline = options.deadline || Math.floor(Date.now() / 1000) + 3600;
// Create meta-transaction data
const metaTxData = {
user: userAddress,
target: target,
functionSignature: functionSignature,
nonce: nonce,
deadline: deadline
};
// Sign the meta-transaction
const signature = await userSigner._signTypedData(
this.domain,
this.types,
metaTxData
);
// Submit to relayer
const response = await this.submitToRelayer({
...metaTxData,
signature
});
return {
success: true,
transactionHash: response.transactionHash,
relayerUsed: response.relayerAddress
};
} catch (error) {
throw new Error(`Gasless transaction failed: ${error.message}`);
}
}
async batchGaslessTransactions(userSigner, transactions, options = {}) {
try {
const userAddress = await userSigner.getAddress();
let nonce = await this.contract.getNonce(userAddress);
const deadline = options.deadline || Math.floor(Date.now() / 1000) + 3600;
// Sign each transaction
const signedTransactions = [];
for (const tx of transactions) {
const metaTxData = {
user: userAddress,
target: tx.target,
functionSignature: tx.functionSignature,
nonce: nonce++,
deadline: deadline
};
const signature = await userSigner._signTypedData(
this.domain,
this.types,
metaTxData
);
signedTransactions.push({
...metaTxData,
signature
});
}
// Submit batch to relayer
const response = await this.submitBatchToRelayer(signedTransactions);
return {
success: true,
transactionHash: response.transactionHash,
batchResults: response.results
};
} catch (error) {
throw new Error(`Batch gasless transaction failed: ${error.message}`);
}
}
async addGasCredits(userSigner, amount, beneficiary = null) {
try {
const userAddress = await userSigner.getAddress();
const target = beneficiary || userAddress;
const tx = await this.contract.connect(userSigner).addGasCredits(target, amount, {
value: amount
});
await tx.wait();
return {
success: true,
transactionHash: tx.hash,
creditsAdded: ethers.formatEther(amount)
};
} catch (error) {
throw new Error(`Failed to add gas credits: ${error.message}`);
}
}
async getGasCreditsBalance(userAddress) {
try {
const balance = await this.contract.getGasCredits(userAddress);
return {
balance: ethers.formatEther(balance),
balanceWei: balance.toString()
};
} catch (error) {
throw new Error(`Failed to get gas credits: ${error.message}`);
}
}
async estimateGaslessTransactionCost(target, functionSignature) {
try {
const gasEstimate = await this.contract.estimateMetaTxGas(
ethers.ZeroAddress, // Placeholder address
target,
functionSignature
);
// Get current gas price from relayer
const gasPriceResponse = await fetch(`${this.relayerEndpoint}/gas-price`);
const { gasPrice } = await gasPriceResponse.json();
const totalCost = gasEstimate * BigInt(gasPrice);
return {
gasEstimate: gasEstimate.toString(),
gasPrice: gasPrice,
totalCost: ethers.formatEther(totalCost),
totalCostWei: totalCost.toString()
};
} catch (error) {
throw new Error(`Failed to estimate cost: ${error.message}`);
}
}
async submitToRelayer(metaTxData) {
const response = await fetch(`${this.relayerEndpoint}/submit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(metaTxData)
});
if (!response.ok) {
throw new Error(`Relayer error: ${response.statusText}`);
}
return await response.json();
}
async submitBatchToRelayer(transactions) {
const response = await fetch(`${this.relayerEndpoint}/submit-batch`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ transactions })
});
if (!response.ok) {
throw new Error(`Batch relayer error: ${response.statusText}`);
}
return await response.json();
}
async getRelayerStatus() {
try {
const response = await fetch(`${this.relayerEndpoint}/status`);
return await response.json();
} catch (error) {
throw new Error(`Failed to get relayer status: ${error.message}`);
}
}
async waitForTransaction(transactionHash, timeout = 60000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const response = await fetch(`${this.relayerEndpoint}/transaction/${transactionHash}`);
const txData = await response.json();
if (txData.status === 'confirmed') {
return txData;
} else if (txData.status === 'failed') {
throw new Error(`Transaction failed: ${txData.error}`);
}
// Wait 2 seconds before checking again
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
if (Date.now() - startTime >= timeout) {
throw new Error('Transaction timeout');
}
}
}
throw new Error('Transaction timeout');
}
}