Events & Errors
This reference documents all events and custom errors used throughout the IXFI Protocol smart contracts, providing developers with comprehensive information for monitoring, debugging, and integration.
Core Protocol Events
IXFI Gateway Events
ContractCall
Emitted when a cross-chain contract call is initiated.
event ContractCall(
address indexed sender,
string destinationChain,
string destinationContractAddress,
bytes32 indexed payloadHash,
bytes payload
);
Parameters:
sender
: Address that initiated the calldestinationChain
: Target blockchain namedestinationContractAddress
: Target contract addresspayloadHash
: Hash of the payload for verificationpayload
: Encoded function call data
Usage Example:
gateway.on("ContractCall", (sender, destinationChain, contractAddress, payloadHash, payload, event) => {
console.log(`Cross-chain call from ${sender} to ${destinationChain}`);
console.log(`Target contract: ${contractAddress}`);
console.log(`Payload hash: ${payloadHash}`);
console.log(`Transaction: ${event.transactionHash}`);
});
ContractCallWithToken
Emitted when a cross-chain contract call with token transfer is initiated.
event ContractCallWithToken(
address indexed sender,
string destinationChain,
string destinationContractAddress,
bytes32 indexed payloadHash,
bytes payload,
string symbol,
uint256 amount
);
Parameters:
sender
: Address that initiated the calldestinationChain
: Target blockchain namedestinationContractAddress
: Target contract addresspayloadHash
: Hash of the payloadpayload
: Encoded function call datasymbol
: Token symbol being transferredamount
: Amount of tokens being transferred
TokenSent
Emitted when tokens are sent cross-chain.
event TokenSent(
address indexed sender,
string destinationChain,
string destinationAddress,
string symbol,
uint256 amount
);
Executed
Emitted when a cross-chain command is executed on the destination chain.
event Executed(
bytes32 indexed commandId,
string sourceChain,
string sourceAddress,
bool success,
bytes returnData
);
Parameters:
commandId
: Unique identifier for the commandsourceChain
: Originating blockchain namesourceAddress
: Original caller's addresssuccess
: Whether execution was successfulreturnData
: Return data from the executed function
IXFI Token Events
TokenMinted
Emitted when IXFI tokens are minted (cross-chain transfer in).
event TokenMinted(
address indexed to,
uint256 amount,
string indexed sourceChain,
bytes32 indexed commandId
);
TokenBurned
Emitted when IXFI tokens are burned (cross-chain transfer out).
event TokenBurned(
address indexed from,
uint256 amount,
string indexed destinationChain,
bytes32 indexed transferId
);
RelayerAdded
Emitted when a new relayer is added to the network.
event RelayerAdded(address indexed relayer);
RelayerRemoved
Emitted when a relayer is removed from the network.
event RelayerRemoved(address indexed relayer);
ChainAdded
Emitted when a new blockchain is added to the protocol.
event ChainAdded(string indexed chainName, uint256 chainId);
ChainRemoved
Emitted when a blockchain is removed from the protocol.
event ChainRemoved(string indexed chainName);
DEX Aggregation Events
TokenSwap
Emitted when a token swap is executed through the aggregator.
event TokenSwap(
address indexed user,
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut,
address dexUsed
);
RouteFound
Emitted when an optimal route is found for a swap.
event RouteFound(
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 expectedOut,
address[] dexPath,
uint256 priceImpact
);
LiquidityAdded
Emitted when liquidity is added to a DEX through the aggregator.
event LiquidityAdded(
address indexed user,
address indexed tokenA,
address indexed tokenB,
uint256 amountA,
uint256 amountB,
address dexUsed
);
Meta Transaction Events
MetaTransactionExecuted
Emitted when a meta-transaction is executed.
event MetaTransactionExecuted(
address indexed user,
address indexed relayer,
address indexed target,
bool success,
bytes returnData
);
GaslessCreditsAdded
Emitted when gasless credits are added for a user.
event GaslessCreditsAdded(
address indexed user,
uint256 amount,
address indexed sponsor
);
GaslessCreditsUsed
Emitted when gasless credits are consumed.
event GaslessCreditsUsed(
address indexed user,
uint256 amount,
bytes32 indexed transactionId
);
Protocol Errors
Core Protocol Errors
InvalidChain
Thrown when an unsupported or invalid chain is specified.
error InvalidChain(string chainName);
Common Causes:
Chain not registered in the protocol
Typo in chain name
Chain temporarily disabled
Resolution:
require(gateway.isValidChain(chainName), "Chain not supported");
InvalidRelayer
Thrown when an unauthorized relayer attempts to execute commands.
error InvalidRelayer(address relayer);
Common Causes:
Relayer not registered
Relayer stake below minimum
Relayer temporarily suspended
CommandAlreadyExecuted
Thrown when attempting to execute a command that has already been processed.
error CommandAlreadyExecuted(bytes32 commandId);
Common Causes:
Replay attack attempt
Duplicate transaction submission
Race condition in relayer network
InsufficientBalance
Thrown when a user doesn't have enough tokens for an operation.
error InsufficientBalance(uint256 requested, uint256 available);
InvalidSignature
Thrown when signature verification fails.
error InvalidSignature();
Common Causes:
Wrong private key used for signing
Signature malformed or corrupted
Domain separator mismatch
PayloadExecutionFailed
Thrown when cross-chain payload execution fails.
error PayloadExecutionFailed(string reason);
DEX Aggregation Errors
InsufficientOutputAmount
Thrown when swap output is below minimum acceptable amount.
error InsufficientOutputAmount(uint256 expected, uint256 actual);
NoLiquidityAvailable
Thrown when no liquidity is available for a trading pair.
error NoLiquidityAvailable(address tokenA, address tokenB);
ExcessiveSlippage
Thrown when price slippage exceeds acceptable limits.
error ExcessiveSlippage(uint256 slippage, uint256 maxSlippage);
InvalidPath
Thrown when the provided swap path is invalid.
error InvalidPath(address[] path);
DEXNotSupported
Thrown when trying to use an unsupported DEX.
error DEXNotSupported(address dex);
Gas Management Errors
InsufficientGasPayment
Thrown when gas payment is insufficient for cross-chain operation.
error InsufficientGasPayment(uint256 required, uint256 provided);
GasLimitExceeded
Thrown when gas limit exceeds maximum allowed.
error GasLimitExceeded(uint256 gasLimit, uint256 maxGasLimit);
GasPriceOutOfRange
Thrown when gas price is outside acceptable range.
error GasPriceOutOfRange(uint256 gasPrice, uint256 minPrice, uint256 maxPrice);
Meta Transaction Errors
InvalidNonce
Thrown when meta-transaction nonce is invalid.
error InvalidNonce(uint256 expected, uint256 provided);
TransactionExpired
Thrown when meta-transaction deadline has passed.
error TransactionExpired(uint256 deadline, uint256 currentTime);
InsufficientGasCredits
Thrown when user doesn't have enough gas credits for gasless transaction.
error InsufficientGasCredits(uint256 required, uint256 available);
RelayerNotAuthorized
Thrown when relayer is not authorized for meta-transactions.
error RelayerNotAuthorized(address relayer);
Event Monitoring Patterns
Basic Event Listening
// Listen to all gateway events
const gateway = new ethers.Contract(gatewayAddress, gatewayABI, provider);
// Cross-chain call monitoring
gateway.on("ContractCall", (sender, destinationChain, contractAddress, payloadHash, payload, event) => {
console.log(`New cross-chain call:`, {
sender,
destinationChain,
contractAddress,
payloadHash,
transactionHash: event.transactionHash,
blockNumber: event.blockNumber
});
});
// Token transfer monitoring
gateway.on("TokenSent", (sender, destinationChain, destinationAddress, symbol, amount, event) => {
console.log(`Token transfer:`, {
from: sender,
to: destinationAddress,
chain: destinationChain,
token: symbol,
amount: ethers.formatEther(amount)
});
});
Advanced Event Filtering
// Filter events by user
const userFilter = gateway.filters.ContractCall(userAddress);
gateway.on(userFilter, (sender, destinationChain, contractAddress, payloadHash, payload, event) => {
// Handle user-specific events
});
// Filter events by destination chain
const chainFilter = gateway.filters.ContractCall(null, "ethereum");
gateway.on(chainFilter, (sender, destinationChain, contractAddress, payloadHash, payload, event) => {
// Handle Ethereum-specific events
});
// Get historical events
const fromBlock = await provider.getBlockNumber() - 1000;
const events = await gateway.queryFilter(gateway.filters.ContractCall(), fromBlock);
events.forEach(event => {
console.log(`Historical event:`, event.args);
});
Event Aggregation and Analytics
class EventAnalytics {
constructor(gatewayContract) {
this.gateway = gatewayContract;
this.metrics = {
totalCalls: 0,
chainDistribution: {},
tokenTransfers: {},
hourlyVolume: {}
};
}
startMonitoring() {
// Monitor contract calls
this.gateway.on("ContractCall", (sender, destinationChain, contractAddress, payloadHash, payload, event) => {
this.metrics.totalCalls++;
if (!this.metrics.chainDistribution[destinationChain]) {
this.metrics.chainDistribution[destinationChain] = 0;
}
this.metrics.chainDistribution[destinationChain]++;
this.updateHourlyVolume();
});
// Monitor token transfers
this.gateway.on("TokenSent", (sender, destinationChain, destinationAddress, symbol, amount, event) => {
if (!this.metrics.tokenTransfers[symbol]) {
this.metrics.tokenTransfers[symbol] = {
totalAmount: BigInt(0),
transferCount: 0
};
}
this.metrics.tokenTransfers[symbol].totalAmount += amount;
this.metrics.tokenTransfers[symbol].transferCount++;
});
}
updateHourlyVolume() {
const currentHour = Math.floor(Date.now() / (1000 * 60 * 60));
if (!this.metrics.hourlyVolume[currentHour]) {
this.metrics.hourlyVolume[currentHour] = 0;
}
this.metrics.hourlyVolume[currentHour]++;
}
getMetrics() {
return {
...this.metrics,
avgCallsPerHour: this.calculateAverageCallsPerHour(),
topChains: this.getTopChains(),
topTokens: this.getTopTokens()
};
}
calculateAverageCallsPerHour() {
const hours = Object.keys(this.metrics.hourlyVolume);
if (hours.length === 0) return 0;
const total = Object.values(this.metrics.hourlyVolume).reduce((sum, count) => sum + count, 0);
return total / hours.length;
}
getTopChains() {
return Object.entries(this.metrics.chainDistribution)
.sort(([,a], [,b]) => b - a)
.slice(0, 5);
}
getTopTokens() {
return Object.entries(this.metrics.tokenTransfers)
.sort(([,a], [,b]) => Number(b.totalAmount - a.totalAmount))
.slice(0, 5);
}
}
Error Handling Patterns
Comprehensive Error Handling
class ProtocolErrorHandler {
constructor() {
this.errorCounts = {};
this.retryAttempts = {};
}
async handleTransaction(txFunction, retryCount = 3) {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
const result = await txFunction();
// Reset retry count on success
this.retryAttempts[txFunction.name] = 0;
return result;
} catch (error) {
this.logError(error, attempt);
if (this.isRetryableError(error) && attempt < retryCount) {
const delay = this.calculateBackoffDelay(attempt);
await this.sleep(delay);
continue;
}
throw this.enhanceError(error);
}
}
}
isRetryableError(error) {
const retryablePatterns = [
/network timeout/i,
/insufficient funds/i,
/gas price too low/i,
/replacement transaction underpriced/i,
/nonce too low/i
];
return retryablePatterns.some(pattern =>
pattern.test(error.message)
);
}
enhanceError(error) {
// Parse custom errors
if (error.data) {
const customError = this.parseCustomError(error.data);
if (customError) {
error.customError = customError;
error.message = `${customError.name}: ${customError.message}`;
}
}
// Add context
error.timestamp = new Date().toISOString();
error.errorCount = this.incrementErrorCount(error.message);
return error;
}
parseCustomError(errorData) {
const errorSignatures = {
'0x1234...': { name: 'InvalidChain', decoder: (data) => `Chain ${data} not supported` },
'0x5678...': { name: 'InsufficientBalance', decoder: (data) => `Insufficient balance` },
// Add more error signatures
};
const signature = errorData.slice(0, 10);
if (errorSignatures[signature]) {
const errorInfo = errorSignatures[signature];
return {
name: errorInfo.name,
message: errorInfo.decoder(errorData)
};
}
return null;
}
calculateBackoffDelay(attempt) {
return Math.min(1000 * Math.pow(2, attempt - 1), 30000); // Max 30 seconds
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
logError(error, attempt) {
console.error(`Transaction attempt ${attempt} failed:`, {
message: error.message,
code: error.code,
data: error.data
});
}
incrementErrorCount(errorMessage) {
if (!this.errorCounts[errorMessage]) {
this.errorCounts[errorMessage] = 0;
}
return ++this.errorCounts[errorMessage];
}
}
React Error Boundary
import React from 'react';
class ProtocolErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Protocol error caught:', error, errorInfo);
// Report to error tracking service
this.reportError(error, errorInfo);
}
reportError(error, errorInfo) {
// Parse protocol-specific errors
const protocolError = this.parseProtocolError(error);
// Send to monitoring service
if (protocolError) {
console.log('Protocol error detected:', protocolError);
}
}
parseProtocolError(error) {
if (error.customError) {
return {
type: 'protocol',
name: error.customError.name,
message: error.customError.message,
severity: this.getErrorSeverity(error.customError.name)
};
}
return null;
}
getErrorSeverity(errorName) {
const severityMap = {
'InvalidChain': 'high',
'InsufficientBalance': 'medium',
'InvalidSignature': 'high',
'CommandAlreadyExecuted': 'low'
};
return severityMap[errorName] || 'medium';
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<details>
<summary>Error details</summary>
<pre>{this.state.error?.message}</pre>
{this.state.error?.customError && (
<div>
<strong>Protocol Error:</strong> {this.state.error.customError.name}
<br />
<strong>Details:</strong> {this.state.error.customError.message}
</div>
)}
</details>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
Best Practices
Event Monitoring
Use Filters: Filter events by user or specific parameters to reduce noise
Handle Reconnections: Implement robust connection handling for WebSocket providers
Store Event History: Cache important events for offline access
Rate Limiting: Implement rate limiting for high-frequency events
Error Handling
Categorize Errors: Distinguish between user errors, network issues, and protocol errors
Implement Retries: Use exponential backoff for transient errors
User Feedback: Provide clear, actionable error messages to users
Monitoring: Track error patterns to identify systemic issues
Performance
Batch Event Queries: Use
queryFilter
for historical data instead of individual callsOptimize Filters: Use indexed parameters in event filters
Connection Pooling: Manage WebSocket connections efficiently
Cleanup: Remove event listeners when components unmount
Resources
Last updated