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

  1. "Native swap config provided but input token is not native token"

    • Solution: Use isNativeToken() to verify token type before choosing config
  2. Gas estimation failures

    • Solution: Provide fallback gas limits in your txExecutor.estimateGas method
  3. Transaction reverts

    • Solution: Check ETH balance, gas limits, and transaction parameters
  4. "Quote response indicates ERC20 swap, not native token"

    • Solution: Verify that your quote request uses the correct native token address