Token Management
The Aori protocol supports a wide range of tokens across multiple blockchain networks. This guide covers how to discover available tokens, load token metadata, and work with token information in your applications.
Overview
Token management in Aori involves:
- Discovery: Finding available tokens across supported chains
- Caching: Loading and storing token metadata locally for performance
- Validation: Verifying token addresses and metadata before trading
- Cross-Chain Mapping: Understanding token relationships across different networks
Token Information Structure
Each token in the Aori protocol includes comprehensive metadata:
interface TokenInfo {
symbol: string; // Token symbol (e.g., "USDC", "ETH")
address: string; // Token contract address
chainId: number; // Chain ID where token is deployed
chainKey: string; // Human-readable chain identifier
}
Loading All Tokens
During Initialization
Load all tokens when creating an Aori instance for immediate access:
import { Aori } from '@aori-io/aori-ts';
// Load all tokens during initialization
const aori = await Aori.create(
'https://api.aori.io',
'wss://api.aori.io',
'your_api_key',
true // Load tokens during initialization
);
// Access all cached tokens
const allTokens = aori.getAllTokens();
console.log(`Loaded ${allTokens.length} tokens across all chains`);
// Filter tokens by chain
const ethereumTokens = allTokens.filter(token => token.chainKey === 'ethereum');
const baseTokens = allTokens.filter(token => token.chainId === 8453);
Manual Loading
Load all tokens after initialization when needed:
const aori = await Aori.create(); // Don't load tokens initially
// Load all tokens manually
await aori.loadTokens();
const allTokens = aori.getAllTokens();
console.log(`Loaded ${allTokens.length} tokens`);
Loading Tokens by Chain
Using Chain Keys
Load tokens for specific chains using human-readable identifiers:
// Load tokens for specific chains
await aori.loadTokens('ethereum');
await aori.loadTokens('base');
await aori.loadTokens('arbitrum');
await aori.loadTokens('optimism');
// Get cached tokens for specific chains
const ethereumTokens = aori.getTokens('ethereum');
const baseTokens = aori.getTokens('base');
console.log(`Ethereum: ${ethereumTokens.length} tokens`);
console.log(`Base: ${baseTokens.length} tokens`);
Using Chain IDs
Load tokens using numeric chain IDs:
// Load tokens by chain ID
await aori.loadTokens(1); // Ethereum
await aori.loadTokens(8453); // Base
await aori.loadTokens(42161); // Arbitrum
await aori.loadTokens(10); // Optimism
// Get cached tokens by chain ID
const ethereumTokens = aori.getTokens(1);
const baseTokens = aori.getTokens(8453);
Direct Token Fetching
Bypass Cache
Fetch tokens directly from the API without using the cache:
import { fetchAllTokens, getTokens } from '@aori-io/aori-ts';
// Fetch all tokens directly from API
const allTokens = await fetchAllTokens('https://api.aori.io', 'your_api_key');
// Fetch tokens for specific chain directly
const ethereumTokens = await getTokens('ethereum', 'https://api.aori.io', 'your_api_key');
const baseTokens = await getTokens(8453, 'https://api.aori.io', 'your_api_key');
With Instance Methods
Fetch tokens using instance methods to bypass cache:
// Fetch tokens for specific chain (bypasses cache)
const ethereumTokens = await aori.fetchTokens('ethereum');
const baseTokens = await aori.fetchTokens(8453);
// These don't update the internal cache - use loadTokens() for that
Finding Specific Tokens
By Symbol
Find tokens across chains by their symbol:
const allTokens = aori.getAllTokens();
// Find all USDC tokens across chains
const usdcTokens = allTokens.filter(token => token.symbol === 'USDC');
console.log('USDC tokens found:');
usdcTokens.forEach(token => {
console.log(`${token.chainKey}: ${token.address}`);
});
// Find USDC on specific chain
const ethereumUSDC = allTokens.find(token =>
token.symbol === 'USDC' && token.chainKey === 'ethereum'
);
By Address
Validate and find tokens by address:
const allTokens = aori.getAllTokens();
// Find token by address
const tokenAddress = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const token = allTokens.find(token =>
token.address.toLowerCase() === tokenAddress.toLowerCase()
);
if (token) {
console.log(`Found ${token.symbol} on ${token.chainKey}`);
} else {
console.log('Token not found or not supported');
}
Cross-Chain Token Mapping
Find the same token across different chains:
function findTokenAcrossChains(symbol: string, tokens: TokenInfo[]) {
return tokens
.filter(token => token.symbol === symbol)
.reduce((acc, token) => {
acc[token.chainKey] = token;
return acc;
}, {} as Record<string, TokenInfo>);
}
const allTokens = aori.getAllTokens();
const usdcMapping = findTokenAcrossChains('USDC', allTokens);
console.log('USDC addresses:');
Object.entries(usdcMapping).forEach(([chain, token]) => {
console.log(`${chain}: ${token.address}`);
});
Native Token Handling
Native tokens (ETH) are represented with a special address across all chains:
import { NATIVE_TOKEN_ADDRESS, isNativeToken } from '@aori-io/aori-ts';
const allTokens = aori.getAllTokens();
// Find all native tokens
const nativeTokens = allTokens.filter(token => isNativeToken(token.address));
console.log('Native tokens:');
nativeTokens.forEach(token => {
console.log(`${token.chainKey}: ${token.symbol} (${token.name})`);
});
// Check if a specific address is native
const isNative = isNativeToken('0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'); // true
console.log(`Native token address: ${NATIVE_TOKEN_ADDRESS}`);
Validation and Verification
Token Address Validation
Validate token addresses before using them in quotes:
function validateTokenAddress(address: string, chainKey: string, tokens: TokenInfo[]): boolean {
return tokens.some(token =>
token.address.toLowerCase() === address.toLowerCase() &&
token.chainKey === chainKey
);
}
const allTokens = aori.getAllTokens();
const isValid = validateTokenAddress(
'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
'base',
allTokens
);
if (!isValid) {
throw new Error('Token not supported on specified chain');
}
Token Existence Validation
Ensure tokens exist in the supported token list:
function validateTokenExists(tokenAddress: string, chainKey: string, tokens: TokenInfo[]): TokenInfo | null {
const token = tokens.find(t =>
t.address.toLowerCase() === tokenAddress.toLowerCase() &&
t.chainKey === chainKey
);
return token || null;
}
Practical Examples
Building a Token Selector
Create a token selector component for your UI:
interface TokenSelectorProps {
chainKey: string;
onTokenSelect: (token: TokenInfo) => void;
}
function TokenSelector({ chainKey, onTokenSelect }: TokenSelectorProps) {
const [tokens, setTokens] = useState<TokenInfo[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadTokens() {
try {
const aori = await Aori.create();
await aori.loadTokens(chainKey);
const chainTokens = aori.getTokens(chainKey);
setTokens(chainTokens);
} catch (error) {
console.error('Failed to load tokens:', error);
} finally {
setLoading(false);
}
}
loadTokens();
}, [chainKey]);
if (loading) return <div>Loading tokens...</div>;
return (
<div className="token-selector">
{tokens.map(token => (
<div
key={`${token.chainId}-${token.address}`}
className="token-item"
onClick={() => onTokenSelect(token)}
>
<div>
<div className="symbol">{token.symbol}</div>
<div className="address">{token.address.slice(0, 6)}...{token.address.slice(-4)}</div>
</div>
</div>
))}
</div>
);
}
Cross-Chain Trading Setup
Set up tokens for cross-chain trading:
async function setupCrossChainTrade(
inputChain: string,
outputChain: string,
tokenSymbol: string
) {
const aori = await Aori.create();
// Load tokens for both chains
await Promise.all([
aori.loadTokens(inputChain),
aori.loadTokens(outputChain)
]);
// Find token on both chains
const inputTokens = aori.getTokens(inputChain);
const outputTokens = aori.getTokens(outputChain);
const inputToken = inputTokens.find(t => t.symbol === tokenSymbol);
const outputToken = outputTokens.find(t => t.symbol === tokenSymbol);
if (!inputToken) {
throw new Error(`${tokenSymbol} not available on ${inputChain}`);
}
if (!outputToken) {
throw new Error(`${tokenSymbol} not available on ${outputChain}`);
}
return { inputToken, outputToken };
}
// Usage
const { inputToken, outputToken } = await setupCrossChainTrade(
'ethereum',
'base',
'USDC'
);
// Now create quote with validated tokens
const quote = await aori.getQuote({
offerer: userAddress,
recipient: userAddress,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputAmount: '1000000', // 1 USDC (6 decimals)
inputChain: 'ethereum',
outputChain: 'base'
});
Token Metadata Cache
Implement a token metadata cache for performance:
class TokenCache {
private cache = new Map<string, TokenInfo[]>();
private aori: Aori;
constructor(aori: Aori) {
this.aori = aori;
}
async getTokens(chainKey: string): Promise<TokenInfo[]> {
if (this.cache.has(chainKey)) {
return this.cache.get(chainKey)!;
}
await this.aori.loadTokens(chainKey);
const tokens = this.aori.getTokens(chainKey);
this.cache.set(chainKey, tokens);
return tokens;
}
findToken(address: string, chainKey: string): TokenInfo | undefined {
const tokens = this.cache.get(chainKey);
return tokens?.find(t =>
t.address.toLowerCase() === address.toLowerCase()
);
}
clearCache(chainKey?: string) {
if (chainKey) {
this.cache.delete(chainKey);
} else {
this.cache.clear();
}
}
}
Performance Considerations
Efficient Loading
Load tokens strategically based on your application needs:
// Load only what you need
const supportedChains = ['ethereum', 'base', 'arbitrum'];
async function loadRequiredTokens(aori: Aori) {
// Load tokens for all supported chains in parallel
await Promise.all(
supportedChains.map(chain => aori.loadTokens(chain))
);
}
// Or load on-demand
async function getTokensOnDemand(aori: Aori, chainKey: string) {
const cached = aori.getTokens(chainKey);
if (cached.length === 0) {
await aori.loadTokens(chainKey);
return aori.getTokens(chainKey);
}
return cached;
}
Filtering for UI
Filter tokens appropriately for different UI contexts:
function getPopularTokens(tokens: TokenInfo[]): TokenInfo[] {
const popularSymbols = ['ETH', 'USDC', 'USDT', 'WETH', 'DAI'];
return tokens.filter(token =>
popularSymbols.includes(token.symbol)
);
}
function getTestnetTokens(tokens: TokenInfo[]): TokenInfo[] {
// Filter tokens based on testnet chain IDs
const testnetChainIds = [5, 11155111]; // Goerli, Sepolia
return tokens.filter(token =>
testnetChainIds.includes(token.chainId)
);
}
Error Handling
Implement robust error handling for token operations:
async function safeLoadTokens(aori: Aori, chainKey: string): Promise<TokenInfo[]> {
try {
await aori.loadTokens(chainKey);
return aori.getTokens(chainKey);
} catch (error) {
console.error(`Failed to load tokens for ${chainKey}:`, error);
// Fallback to direct fetch
try {
return await aori.fetchTokens(chainKey);
} catch (fallbackError) {
console.error(`Fallback token fetch failed:`, fallbackError);
return [];
}
}
}
function validateTokenForTrading(token: TokenInfo | undefined, chainKey: string): TokenInfo {
if (!token) {
throw new Error(`Token not found on ${chainKey}`);
}
if (!token.address || token.address === '0x') {
throw new Error('Invalid token address');
}
if (!token.symbol || token.symbol.trim() === '') {
throw new Error('Invalid token symbol');
}
return token;
}