Gas Management
Efficient gas management is crucial for cross-chain operations in the IXFI Protocol. This guide covers gas estimation, payment methods, optimization strategies, and cost management across multiple blockchains.
Overview
Cross-chain transactions require gas on both source and destination chains. The IXFI Protocol provides several mechanisms to handle gas payments, estimate costs, and optimize gas usage for cross-chain operations.
Gas Architecture
Gas Payment Models
1. Prepaid Gas Model
Users pay gas costs upfront on the source chain:
contract PrepaidGasManager {
mapping(string => uint256) public gasRates; // Gas price per destination chain
mapping(string => uint256) public gasMultipliers; // Chain-specific multipliers
event GasPaid(
address indexed user,
string indexed destinationChain,
uint256 gasAmount,
uint256 nativeAmount
);
function payGasForContractCall(
string memory destinationChain,
uint256 gasLimit
) external payable returns (bytes32 gasPaymentId) {
uint256 requiredPayment = calculateGasCost(destinationChain, gasLimit);
require(msg.value >= requiredPayment, "Insufficient gas payment");
gasPaymentId = keccak256(abi.encode(
msg.sender,
destinationChain,
gasLimit,
block.timestamp
));
// Store gas payment for relayer reimbursement
gasPayments[gasPaymentId] = GasPayment({
payer: msg.sender,
destinationChain: destinationChain,
gasLimit: gasLimit,
amountPaid: msg.value,
used: false
});
emit GasPaid(msg.sender, destinationChain, gasLimit, msg.value);
return gasPaymentId;
}
function calculateGasCost(
string memory destinationChain,
uint256 gasLimit
) public view returns (uint256) {
uint256 baseGasPrice = gasRates[destinationChain];
uint256 multiplier = gasMultipliers[destinationChain];
// Add 20% buffer for gas price fluctuations
return (gasLimit * baseGasPrice * multiplier * 120) / (100 * 100);
}
}
2. Token-Based Gas Payment
Pay gas fees using IXFI or other tokens:
contract TokenGasPayment {
IERC20 public ixfiToken;
mapping(string => uint256) public tokenGasRates; // IXFI per gas unit
function payGasWithTokens(
string memory destinationChain,
uint256 gasLimit,
uint256 tokenAmount
) external returns (bytes32 gasPaymentId) {
uint256 requiredTokens = calculateTokenGasCost(destinationChain, gasLimit);
require(tokenAmount >= requiredTokens, "Insufficient token amount");
// Transfer tokens from user
ixfiToken.transferFrom(msg.sender, address(this), tokenAmount);
gasPaymentId = _createGasPayment(
msg.sender,
destinationChain,
gasLimit,
tokenAmount
);
emit TokenGasPaid(msg.sender, destinationChain, gasLimit, tokenAmount);
return gasPaymentId;
}
function calculateTokenGasCost(
string memory destinationChain,
uint256 gasLimit
) public view returns (uint256) {
uint256 tokenRate = tokenGasRates[destinationChain];
uint256 baseCost = gasLimit * tokenRate;
// Add buffer and convert to token units
return (baseCost * 120) / 100;
}
}
3. Gasless Transactions
Enable gasless transactions through meta-transactions:
contract GaslessExecutor {
mapping(address => uint256) public nonces;
mapping(address => uint256) public gasCredits;
struct MetaTransaction {
address from;
string destinationChain;
address targetContract;
bytes payload;
uint256 gasLimit;
uint256 nonce;
bytes signature;
}
function executeGasless(MetaTransaction memory metaTx) external {
// Verify signature
require(_verifySignature(metaTx), "Invalid signature");
// Check gas credits
require(gasCredits[metaTx.from] >= metaTx.gasLimit, "Insufficient gas credits");
// Deduct gas credits
gasCredits[metaTx.from] -= metaTx.gasLimit;
nonces[metaTx.from]++;
// Execute cross-chain call
gateway.callContract(
metaTx.destinationChain,
Strings.toHexString(uint160(metaTx.targetContract), 20),
metaTx.payload
);
emit GaslessExecuted(metaTx.from, metaTx.destinationChain, metaTx.gasLimit);
}
function addGasCredits(address user, uint256 amount) external {
ixfiToken.transferFrom(msg.sender, address(this), amount);
gasCredits[user] += amount;
emit GasCreditsAdded(user, amount);
}
}
Gas Estimation
Dynamic Gas Estimation
contract GasEstimator {
struct ChainGasInfo {
uint256 baseGasPrice;
uint256 priorityFee;
uint256 blockGasLimit;
uint256 avgBlockTime;
uint256 congestionMultiplier;
}
mapping(string => ChainGasInfo) public chainGasInfo;
mapping(bytes4 => uint256) public functionGasCosts;
function estimateGasForCall(
string memory destinationChain,
bytes memory payload
) external view returns (
uint256 estimatedGas,
uint256 estimatedCost,
uint256 confidence
) {
// Base gas for cross-chain execution
uint256 baseGas = 50000;
// Function-specific gas estimation
bytes4 selector = bytes4(payload);
uint256 functionGas = functionGasCosts[selector];
if (functionGas == 0) {
// Estimate based on payload size
functionGas = _estimateGasFromPayload(payload);
}
// Chain-specific adjustments
ChainGasInfo memory gasInfo = chainGasInfo[destinationChain];
uint256 chainMultiplier = gasInfo.congestionMultiplier;
estimatedGas = (baseGas + functionGas) * chainMultiplier / 100;
estimatedCost = estimatedGas * (gasInfo.baseGasPrice + gasInfo.priorityFee);
// Confidence based on data freshness and volatility
confidence = _calculateConfidence(destinationChain);
return (estimatedGas, estimatedCost, confidence);
}
function _estimateGasFromPayload(bytes memory payload) internal pure returns (uint256) {
// Base gas: 21000 + 4 gas per zero byte + 16 gas per non-zero byte
uint256 dataGas = 0;
for (uint256 i = 0; i < payload.length; i++) {
if (payload[i] == 0) {
dataGas += 4;
} else {
dataGas += 16;
}
}
return 21000 + dataGas;
}
function updateGasInfo(
string memory chainName,
uint256 baseGasPrice,
uint256 priorityFee,
uint256 congestionMultiplier
) external onlyOracle {
chainGasInfo[chainName] = ChainGasInfo({
baseGasPrice: baseGasPrice,
priorityFee: priorityFee,
blockGasLimit: chainGasInfo[chainName].blockGasLimit,
avgBlockTime: chainGasInfo[chainName].avgBlockTime,
congestionMultiplier: congestionMultiplier
});
emit GasInfoUpdated(chainName, baseGasPrice, priorityFee);
}
}
Real-Time Gas Oracle
class GasOracle {
constructor() {
this.providers = new Map();
this.gasData = new Map();
this.updateInterval = 30000; // 30 seconds
}
async initializeChains(chainConfigs) {
for (const [chainName, config] of Object.entries(chainConfigs)) {
this.providers.set(chainName, new ethers.JsonRpcProvider(config.rpc));
await this.updateGasData(chainName);
}
// Start periodic updates
setInterval(() => this.updateAllChains(), this.updateInterval);
}
async updateGasData(chainName) {
const provider = this.providers.get(chainName);
try {
const feeData = await provider.getFeeData();
const block = await provider.getBlock('latest');
const gasData = {
gasPrice: feeData.gasPrice,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
blockGasLimit: block.gasLimit,
blockGasUsed: block.gasUsed,
utilization: Number(block.gasUsed * 100n / block.gasLimit),
timestamp: Date.now()
};
this.gasData.set(chainName, gasData);
// Update congestion multiplier based on utilization
const congestionMultiplier = this.calculateCongestionMultiplier(gasData.utilization);
await this.updateChainMultiplier(chainName, congestionMultiplier);
} catch (error) {
console.error(`Failed to update gas data for ${chainName}:`, error);
}
}
calculateCongestionMultiplier(utilization) {
if (utilization < 50) return 100; // No congestion
if (utilization < 70) return 120; // Light congestion
if (utilization < 85) return 150; // Moderate congestion
if (utilization < 95) return 200; // High congestion
return 300; // Extreme congestion
}
async estimateGasForOperation(chainName, operationType, payload) {
const gasData = this.gasData.get(chainName);
if (!gasData) throw new Error(`No gas data for chain: ${chainName}`);
const baseGas = this.getBaseGasForOperation(operationType);
const payloadGas = this.estimatePayloadGas(payload);
const totalGas = baseGas + payloadGas;
const congestionMultiplier = this.calculateCongestionMultiplier(gasData.utilization);
const adjustedGas = Math.ceil(totalGas * congestionMultiplier / 100);
return {
estimatedGas: adjustedGas,
gasPrice: gasData.gasPrice,
maxFeePerGas: gasData.maxFeePerGas,
estimatedCost: BigInt(adjustedGas) * gasData.gasPrice,
confidence: this.calculateConfidence(gasData),
congestionLevel: this.getCongestionLevel(gasData.utilization)
};
}
getBaseGasForOperation(operationType) {
const gasLimits = {
'token_transfer': 100000,
'contract_call': 150000,
'contract_call_with_token': 200000,
'complex_execution': 300000
};
return gasLimits[operationType] || 150000;
}
estimatePayloadGas(payload) {
if (!payload) return 0;
const bytes = ethers.getBytes(payload);
let gas = 0;
for (let i = 0; i < bytes.length; i++) {
gas += bytes[i] === 0 ? 4 : 16;
}
return gas;
}
}
Gas Optimization Strategies
1. Payload Optimization
contract OptimizedPayloads {
// Instead of this (expensive)
function expensiveCall(
uint256 a,
uint256 b,
uint256 c,
string memory text,
address[] memory addresses
) external {
// Function implementation
}
// Use this (optimized)
function optimizedCall(bytes memory packedData) external {
(
uint256 a,
uint256 b,
uint256 c,
string memory text,
address[] memory addresses
) = abi.decode(packedData, (uint256, uint256, uint256, string, address[]));
// Function implementation
}
// Even better: use packed encoding for similar types
function superOptimizedCall(
uint256 packedInts, // Pack multiple uint256s into one
bytes memory packedData
) external {
uint256 a = packedInts >> 128;
uint256 b = (packedInts >> 64) & 0xFFFFFFFFFFFFFFFF;
uint256 c = packedInts & 0xFFFFFFFFFFFFFFFF;
// Decode other data as needed
}
}
2. Batch Operations
contract BatchGasOptimizer {
struct BatchCall {
address target;
bytes data;
uint256 gasLimit;
}
function batchExecute(
string memory destinationChain,
BatchCall[] memory calls
) external {
require(calls.length <= 10, "Too many calls");
// Encode all calls into single payload
bytes memory batchPayload = abi.encodeWithSignature(
"executeBatch((address,bytes,uint256)[])",
calls
);
gateway.callContract(
destinationChain,
Strings.toHexString(uint160(address(this)), 20),
batchPayload
);
}
function executeBatch(BatchCall[] memory calls) external onlyGateway {
for (uint256 i = 0; i < calls.length; i++) {
(bool success,) = calls[i].target.call{gas: calls[i].gasLimit}(calls[i].data);
if (!success) {
emit BatchCallFailed(i, calls[i].target);
}
}
}
}
3. Smart Gas Scheduling
contract GasScheduler {
struct ScheduledExecution {
string destinationChain;
address targetContract;
bytes payload;
uint256 executeAfter;
uint256 maxGasPrice;
address payer;
}
mapping(bytes32 => ScheduledExecution) public scheduledExecutions;
function scheduleExecution(
string memory destinationChain,
address targetContract,
bytes memory payload,
uint256 delaySeconds,
uint256 maxGasPrice
) external payable returns (bytes32 executionId) {
executionId = keccak256(abi.encode(
msg.sender,
destinationChain,
targetContract,
payload,
block.timestamp
));
scheduledExecutions[executionId] = ScheduledExecution({
destinationChain: destinationChain,
targetContract: targetContract,
payload: payload,
executeAfter: block.timestamp + delaySeconds,
maxGasPrice: maxGasPrice,
payer: msg.sender
});
// Lock gas payment
require(msg.value > 0, "Must pay for gas");
emit ExecutionScheduled(executionId, delaySeconds, maxGasPrice);
}
function executeScheduled(bytes32 executionId) external {
ScheduledExecution memory execution = scheduledExecutions[executionId];
require(execution.payer != address(0), "Execution not found");
require(block.timestamp >= execution.executeAfter, "Too early");
// Check current gas price
uint256 currentGasPrice = tx.gasprice;
require(currentGasPrice <= execution.maxGasPrice, "Gas price too high");
delete scheduledExecutions[executionId];
// Execute with optimal gas conditions
gateway.callContract(
execution.destinationChain,
Strings.toHexString(uint160(execution.targetContract), 20),
execution.payload
);
emit ScheduledExecutionCompleted(executionId, currentGasPrice);
}
}
Gas Monitoring and Analytics
Gas Usage Tracking
contract GasMonitor {
struct GasMetrics {
uint256 totalGasUsed;
uint256 totalTransactions;
uint256 averageGasUsed;
uint256 maxGasUsed;
uint256 minGasUsed;
mapping(string => uint256) chainGasUsed;
}
mapping(address => GasMetrics) public userGasMetrics;
GasMetrics public globalGasMetrics;
event GasUsageRecorded(
address indexed user,
string indexed chain,
uint256 gasUsed,
uint256 gasCost
);
function recordGasUsage(
address user,
string memory chain,
uint256 gasUsed,
uint256 gasCost
) external onlyGateway {
GasMetrics storage userMetrics = userGasMetrics[user];
// Update user metrics
userMetrics.totalGasUsed += gasUsed;
userMetrics.totalTransactions++;
userMetrics.chainGasUsed[chain] += gasUsed;
if (gasUsed > userMetrics.maxGasUsed || userMetrics.maxGasUsed == 0) {
userMetrics.maxGasUsed = gasUsed;
}
if (gasUsed < userMetrics.minGasUsed || userMetrics.minGasUsed == 0) {
userMetrics.minGasUsed = gasUsed;
}
userMetrics.averageGasUsed = userMetrics.totalGasUsed / userMetrics.totalTransactions;
// Update global metrics
globalGasMetrics.totalGasUsed += gasUsed;
globalGasMetrics.totalTransactions++;
globalGasMetrics.averageGasUsed = globalGasMetrics.totalGasUsed / globalGasMetrics.totalTransactions;
emit GasUsageRecorded(user, chain, gasUsed, gasCost);
}
function getGasEfficiencyScore(address user) external view returns (uint256) {
GasMetrics storage metrics = userGasMetrics[user];
if (metrics.totalTransactions == 0) return 0;
uint256 userAverage = metrics.averageGasUsed;
uint256 globalAverage = globalGasMetrics.averageGasUsed;
if (globalAverage == 0) return 100;
// Score: 100 = average, >100 = better than average, <100 = worse
return (globalAverage * 100) / userAverage;
}
}
Gas Alert System
class GasAlertSystem {
constructor(gasOracle) {
this.gasOracle = gasOracle;
this.alerts = new Map();
this.thresholds = {
highGasPrice: ethers.parseUnits('50', 'gwei'),
veryHighGasPrice: ethers.parseUnits('100', 'gwei'),
extremeGasPrice: ethers.parseUnits('200', 'gwei')
};
}
async checkGasAlerts() {
for (const [chainName, gasData] of this.gasOracle.gasData) {
const currentPrice = gasData.gasPrice;
if (currentPrice >= this.thresholds.extremeGasPrice) {
this.sendAlert({
level: 'CRITICAL',
chain: chainName,
message: `Extreme gas prices: ${ethers.formatUnits(currentPrice, 'gwei')} gwei`,
recommendation: 'Consider delaying non-urgent transactions'
});
} else if (currentPrice >= this.thresholds.veryHighGasPrice) {
this.sendAlert({
level: 'WARNING',
chain: chainName,
message: `Very high gas prices: ${ethers.formatUnits(currentPrice, 'gwei')} gwei`,
recommendation: 'Use gas scheduling for optimal costs'
});
} else if (currentPrice >= this.thresholds.highGasPrice) {
this.sendAlert({
level: 'INFO',
chain: chainName,
message: `High gas prices: ${ethers.formatUnits(currentPrice, 'gwei')} gwei`,
recommendation: 'Consider gas optimization strategies'
});
}
}
}
async suggestOptimalTiming(chainName, targetGasPrice) {
const gasData = this.gasOracle.gasData.get(chainName);
if (!gasData) return null;
const currentPrice = gasData.gasPrice;
if (currentPrice <= targetGasPrice) {
return {
execute: 'now',
reason: 'Current gas price is acceptable'
};
}
// Analyze historical patterns to suggest optimal timing
const historicalData = await this.getHistoricalGasData(chainName, 24); // 24 hours
const optimalHours = this.analyzeOptimalHours(historicalData);
return {
execute: 'later',
suggestedHours: optimalHours,
currentPrice: ethers.formatUnits(currentPrice, 'gwei'),
targetPrice: ethers.formatUnits(targetGasPrice, 'gwei'),
potentialSavings: this.calculateSavings(currentPrice, targetGasPrice)
};
}
}
Gas Refund Mechanisms
Automatic Gas Refunds
contract GasRefundManager {
mapping(bytes32 => uint256) public gasRefunds;
mapping(address => uint256) public userRefundBalance;
event GasRefundIssued(bytes32 indexed commandId, address user, uint256 amount);
event GasRefundClaimed(address indexed user, uint256 amount);
function processGasRefund(
bytes32 commandId,
address user,
uint256 gasUsed,
uint256 gasPrice,
uint256 paidAmount
) external onlyRelayer {
uint256 actualCost = gasUsed * gasPrice;
if (paidAmount > actualCost) {
uint256 refundAmount = paidAmount - actualCost;
userRefundBalance[user] += refundAmount;
gasRefunds[commandId] = refundAmount;
emit GasRefundIssued(commandId, user, refundAmount);
}
}
function claimRefund() external {
uint256 refundAmount = userRefundBalance[msg.sender];
require(refundAmount > 0, "No refund available");
userRefundBalance[msg.sender] = 0;
(bool success,) = msg.sender.call{value: refundAmount}("");
require(success, "Refund transfer failed");
emit GasRefundClaimed(msg.sender, refundAmount);
}
function claimRefundInTokens(address token) external {
uint256 refundAmount = userRefundBalance[msg.sender];
require(refundAmount > 0, "No refund available");
userRefundBalance[msg.sender] = 0;
// Convert to token amount based on current rates
uint256 tokenAmount = _convertToTokens(token, refundAmount);
IERC20(token).transfer(msg.sender, tokenAmount);
emit GasRefundClaimed(msg.sender, refundAmount);
}
}
Best Practices
1. Gas Estimation
Always add a buffer (10-20%) to gas estimates
Monitor gas price trends before execution
Use dynamic gas pricing for better efficiency
2. Payment Strategy
Choose payment method based on cost efficiency
Consider token-based payments during high ETH gas prices
Implement gas scheduling for non-urgent transactions
3. Optimization Techniques
Batch multiple operations when possible
Optimize payload encoding
Use compression for large data
4. Monitoring
Track gas usage patterns
Set up alerts for high gas prices
Analyze efficiency metrics regularly
5. User Experience
Provide clear gas cost estimates
Offer multiple payment options
Implement gasless options for small transactions
Resources
Last updated