Native Token Support
The Aori protocol provides first-class support for native tokens (ETH) across all supported EVM chains. This guide covers how to request quotes, execute swaps, and handle transactions when working with native tokens.
Overview
Native token support enables users to swap ETH directly without requiring wrapped tokens (WETH). The protocol handles the complexity of native token deposits and transfers, providing a seamless experience for both single-chain and cross-chain swaps.
Key Benefits
- Direct ETH Trading: No need to wrap ETH into WETH before trading
- Simplified UX: Users can send ETH directly from their wallet
- Gas Efficiency: Optimized for native token transfers
- Cross-Chain Support: Native ETH swaps work across all supported chains
Native Token Address
Native tokens are represented using a special address constant across all EVM chains:
import { NATIVE_TOKEN_ADDRESS, isNativeToken } from '@aori-io/aori-ts';
console.log(NATIVE_TOKEN_ADDRESS); // "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
// Check if a token address represents native ETH
const isNative = isNativeToken('0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'); // true
const isNotNative = isNativeToken('0xA0b86a33E6441E85E0d8B7b9B7B1E8b7e2f15E38'); // false
Getting Quotes for Native Tokens
When requesting quotes involving native tokens, use the special native token address:
import { Aori, NATIVE_TOKEN_ADDRESS } from '@aori-io/aori-ts';
const aori = await Aori.create();
// ETH to USDC on the same chain
const ethToUsdcQuote = await aori.getQuote({
offerer: '0x742d35Cc6B3eD4C58BB6B43b6B6aD1D0e3B6d8f1',
recipient: '0x742d35Cc6B3eD4C58BB6B43b6B6aD1D0e3B6d8f1',
inputToken: NATIVE_TOKEN_ADDRESS, // ETH
outputToken: '0xA0b86a33E6441E85E0d8B7b9B7B1E8b7e2f15E38', // USDC
inputAmount: '1000000000000000000', // 1 ETH (18 decimals)
inputChain: 'ethereum',
outputChain: 'ethereum'
});
// Cross-chain ETH to USDC
const crossChainQuote = await aori.getQuote({
offerer: '0x742d35Cc6B3eD4C58BB6B43b6B6aD1D0e3B6d8f1',
recipient: '0x742d35Cc6B3eD4C58BB6B43b6B6aD1D0e3B6d8f1',
inputToken: NATIVE_TOKEN_ADDRESS, // ETH on Ethereum
outputToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
inputAmount: '500000000000000000', // 0.5 ETH
inputChain: 'ethereum',
outputChain: 'base'
});
Identifying Native Token Quotes
The SDK provides utilities to identify when a quote involves native tokens:
import { isNativeSwap, isNativeToken } from '@aori-io/aori-ts';
const quote = await aori.getQuote(quoteRequest);
// Check if this is a native token swap
if (isNativeSwap(quote)) {
console.log('This quote involves native token (ETH)');
// Check which token is native
if (isNativeToken(quote.inputToken)) {
console.log('Input token is native ETH');
}
if (isNativeToken(quote.outputToken)) {
console.log('Output token is native ETH');
}
}
Executing Native Token Swaps
Native token swaps require a different execution flow compared to ERC20 tokens:
Basic Native Token Swap
import { NativeSwapConfig } from '@aori-io/aori-ts';
// Get a quote for native token swap
const quote = await aori.getQuote({
offerer: userAddress,
recipient: userAddress,
inputToken: NATIVE_TOKEN_ADDRESS,
outputToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
inputAmount: '1000000000000000000', // 1 ETH
inputChain: 'ethereum',
outputChain: 'base'
});
// Configure native swap execution
const nativeConfig: NativeSwapConfig = {
type: 'native',
txExecutor: {
sendTransaction: async (txRequest) => {
// Your wallet's sendTransaction method
return await wallet.sendTransaction(txRequest);
},
estimateGas: async (txRequest) => {
// Optional: Your wallet's gas estimation
return await wallet.estimateGas(txRequest);
}
},
gasLimit: '300000' // Optional: Custom gas limit
};
// Execute the native token swap
const result = await aori.executeSwap(quote, nativeConfig);
if (result.success) {
console.log('Native swap executed:', result.txHash);
} else {
console.error('Swap failed:', result.error);
}
Advanced Transaction Executor
For more control over transaction execution, implement a custom transaction executor:
import { TxExecutor, TransactionRequest } from '@aori-io/aori-ts';
class CustomTxExecutor implements TxExecutor {
constructor(private wallet: any) {}
async sendTransaction(txRequest: TransactionRequest) {
// Add custom validation
if (!txRequest.to || !txRequest.data || !txRequest.value) {
throw new Error('Invalid transaction request');
}
// Add custom gas price logic
const gasPrice = await this.wallet.getGasPrice();
return await this.wallet.sendTransaction({
...txRequest,
gasPrice: gasPrice.mul(110).div(100) // 10% gas price bump
});
}
async estimateGas(txRequest: TransactionRequest) {
try {
const estimated = await this.wallet.estimateGas(txRequest);
// Add 20% buffer to estimated gas
return estimated.mul(120).div(100);
} catch (error) {
// Fallback gas limit for native swaps
return BigInt(250000);
}
}
}
// Use the custom executor
const customExecutor = new CustomTxExecutor(wallet);
const nativeConfig: NativeSwapConfig = {
type: 'native',
txExecutor: customExecutor
};
const result = await aori.executeSwap(quote, nativeConfig);
Manual Transaction Construction
For advanced use cases, you can manually construct native swap transactions:
import { constructNativeSwapTransaction, submitSwap } from '@aori-io/aori-ts';
// Get quote and submit swap to get transaction data
const quote = await aori.getQuote(quoteRequest);
const swapResponse = await submitSwap(
{
orderHash: quote.orderHash,
signature: "" // Empty signature for native swaps
},
'https://api.aori.io',
'your_api_key'
);
// Construct the transaction request
const txRequest = constructNativeSwapTransaction(swapResponse, '300000');
console.log('Transaction details:', {
to: txRequest.to,
value: txRequest.value,
data: txRequest.data,
gasLimit: txRequest.gasLimit
});
// Execute using your preferred method
const tx = await wallet.sendTransaction(txRequest);
await tx.wait();
Error Handling
Native token swaps can fail for various reasons. Implement proper error handling:
try {
const result = await aori.executeSwap(quote, nativeConfig);
if (!result.success) {
switch (true) {
case result.error?.includes('insufficient funds'):
console.error('Insufficient ETH balance');
break;
case result.error?.includes('gas'):
console.error('Gas estimation or execution failed');
break;
case result.error?.includes('nonce'):
console.error('Transaction nonce issue');
break;
default:
console.error('Unknown error:', result.error);
}
}
} catch (error) {
console.error('Swap execution failed:', error);
if (error.message.includes('Native swap config provided but input token is not native')) {
console.error('Configuration mismatch: Use ERC20 config for non-native tokens');
}
}
Best Practices
1. Validate Token Types
Always validate that you're using the correct configuration for the token type:
import { isNativeToken } from '@aori-io/aori-ts';
const quote = await aori.getQuote(quoteRequest);
// Determine the correct configuration type
const configType = isNativeToken(quote.inputToken) ? 'native' : 'erc20';
if (configType === 'native') {
// Use NativeSwapConfig
const config: NativeSwapConfig = { /* ... */ };
} else {
// Use ERC20SwapConfig
const config: ERC20SwapConfig = { /* ... */ };
}
2. Gas Estimation
Provide gas estimation for better UX:
const txExecutor = {
sendTransaction: async (txRequest) => wallet.sendTransaction(txRequest),
estimateGas: async (txRequest) => {
try {
return await wallet.estimateGas(txRequest);
} catch (error) {
// Fallback for native swaps
return BigInt(200000);
}
}
};
3. Balance Checking
Check ETH balance before attempting swaps:
const balance = await wallet.getBalance();
const requiredAmount = BigInt(quote.inputAmount);
if (balance < requiredAmount) {
throw new Error(`Insufficient ETH balance. Required: ${requiredAmount}, Available: ${balance}`);
}
4. Transaction Monitoring
Monitor native token transactions for completion:
const result = await aori.executeSwap(quote, nativeConfig);
if (result.success && result.txHash) {
console.log(`Transaction submitted: ${result.txHash}`);
// Monitor transaction status
const receipt = await wallet.provider.waitForTransaction(result.txHash);
if (receipt.status === 1) {
console.log('Native swap confirmed!');
} else {
console.error('Transaction failed');
}
}
Integration Examples
React Hook for Native Swaps
import { useState } from 'react';
import { Aori, NATIVE_TOKEN_ADDRESS, NativeSwapConfig } from '@aori-io/aori-ts';
export function useNativeSwap() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const executeNativeSwap = async (
inputAmount: string,
outputToken: string,
outputChain: string,
wallet: any
) => {
setLoading(true);
setError(null);
try {
const aori = await Aori.create();
const quote = await aori.getQuote({
offerer: wallet.address,
recipient: wallet.address,
inputToken: NATIVE_TOKEN_ADDRESS,
outputToken,
inputAmount,
inputChain: 'ethereum',
outputChain
});
const config: NativeSwapConfig = {
type: 'native',
txExecutor: {
sendTransaction: (txRequest) => wallet.sendTransaction(txRequest),
estimateGas: (txRequest) => wallet.estimateGas(txRequest)
}
};
const result = await aori.executeSwap(quote, config);
if (!result.success) {
throw new Error(result.error || 'Swap failed');
}
return result.txHash;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
} finally {
setLoading(false);
}
};
return { executeNativeSwap, loading, error };
}
Troubleshooting
Common Issues
-
"Native swap config provided but input token is not native token"
- Solution: Use
isNativeToken()
to verify token type before choosing config
- Solution: Use
-
Gas estimation failures
- Solution: Provide fallback gas limits in your
txExecutor.estimateGas
method
- Solution: Provide fallback gas limits in your
-
Transaction reverts
- Solution: Check ETH balance, gas limits, and transaction parameters
-
"Quote response indicates ERC20 swap, not native token"
- Solution: Verify that your quote request uses the correct native token address