Frontend Integration
This comprehensive guide covers integrating IXFI Protocol into frontend applications, including React, Vue, and vanilla JavaScript implementations.
Overview
IXFI Protocol offers multiple frontend integration approaches:
JavaScript SDK: Easy-to-use high-level APIs
React Hooks: Pre-built hooks for React applications
Direct Contract Interaction: Low-level ethers.js integration
REST API: Server-side integration options
Installation & Setup
NPM Package Installation
# Core SDK
npm install @ixfi/sdk ethers
# React hooks (for React apps)
npm install @ixfi/react-hooks
# Vue composables (for Vue apps)
npm install @ixfi/vue-composables
# Additional utilities
npm install @ixfi/utils @ixfi/types
CDN Integration
For vanilla JavaScript or quick prototyping:
<!-- Core IXFI SDK -->
<script src="https://cdn.ixfi.com/sdk/latest/ixfi-sdk.min.js"></script>
<!-- Ethers.js (required dependency) -->
<script src="https://cdn.ethers.io/lib/ethers-5.7.2.umd.min.js"></script>
<script>
// IXFI SDK is now available globally
const { IXFIGateway, CrossChainAggregator } = IXFI;
</script>
JavaScript SDK Integration
Basic Setup
import { IXFIGateway, CrossChainAggregator, MetaTxGateway } from '@ixfi/sdk';
import { ethers } from 'ethers';
// Initialize provider (MetaMask, WalletConnect, etc.)
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
// Initialize IXFI components
const config = {
provider,
signer,
chainId: 1, // Ethereum mainnet
relayerEndpoint: 'https://relayer.ixfi.com',
gasOracleEndpoint: 'https://gas.ixfi.com'
};
const gateway = new IXFIGateway(config);
const aggregator = new CrossChainAggregator(config);
const metaTxGateway = new MetaTxGateway(config);
Wallet Connection
class WalletManager {
constructor() {
this.provider = null;
this.signer = null;
this.account = null;
this.chainId = null;
}
async connectWallet(walletType = 'metamask') {
try {
switch (walletType) {
case 'metamask':
await this.connectMetaMask();
break;
case 'walletconnect':
await this.connectWalletConnect();
break;
case 'coinbase':
await this.connectCoinbaseWallet();
break;
default:
throw new Error('Unsupported wallet type');
}
await this.setupEventListeners();
return {
account: this.account,
chainId: this.chainId,
provider: this.provider
};
} catch (error) {
console.error('Wallet connection failed:', error);
throw error;
}
}
async connectMetaMask() {
if (!window.ethereum) {
throw new Error('MetaMask not installed');
}
// Request account access
await window.ethereum.request({ method: 'eth_requestAccounts' });
this.provider = new ethers.providers.Web3Provider(window.ethereum);
this.signer = this.provider.getSigner();
this.account = await this.signer.getAddress();
const network = await this.provider.getNetwork();
this.chainId = network.chainId;
}
async connectWalletConnect() {
const WalletConnectProvider = (await import('@walletconnect/ethereum-provider')).default;
const walletConnectProvider = await WalletConnectProvider.init({
projectId: 'your-walletconnect-project-id',
chains: [1, 137, 56, 43114, 42161, 10], // Supported chains
showQrModal: true
});
await walletConnectProvider.enable();
this.provider = new ethers.providers.Web3Provider(walletConnectProvider);
this.signer = this.provider.getSigner();
this.account = await this.signer.getAddress();
const network = await this.provider.getNetwork();
this.chainId = network.chainId;
}
async connectCoinbaseWallet() {
const CoinbaseWalletSDK = (await import('@coinbase/wallet-sdk')).default;
const coinbaseWallet = new CoinbaseWalletSDK({
appName: 'IXFI DApp',
appLogoUrl: 'https://ixfi.com/logo.png',
darkMode: false
});
const ethereum = coinbaseWallet.makeWeb3Provider('https://mainnet.infura.io/v3/your-infura-key', 1);
await ethereum.request({ method: 'eth_requestAccounts' });
this.provider = new ethers.providers.Web3Provider(ethereum);
this.signer = this.provider.getSigner();
this.account = await this.signer.getAddress();
const network = await this.provider.getNetwork();
this.chainId = network.chainId;
}
async setupEventListeners() {
if (window.ethereum) {
// Account changed
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
this.disconnect();
} else {
this.account = accounts[0];
this.onAccountChanged(accounts[0]);
}
});
// Chain changed
window.ethereum.on('chainChanged', (chainId) => {
this.chainId = parseInt(chainId, 16);
this.onChainChanged(this.chainId);
// Reload page for chain changes
window.location.reload();
});
// Disconnect
window.ethereum.on('disconnect', () => {
this.disconnect();
});
}
}
disconnect() {
this.provider = null;
this.signer = null;
this.account = null;
this.chainId = null;
this.onDisconnect();
}
// Event handlers (override in implementation)
onAccountChanged(account) {
console.log('Account changed:', account);
}
onChainChanged(chainId) {
console.log('Chain changed:', chainId);
}
onDisconnect() {
console.log('Wallet disconnected');
}
}
Token Swapping Interface
class SwapInterface {
constructor(aggregator, walletManager) {
this.aggregator = aggregator;
this.walletManager = walletManager;
this.quotes = [];
this.selectedQuote = null;
}
async getSwapQuotes(tokenIn, tokenOut, amountIn) {
try {
// Show loading state
this.onLoadingStart('Getting quotes...');
// Get quotes from all DEXes
const quotes = await this.aggregator.getAllQuotes(tokenIn, tokenOut, amountIn);
// Filter and sort quotes
this.quotes = quotes
.filter(quote => quote.success && quote.amountOut > 0)
.sort((a, b) => parseFloat(b.amountOut) - parseFloat(a.amountOut))
.map(quote => ({
...quote,
dexName: this.getDEXName(quote.routerType),
amountOutFormatted: ethers.utils.formatUnits(quote.amountOut, 18),
priceImpactFormatted: `${(quote.priceImpact / 100).toFixed(2)}%`,
gasEstimateETH: ethers.utils.formatEther(quote.gasEstimate || 0)
}));
this.selectedQuote = this.quotes[0]; // Default to best quote
this.onQuotesUpdated(this.quotes);
return this.quotes;
} catch (error) {
this.onError('Failed to get quotes', error);
throw error;
} finally {
this.onLoadingEnd();
}
}
async executeSwap(slippageTolerance = 0.5) {
if (!this.selectedQuote) {
throw new Error('No quote selected');
}
try {
this.onLoadingStart('Executing swap...');
const { tokenIn, tokenOut, amountIn } = this.selectedQuote;
// Calculate minimum amount out with slippage
const minAmountOut = ethers.BigNumber.from(this.selectedQuote.amountOut)
.mul(Math.floor((100 - slippageTolerance) * 100))
.div(10000);
// Check and approve token if needed
await this.ensureTokenApproval(tokenIn, amountIn);
// Execute swap
const tx = await this.aggregator.executeSwap({
tokenIn,
tokenOut,
amountIn,
minAmountOut,
routerType: this.selectedQuote.routerType,
to: this.walletManager.account
});
this.onTransactionSubmitted(tx.hash);
// Wait for confirmation
const receipt = await tx.wait();
this.onTransactionConfirmed(receipt);
return receipt;
} catch (error) {
this.onError('Swap failed', error);
throw error;
} finally {
this.onLoadingEnd();
}
}
async ensureTokenApproval(tokenAddress, amount) {
if (tokenAddress === ethers.constants.AddressZero) {
return; // No approval needed for ETH
}
const tokenContract = new ethers.Contract(
tokenAddress,
['function allowance(address,address) view returns (uint256)', 'function approve(address,uint256) returns (bool)'],
this.walletManager.signer
);
const currentAllowance = await tokenContract.allowance(
this.walletManager.account,
this.aggregator.address
);
if (currentAllowance.lt(amount)) {
const approveTx = await tokenContract.approve(this.aggregator.address, amount);
await approveTx.wait();
this.onApprovalConfirmed(tokenAddress, amount);
}
}
getDEXName(routerType) {
const dexNames = {
0: 'Uniswap V2',
1: 'SushiSwap V2',
2: 'PancakeSwap V2',
3: 'QuickSwap',
10: 'Uniswap V3',
11: 'SushiSwap V3',
12: 'PancakeSwap V3',
20: 'Velodrome',
21: 'Aerodrome',
30: 'Curve',
35: 'Balancer V2',
36: '1inch'
};
return dexNames[routerType] || `Router ${routerType}`;
}
// Event handlers (override in implementation)
onLoadingStart(message) {
console.log('Loading:', message);
}
onLoadingEnd() {
console.log('Loading complete');
}
onQuotesUpdated(quotes) {
console.log('Quotes updated:', quotes.length);
}
onTransactionSubmitted(hash) {
console.log('Transaction submitted:', hash);
}
onTransactionConfirmed(receipt) {
console.log('Transaction confirmed:', receipt.transactionHash);
}
onApprovalConfirmed(token, amount) {
console.log('Approval confirmed:', token, amount);
}
onError(message, error) {
console.error(message, error);
}
}
React Integration
React Hooks
// hooks/useIXFI.js
import { useState, useEffect, useContext, createContext } from 'react';
import { IXFIGateway, CrossChainAggregator } from '@ixfi/sdk';
// Context for IXFI SDK
const IXFIContext = createContext();
export const IXFIProvider = ({ children, config }) => {
const [gateway, setGateway] = useState(null);
const [aggregator, setAggregator] = useState(null);
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
const initializeSDK = async () => {
try {
const gatewayInstance = new IXFIGateway(config);
const aggregatorInstance = new CrossChainAggregator(config);
setGateway(gatewayInstance);
setAggregator(aggregatorInstance);
setIsInitialized(true);
} catch (error) {
console.error('Failed to initialize IXFI SDK:', error);
}
};
initializeSDK();
}, [config]);
return (
<IXFIContext.Provider value={{ gateway, aggregator, isInitialized }}>
{children}
</IXFIContext.Provider>
);
};
export const useIXFI = () => {
const context = useContext(IXFIContext);
if (!context) {
throw new Error('useIXFI must be used within IXFIProvider');
}
return context;
};
// hooks/useSwap.js
import { useState, useCallback } from 'react';
import { useIXFI } from './useIXFI';
export const useSwap = () => {
const { aggregator } = useIXFI();
const [quotes, setQuotes] = useState([]);
const [selectedQuote, setSelectedQuote] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const getQuotes = useCallback(async (tokenIn, tokenOut, amountIn) => {
if (!aggregator) return;
setLoading(true);
setError(null);
try {
const quotesResult = await aggregator.getAllQuotes(tokenIn, tokenOut, amountIn);
const formattedQuotes = quotesResult
.filter(q => q.success)
.sort((a, b) => parseFloat(b.amountOut) - parseFloat(a.amountOut));
setQuotes(formattedQuotes);
setSelectedQuote(formattedQuotes[0]);
return formattedQuotes;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [aggregator]);
const executeSwap = useCallback(async (swapParams) => {
if (!aggregator || !selectedQuote) return;
setLoading(true);
setError(null);
try {
const result = await aggregator.executeSwap(swapParams);
return result;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [aggregator, selectedQuote]);
return {
quotes,
selectedQuote,
setSelectedQuote,
getQuotes,
executeSwap,
loading,
error
};
};
// hooks/useCrossChain.js
import { useState, useCallback } from 'react';
import { useIXFI } from './useIXFI';
export const useCrossChain = () => {
const { gateway } = useIXFI();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const transferTokens = useCallback(async (params) => {
if (!gateway) return;
setLoading(true);
setError(null);
try {
const result = await gateway.transferTokens(params);
return result;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [gateway]);
const crossChainCall = useCallback(async (params) => {
if (!gateway) return;
setLoading(true);
setError(null);
try {
const result = await gateway.callContract(params);
return result;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [gateway]);
return {
transferTokens,
crossChainCall,
loading,
error
};
};
React Components
// components/SwapInterface.jsx
import React, { useState } from 'react';
import { useSwap } from '../hooks/useSwap';
import { useWallet } from '../hooks/useWallet';
import { ethers } from 'ethers';
const SwapInterface = () => {
const { account, signer } = useWallet();
const { quotes, selectedQuote, setSelectedQuote, getQuotes, executeSwap, loading, error } = useSwap();
const [tokenIn, setTokenIn] = useState('');
const [tokenOut, setTokenOut] = useState('');
const [amountIn, setAmountIn] = useState('');
const [slippage, setSlippage] = useState(0.5);
const handleGetQuotes = async () => {
if (!tokenIn || !tokenOut || !amountIn) return;
try {
const amount = ethers.utils.parseUnits(amountIn, 18);
await getQuotes(tokenIn, tokenOut, amount);
} catch (err) {
console.error('Failed to get quotes:', err);
}
};
const handleSwap = async () => {
if (!selectedQuote || !signer) return;
try {
const minAmountOut = ethers.BigNumber.from(selectedQuote.amountOut)
.mul(Math.floor((100 - slippage) * 100))
.div(10000);
await executeSwap({
tokenIn,
tokenOut,
amountIn: ethers.utils.parseUnits(amountIn, 18),
minAmountOut,
routerType: selectedQuote.routerType,
to: account
});
} catch (err) {
console.error('Swap failed:', err);
}
};
return (
<div className="swap-interface">
<h2>Token Swap</h2>
<div className="swap-form">
<div className="input-group">
<label>From Token</label>
<input
type="text"
placeholder="Token address"
value={tokenIn}
onChange={(e) => setTokenIn(e.target.value)}
/>
</div>
<div className="input-group">
<label>To Token</label>
<input
type="text"
placeholder="Token address"
value={tokenOut}
onChange={(e) => setTokenOut(e.target.value)}
/>
</div>
<div className="input-group">
<label>Amount</label>
<input
type="number"
placeholder="0.0"
value={amountIn}
onChange={(e) => setAmountIn(e.target.value)}
/>
</div>
<div className="input-group">
<label>Slippage Tolerance (%)</label>
<input
type="number"
step="0.1"
value={slippage}
onChange={(e) => setSlippage(parseFloat(e.target.value))}
/>
</div>
<button
onClick={handleGetQuotes}
disabled={loading || !tokenIn || !tokenOut || !amountIn}
>
{loading ? 'Getting Quotes...' : 'Get Quotes'}
</button>
</div>
{error && (
<div className="error">
Error: {error}
</div>
)}
{quotes.length > 0 && (
<div className="quotes-section">
<h3>Available Quotes</h3>
<div className="quotes-list">
{quotes.map((quote, index) => (
<div
key={index}
className={`quote-item ${selectedQuote === quote ? 'selected' : ''}`}
onClick={() => setSelectedQuote(quote)}
>
<div className="dex-name">{getDEXName(quote.routerType)}</div>
<div className="amount-out">
{ethers.utils.formatUnits(quote.amountOut, 18)} tokens
</div>
<div className="price-impact">
Impact: {(quote.priceImpact / 100).toFixed(2)}%
</div>
</div>
))}
</div>
{selectedQuote && (
<button
onClick={handleSwap}
disabled={loading}
className="swap-button"
>
{loading ? 'Swapping...' : 'Execute Swap'}
</button>
)}
</div>
)}
</div>
);
};
export default SwapInterface;
// components/CrossChainTransfer.jsx
import React, { useState } from 'react';
import { useCrossChain } from '../hooks/useCrossChain';
import { ethers } from 'ethers';
const CrossChainTransfer = () => {
const { transferTokens, loading, error } = useCrossChain();
const [fromChain, setFromChain] = useState('ethereum');
const [toChain, setToChain] = useState('polygon');
const [token, setToken] = useState('');
const [amount, setAmount] = useState('');
const [recipient, setRecipient] = useState('');
const supportedChains = [
{ id: 'ethereum', name: 'Ethereum' },
{ id: 'polygon', name: 'Polygon' },
{ id: 'bsc', name: 'BSC' },
{ id: 'avalanche', name: 'Avalanche' },
{ id: 'arbitrum', name: 'Arbitrum' },
{ id: 'optimism', name: 'Optimism' }
];
const handleTransfer = async () => {
if (!token || !amount || !recipient) return;
try {
await transferTokens({
fromChain,
toChain,
token,
amount: ethers.utils.parseUnits(amount, 18),
recipient
});
} catch (err) {
console.error('Transfer failed:', err);
}
};
return (
<div className="cross-chain-transfer">
<h2>Cross-Chain Transfer</h2>
<div className="transfer-form">
<div className="chain-selection">
<div className="input-group">
<label>From Chain</label>
<select value={fromChain} onChange={(e) => setFromChain(e.target.value)}>
{supportedChains.map(chain => (
<option key={chain.id} value={chain.id}>{chain.name}</option>
))}
</select>
</div>
<div className="input-group">
<label>To Chain</label>
<select value={toChain} onChange={(e) => setToChain(e.target.value)}>
{supportedChains.map(chain => (
<option key={chain.id} value={chain.id}>{chain.name}</option>
))}
</select>
</div>
</div>
<div className="input-group">
<label>Token Address</label>
<input
type="text"
placeholder="0x..."
value={token}
onChange={(e) => setToken(e.target.value)}
/>
</div>
<div className="input-group">
<label>Amount</label>
<input
type="number"
placeholder="0.0"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<div className="input-group">
<label>Recipient Address</label>
<input
type="text"
placeholder="0x..."
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
/>
</div>
<button
onClick={handleTransfer}
disabled={loading || !token || !amount || !recipient}
>
{loading ? 'Transferring...' : 'Transfer Tokens'}
</button>
</div>
{error && (
<div className="error">
Error: {error}
</div>
)}
</div>
);
};
export default CrossChainTransfer;
Vue.js Integration
Vue Composables
// composables/useIXFI.js
import { ref, reactive, computed } from 'vue';
import { IXFIGateway, CrossChainAggregator } from '@ixfi/sdk';
const state = reactive({
gateway: null,
aggregator: null,
isInitialized: false,
config: null
});
export const useIXFI = () => {
const initialize = async (config) => {
try {
state.config = config;
state.gateway = new IXFIGateway(config);
state.aggregator = new CrossChainAggregator(config);
state.isInitialized = true;
} catch (error) {
console.error('Failed to initialize IXFI SDK:', error);
throw error;
}
};
const isInitialized = computed(() => state.isInitialized);
const gateway = computed(() => state.gateway);
const aggregator = computed(() => state.aggregator);
return {
initialize,
isInitialized,
gateway,
aggregator
};
};
// composables/useSwap.js
import { ref, computed } from 'vue';
import { useIXFI } from './useIXFI';
export const useSwap = () => {
const { aggregator } = useIXFI();
const quotes = ref([]);
const selectedQuote = ref(null);
const loading = ref(false);
const error = ref(null);
const getQuotes = async (tokenIn, tokenOut, amountIn) => {
if (!aggregator.value) return;
loading.value = true;
error.value = null;
try {
const result = await aggregator.value.getAllQuotes(tokenIn, tokenOut, amountIn);
quotes.value = result.filter(q => q.success);
selectedQuote.value = quotes.value[0];
return quotes.value;
} catch (err) {
error.value = err.message;
throw err;
} finally {
loading.value = false;
}
};
const executeSwap = async (swapParams) => {
if (!aggregator.value || !selectedQuote.value) return;
loading.value = true;
error.value = null;
try {
const result = await aggregator.value.executeSwap(swapParams);
return result;
} catch (err) {
error.value = err.message;
throw err;
} finally {
loading.value = false;
}
};
const bestQuote = computed(() => quotes.value[0]);
const hasQuotes = computed(() => quotes.value.length > 0);
return {
quotes,
selectedQuote,
loading,
error,
bestQuote,
hasQuotes,
getQuotes,
executeSwap
};
};
Vue Components
<!-- components/SwapInterface.vue -->
<template>
<div class="swap-interface">
<h2>Token Swap</h2>
<form @submit.prevent="handleGetQuotes" class="swap-form">
<div class="input-group">
<label>From Token</label>
<input
v-model="formData.tokenIn"
type="text"
placeholder="Token address"
required
/>
</div>
<div class="input-group">
<label>To Token</label>
<input
v-model="formData.tokenOut"
type="text"
placeholder="Token address"
required
/>
</div>
<div class="input-group">
<label>Amount</label>
<input
v-model="formData.amountIn"
type="number"
placeholder="0.0"
step="any"
required
/>
</div>
<button
type="submit"
:disabled="loading || !isFormValid"
class="get-quotes-btn"
>
{{ loading ? 'Getting Quotes...' : 'Get Quotes' }}
</button>
</form>
<div v-if="error" class="error">
Error: {{ error }}
</div>
<div v-if="hasQuotes" class="quotes-section">
<h3>Available Quotes</h3>
<div class="quotes-list">
<div
v-for="(quote, index) in quotes"
:key="index"
:class="['quote-item', { selected: selectedQuote === quote }]"
@click="selectedQuote = quote"
>
<div class="dex-name">{{ getDEXName(quote.routerType) }}</div>
<div class="amount-out">{{ formatAmount(quote.amountOut) }} tokens</div>
<div class="price-impact">Impact: {{ formatPercent(quote.priceImpact) }}</div>
</div>
</div>
<button
v-if="selectedQuote"
@click="handleSwap"
:disabled="loading"
class="swap-btn"
>
{{ loading ? 'Swapping...' : 'Execute Swap' }}
</button>
</div>
</div>
</template>
<script setup>
import { reactive, computed } from 'vue';
import { useSwap } from '../composables/useSwap';
import { ethers } from 'ethers';
const { quotes, selectedQuote, loading, error, hasQuotes, getQuotes, executeSwap } = useSwap();
const formData = reactive({
tokenIn: '',
tokenOut: '',
amountIn: '',
slippage: 0.5
});
const isFormValid = computed(() =>
formData.tokenIn && formData.tokenOut && formData.amountIn
);
const handleGetQuotes = async () => {
try {
const amount = ethers.utils.parseUnits(formData.amountIn, 18);
await getQuotes(formData.tokenIn, formData.tokenOut, amount);
} catch (err) {
console.error('Failed to get quotes:', err);
}
};
const handleSwap = async () => {
if (!selectedQuote.value) return;
try {
const minAmountOut = ethers.BigNumber.from(selectedQuote.value.amountOut)
.mul(Math.floor((100 - formData.slippage) * 100))
.div(10000);
await executeSwap({
tokenIn: formData.tokenIn,
tokenOut: formData.tokenOut,
amountIn: ethers.utils.parseUnits(formData.amountIn, 18),
minAmountOut,
routerType: selectedQuote.value.routerType
});
} catch (err) {
console.error('Swap failed:', err);
}
};
const getDEXName = (routerType) => {
const dexNames = {
0: 'Uniswap V2',
1: 'SushiSwap V2',
10: 'Uniswap V3',
30: 'Curve'
};
return dexNames[routerType] || `Router ${routerType}`;
};
const formatAmount = (amount) => {
return ethers.utils.formatUnits(amount, 18);
};
const formatPercent = (value) => {
return `${(value / 100).toFixed(2)}%`;
};
</script>
<style scoped>
.swap-interface {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.swap-form {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 20px;
}
.input-group {
display: flex;
flex-direction: column;
}
.input-group label {
margin-bottom: 5px;
font-weight: bold;
}
.input-group input {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.quotes-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
}
.quote-item {
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s;
}
.quote-item:hover {
border-color: #007bff;
}
.quote-item.selected {
border-color: #007bff;
background-color: #f8f9fa;
}
.error {
color: red;
padding: 10px;
margin: 10px 0;
border: 1px solid red;
border-radius: 5px;
background-color: #ffeaea;
}
button {
padding: 12px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
</style>
Error Handling & Best Practices
Comprehensive Error Handling
class IXFIErrorHandler {
constructor() {
this.errorTypes = {
WALLET_NOT_CONNECTED: 'WalletNotConnected',
INSUFFICIENT_BALANCE: 'InsufficientBalance',
SLIPPAGE_EXCEEDED: 'SlippageExceeded',
TRANSACTION_FAILED: 'TransactionFailed',
NETWORK_ERROR: 'NetworkError',
QUOTE_EXPIRED: 'QuoteExpired'
};
}
handleError(error, context = '') {
console.error(`[IXFI] Error in ${context}:`, error);
// Parse error message and code
const errorInfo = this.parseError(error);
// Show user-friendly message
this.showUserMessage(errorInfo);
// Log for analytics
this.logError(errorInfo, context);
return errorInfo;
}
parseError(error) {
// Check for common error patterns
if (error.code === 4001) {
return {
type: 'USER_REJECTED',
message: 'Transaction was rejected by user',
userMessage: 'You rejected the transaction'
};
}
if (error.message.includes('insufficient funds')) {
return {
type: this.errorTypes.INSUFFICIENT_BALANCE,
message: error.message,
userMessage: 'Insufficient balance for this transaction'
};
}
if (error.message.includes('slippage')) {
return {
type: this.errorTypes.SLIPPAGE_EXCEEDED,
message: error.message,
userMessage: 'Price moved too much. Try increasing slippage tolerance.'
};
}
// Default error
return {
type: 'UNKNOWN_ERROR',
message: error.message || 'An unknown error occurred',
userMessage: 'Something went wrong. Please try again.'
};
}
showUserMessage(errorInfo) {
// Use your preferred notification system
console.warn('User Message:', errorInfo.userMessage);
// Example: show in UI toast/notification
if (window.showNotification) {
window.showNotification(errorInfo.userMessage, 'error');
}
}
logError(errorInfo, context) {
// Log to your analytics service
console.log('Logging error:', errorInfo, context);
}
}
const errorHandler = new IXFIErrorHandler();
// Usage in async functions
const safeExecute = async (operation, context = '') => {
try {
return await operation();
} catch (error) {
return errorHandler.handleError(error, context);
}
};
Performance Optimization
class IXFIPerformanceOptimizer {
constructor() {
this.cache = new Map();
this.cacheTimeout = 30000; // 30 seconds
}
// Cache quotes to avoid repeated requests
async getCachedQuotes(tokenIn, tokenOut, amountIn) {
const cacheKey = `${tokenIn}-${tokenOut}-${amountIn}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data;
}
// Fetch fresh quotes
const quotes = await aggregator.getAllQuotes(tokenIn, tokenOut, amountIn);
// Cache the result
this.cache.set(cacheKey, {
data: quotes,
timestamp: Date.now()
});
return quotes;
}
// Debounce quote requests
debounceQuoteRequest = this.debounce(async (tokenIn, tokenOut, amountIn) => {
return await this.getCachedQuotes(tokenIn, tokenOut, amountIn);
}, 500);
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Batch multiple operations
async batchOperations(operations) {
return await Promise.allSettled(operations);
}
// Clean up cache periodically
startCacheCleanup() {
setInterval(() => {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp > this.cacheTimeout) {
this.cache.delete(key);
}
}
}, 60000); // Clean every minute
}
}
Testing
Unit Testing with Jest
// tests/ixfi-integration.test.js
import { IXFIGateway, CrossChainAggregator } from '@ixfi/sdk';
import { ethers } from 'ethers';
describe('IXFI Integration', () => {
let gateway;
let aggregator;
let mockProvider;
let mockSigner;
beforeEach(() => {
mockProvider = {
getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }),
getBlockNumber: jest.fn().mockResolvedValue(12345)
};
mockSigner = {
getAddress: jest.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'),
provider: mockProvider
};
const config = {
provider: mockProvider,
signer: mockSigner,
chainId: 1
};
gateway = new IXFIGateway(config);
aggregator = new CrossChainAggregator(config);
});
test('should initialize gateway correctly', () => {
expect(gateway).toBeDefined();
expect(gateway.provider).toBe(mockProvider);
});
test('should get quotes from aggregator', async () => {
// Mock the getAllQuotes method
aggregator.getAllQuotes = jest.fn().mockResolvedValue([
{
routerType: 0,
amountOut: ethers.BigNumber.from('1000000000000000000'),
success: true
}
]);
const quotes = await aggregator.getAllQuotes(
'0xA0b86a33E6441e1a02c4e4670dd96EA0f25A632', // USDC
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
ethers.utils.parseUnits('1000', 6)
);
expect(quotes).toHaveLength(1);
expect(quotes[0].success).toBe(true);
});
});
Deployment
Production Deployment
// config/production.js
export const productionConfig = {
networks: {
ethereum: {
rpcUrl: 'https://mainnet.infura.io/v3/YOUR_INFURA_KEY',
chainId: 1,
contracts: {
gateway: '0x...',
aggregator: '0x...',
metaTxGateway: '0x...'
}
},
polygon: {
rpcUrl: 'https://polygon-rpc.com',
chainId: 137,
contracts: {
gateway: '0x...',
aggregator: '0x...',
metaTxGateway: '0x...'
}
}
},
relayer: {
endpoint: 'https://relayer.ixfi.com',
timeout: 30000
},
features: {
gaslessTransactions: true,
crossChainSwaps: true,
multiRouting: true
}
};
// Build configuration
export const buildConfig = {
optimization: {
minimize: true,
moduleIds: 'deterministic'
},
externals: {
'ethers': 'ethers'
}
};
Security Considerations
Input Validation: Always validate user inputs
Token Approvals: Check and handle token approvals securely
Slippage Protection: Implement appropriate slippage tolerances
Error Handling: Never expose sensitive error details to users
Rate Limiting: Implement rate limiting for API calls
HTTPS Only: Always use HTTPS in production
Wallet Security: Guide users on wallet security best practices
Resources
Last updated