# Portals API - Complete Documentation for AI Agents > Comprehensive DeFi API for token data, cross-chain swaps, and intent-based transaction execution --- ## Table of Contents 1. Introduction & Overview 2. Authentication & API Keys 3. Core Concepts 4. Data API Endpoints 5. Trading API Endpoints 6. Cross-Chain Operations 7. Code Examples 8. Error Handling & Rate Limits 9. Best Practices 10. Resources & Tools --- ## 1. Introduction & Overview Portals removes the complexity from DeFi development. Instead of integrating with hundreds of protocols individually, managing cross-chain bridging logic, and handling token conversions across different standards, you get a single API that makes any DeFi operation as simple as a REST call. ### Core Value Proposition - **Single API** for 290+ DeFi protocols across 10+ networks - **Intent-based** - specify what you want, not how to do it - **Any-to-any** swaps including complex DeFi positions - **Cross-chain** operations in single transactions - **Real-time & historical** data for all supported assets ### Key Statistics - 290+ DeFi protocols integrated - 9 EVM networks supported - 7 networks with cross-chain capabilities - 100+ requests/minute free tier - 0-1% customizable fee structure - 100% fee retention for partners ### Supported Networks **Cross-chain enabled**: Ethereum, Arbitrum, Base, Optimism, Polygon, BSC, Avalanche **Coming soon**: Sonic, HyperEVM ### Supported Platforms Yearn V3, Aave V3, Curve, Uniswap V3, Morpho, Balancer, Convex, Beefy, Pendle, Velodrome, Ether.fi, Lido, Compound, Euler, Rocket Pool, and 275+ more. ### Supported Asset Types Pool, CLP (concentrated liquidity pool), yield, yield aggregator, RWA (real world assets), liquid staking derivatives (LSD), liquid restaking tokens (LRT), stablecoins, bitcoin derivatives, and more. --- ## 2. Authentication & API Keys ### API Key Requirements | Endpoint | API Key Required | Plan Required | Purpose | |----------|------------------|---------------|---------| | /v2/tokens | Required | All Plans | Get token data and prices | | /v2/account | Required | All Plans | Get account balances | | /v2/tokens/history | Required | Pioneer+ | Historical token data | | /v2/tokens/transactions | Required | Pioneer+ | Token transaction history | | /v2/tokens/holders | Required | Pioneer+ | Token holder information | | /v2/portal | Optional | All Plans | Generate swap/zap orders | | /v2/portal/estimate | Optional | All Plans | Simulate transactions | | /v2/approval | Optional | All Plans | Check/generate approvals | ### Getting an API Key 1. Sign up at https://build.portals.fi/dashboard 2. Create new API key from dashboard 3. Include in Authorization header: `Authorization: YOUR_API_KEY` ### Security Best Practices - Never expose API keys in client-side code - Never commit API keys to version control - Rotate keys if compromised - Use environment variables for key storage --- ## 3. Core Concepts ### Token Identification Format All tokens use format: `{network}:{address}` - Example: `ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` (USDC on Ethereum) - Native tokens use zero address: `ethereum:0x0000000000000000000000000000000000000000` (ETH) ### Token Types **Native Tokens** (platform: "native") - Network gas tokens (ETH, POL, BNB) - Zero address: 0x0000000000000000000000000000000000000000 - No approval needed for swaps **Basic Tokens** (platform: "basic") - Simple ERC-20 tokens (USDC, WETH, LINK) - Not composed of other tokens - Require approval for swaps **Platform Tokens** - Composed of other tokens - Examples: LP tokens, vault shares, lending positions - Platforms: curve, uniswap-v3, yearn-v3, morpho, aave-v3, etc. - Key properties: price, liquidity, APY, reserves, pricePerShare ### Intent-Based Architecture Instead of specifying "swap ETH to USDC then deposit to Curve", you specify: - Input token: ETH - Output token: Curve pool token - Amount The system automatically: - Finds optimal routing - Handles intermediate swaps - Manages approvals - Bundles into single transaction - Validates before execution --- ## 4. Data API Endpoints ### GET /v2/tokens - Token Prices & Metrics **Purpose**: Get comprehensive token information across all networks and protocols **Base URL**: https://api.portals.fi/v2/tokens **Authentication**: Required **Query Parameters**: - `search` (string): Search term for token names/symbols - `platforms` (string): Filter by platform (comma-separated) - `networks` (string): Filter by network (comma-separated) - `ids` (string[]): Specific token IDs to fetch - `minLiquidity` (number): Minimum TVL in USD (default: 500) - `maxLiquidity` (number): Maximum TVL in USD - `minApy` (number): Minimum APY percentage - `maxApy` (number): Maximum APY percentage - `sortBy` (enum): Field to sort by (liquidity, price, apy, volumeUsd1d, name, symbol) - `sortDirection` (enum): asc or desc (default: asc) - `limit` (number): Results per page (default: 25, max: 250) - `page` (number): Page number (default: 0) **Response Structure**: ```json { "tokens": [ { "key": "ethereum:0x028ec7330ff87667b6dfb0d94b954c820195336c", "name": "Yearn V3 DAI-1 yVault", "symbol": "yvDAI-1", "price": 1.0147, "decimals": 18, "address": "0x028ec7330ff87667b6dfb0d94b954c820195336c", "network": "ethereum", "platform": "yearn-v3", "liquidity": 2788610.90, "metrics": { "apy": "7.736", "volumeUsd1d": "1046.48", "volumeUsd7d": "-270069.23" }, "tokens": ["0x6b175474e89094c44da98b954eedeac495271d0f"], "reserves": ["2806126.75"], "pricePerShare": "1.021065525400067639", "totalSupply": "2748233.76", "images": ["https://...yfi.png", "https://...dai.png"] } ], "pageItems": 46, "totalItems": 46, "page": 0, "more": false } ``` ### GET /v2/account - Multichain Account Balances **Purpose**: Get token balances for an address across multiple networks **Base URL**: https://api.portals.fi/v2/account **Authentication**: Required **Query Parameters**: - `owner` (string, required): Wallet address to query - `networks` (string[], required): Networks to check (repeat param for multiple) **Response Structure**: ```json { "balances": [ { "key": "ethereum:0x0000000000000000000000000000000000000000", "name": "Ethereum", "symbol": "ETH", "price": 3492.45, "network": "ethereum", "platform": "native", "balance": 456.11, "rawBalance": "456110626955260337134", "balanceUSD": 1592943.56, "liquidity": 427391219422, "decimals": 18 } ] } ``` ### GET /v2/tokens/history - Historical Data **Purpose**: Get historical price, APY, TVL, volume data for tokens **Base URL**: https://api.portals.fi/v2/tokens/history **Authentication**: Required (Pioneer+ plan) **Query Parameters**: - `id` (string, required): Token ID in format `{network}:{address}` - `from` (number): Unix timestamp for start date (default: 1 day ago) - `resolution` (enum): 15m, 1h, 4h, 1d (default: 1d) - `page` (number): Page number for pagination (default: 0) **Response Structure**: ```json { "history": [ { "time": "2024-09-27T00:00:00.000Z", "highPrice": "1.035163810086583", "lowPrice": "1.03086199782478", "openPrice": "1.033068273524005", "closePrice": "1.0322831671539245", "liquidity": "4770742.98", "reserves": ["4774639.09"], "totalSupply": "4621544.88", "pricePerShare": "1.033126198131600023", "volume1dUsd": "37256.57", "apy": "4.358", "baseApy": "4.358" } ], "pageItems": 7, "totalItems": 7, "page": 0, "more": false } ``` ### GET /v2/tokens/transactions - Token Activity & Transactions **Purpose**: Get deposits, withdrawals, and swaps for a token (last 24h) **Base URL**: https://api.portals.fi/v2/tokens/transactions **Authentication**: Required (Pioneer+ plan) **Query Parameters**: - `id` (string, required): Token ID in format `{network}:{address}` **Response Structure**: ```json { "withdraws": [ { "amounts": [{ "key": "ethereum:0x06325...", "symbol": "steCRV", "amount": "0.029379242610574816", "amountUsd": "125.63" }], "transactionHash": "0xdb6e7f...", "time": "2024-12-17T18:08:35.000Z" } ], "deposits": [...], "swaps": [ { "inputAmount": "4.986282442671578131", "inputToken": "ethereum:0xae7ab9...", "outputAmount": "4.980060021504205258", "outputToken": "ethereum:0x000...", "transactionHash": "0x900485...", "time": "2024-12-17T22:49:47.000Z" } ] } ``` ### GET /v2/tokens/holders - Token Holders & Distribution **Purpose**: Get paginated list of token holders with balances and percentages **Base URL**: https://api.portals.fi/v2/tokens/holders **Authentication**: Required (Pioneer+ plan, Beta) **Query Parameters**: - `id` (string, required): Token ID in format `{network}:{address}` - `limit` (number): Results per page (default: 100, max: 1000) - `page` (number): Page number (default: 0) **Response Structure**: ```json { "holders": [ { "address": "0xbdfa7b7893081b35fb54027489e2bc7a38275129", "balance": 1973784.72, "rawBalance": "1973784729033927914144824", "balanceUsd": 7700460918.14, "percentage": 74.73 } ], "totalSupply": 2641205.15, "totalItems": 23402, "pageItems": 100, "page": 0, "more": true } ``` --- ## 5. Trading API Endpoints ### GET /v2/portal - Swaps & Zaps **Purpose**: Generate ready-to-execute transaction for swaps and zaps **Base URL**: https://api.portals.fi/v2/portal **Authentication**: Optional (recommended for tracking) **Required Parameters**: - `inputToken` (string): Input token ID `{network}:{address}` - `inputAmount` (string): Amount in base units (wei for ETH) - `outputToken` (string): Output token ID `{network}:{address}` - `sender` (string): Address initiating and receiving transaction **Optional Parameters**: - `partner` (string): Referral address for fee sharing and statistics - `feePercentage` (number): Fee to charge, 0-1% (default: 0.3%) - `slippageTolerancePercentage` (number): Max slippage 0.1-10% (auto if not set) - `validate` (boolean): Simulate before returning (default: true) - `permitSignature` (string): EIP-2612 permit signature for gasless approval - `permitDeadline` (string): Deadline for permit transaction **Response Structure**: ```json { "tx": { "data": "0xa2e42c65...", "to": "0xbf5a7f3629fb325e2a8453d595ab103465f75e62", "from": "0x4689cff824d63117f9c4c42f3ec0001676f00d25", "value": "100000000000000000", "gasLimit": "648902" }, "context": { "orderId": "b038c425-416e-4a59-9ef2-57e5378b98d5", "inputToken": "ethereum:0x000...", "inputAmount": "100000000000000000", "inputAmountUsd": 341.18, "outputToken": "ethereum:0x3175df...", "outputAmount": "339993670165064078339", "outputAmountUsd": 339.99, "minOutputAmount": "339143685989651418144", "minOutputAmountUsd": 339.14, "slippageTolerancePercentage": 0.3, "gasLimit": "648902", "route": ["ETH", "USDC", "crvFRAX"], "steps": [ "Swap ETH for USDC on 0x", "Transfer fee to collector", "Approve USDC", "Curve add_liquidity from USDC to crvFRAX", "Transfer output to sender" ], "feeToken": "ethereum:0xa0b86991...", "feeAmount": "1024133", "feeAmountUsd": 1.024, "partner": "0xFBD4C3D8bE6B15b7cf428Db2838bb44C0054fCd2" } } ``` ### GET /v2/portal/estimate - Simulate Transactions **Purpose**: Preview swap/zap outcomes without approvals or balances **Base URL**: https://api.portals.fi/v2/portal/estimate **Authentication**: Optional **Required Parameters**: - `inputToken` (string): Input token ID - `inputAmount` (string): Amount in base units - `outputToken` (string): Output token ID **Optional Parameters**: - `slippageTolerancePercentage` (number): Custom slippage (auto if not set) **Response Structure**: ```json { "outputToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "outputAmount": "279694107", "minOutputAmount": "278994872", "outputTokenDecimals": 6, "context": { "inputToken": "ethereum:0x000...", "inputAmount": "100000000000000000", "inputAmountUsd": 279.789, "outputAmountUsd": 279.638, "minOutputAmountUsd": 278.939, "slippageTolerancePercentage": 0.3039, "gasLimit": "295774", "simulated": true } } ``` ### GET /v2/approval - Token Approvals **Purpose**: Check approval status and generate approval transactions **Base URL**: https://api.portals.fi/v2/approval **Authentication**: Optional **Required Parameters**: - `sender` (string): Token holder address - `inputToken` (string): Token to approve (format: `{network}:{address}`) - `inputAmount` (string): Amount to approve in base units **Optional Parameters**: - `permitDeadline` (string): Deadline for permit signature **Response Structure**: ```json { "context": { "network": "ethereum", "allowance": "0", "approvalAmount": "1000000000", "shouldApprove": true, "canPermit": true, "spender": "0xbf5a7f3629fb325e2a8453d595ab103465f75e62", "target": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" }, "approve": { "data": "0x095ea7b3...", "to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "from": "0x4689cff824d63117f9c4c42f3ec0001676f00d25", "gasLimit": "43040" }, "permit": { "types": { "Permit": [...] }, "domain": { "verifyingContract": "0xa0b8...", "chainId": 1 }, "value": { "owner": "0x4689...", "spender": "0xbf5a...", "value": "1000000000" } } } ``` --- ## 6. Cross-Chain Operations ### How It Works 1. User submits ONE transaction on source chain 2. Portals router exits position/swaps on source chain 3. Automatic bridge via Axelar (1-10 minutes) 4. Automatic entry/swap on destination chain 5. Tokens arrive at recipient address ### Supported Network Pairs All combinations of: Ethereum, Arbitrum, Base, Optimism, Polygon, BSC, Avalanche ### Cross-Chain Request Example Same endpoint as single-chain, just different networks: ```bash curl 'https://api.portals.fi/v2/portal?inputToken=ethereum:0x0000000000000000000000000000000000000000&inputAmount=100000000000000000&outputToken=arbitrum:0xaf88d065e77c8cc2239327c5edb3a432268e5831&sender=0xYOUR_ADDRESS' ``` ### Cross-Chain Response Includes additional fields: - `context.expiry`: Unix timestamp when quote expires - `context.steps`: Shows "Bridge USDC to Arbitrum" step - `context.route`: Includes bridging tokens (e.g., USDC.axl) - `tx.value`: Includes bridge fees (slightly higher than input amount) ### Tracking Cross-Chain Transactions Use Axelar explorer: `https://axelarscan.io/gmp/{txHash}` Shows: - Source chain transaction status - Bridge progress - Destination chain transaction - Complete in 1-10 minutes typically --- ## 7. Code Examples ### JavaScript/TypeScript Example ```javascript import axios from 'axios'; // Get token data async function getYearnVaults() { const response = await axios.get('https://api.portals.fi/v2/tokens', { headers: { 'Authorization': 'YOUR_API_KEY' }, params: { platforms: 'yearn-v3', networks: 'ethereum', sortBy: 'liquidity', sortDirection: 'desc', limit: 10 } }); return response.data.tokens; } // Get multichain account balances async function getBalances(address, networks) { const response = await axios.get('https://api.portals.fi/v2/account', { headers: { 'Authorization': 'YOUR_API_KEY' }, params: { owner: address, networks: networks } }); return response.data.balances; } // Execute swap async function executeSwap(inputToken, inputAmount, outputToken, sender) { const response = await axios.get('https://api.portals.fi/v2/portal', { params: { inputToken, inputAmount, outputToken, sender } }); // Use response.data.tx with Viem or Ethers to execute return response.data; } // Simulate cross-chain swap async function simulateCrossChain(inputToken, inputAmount, outputToken) { const response = await axios.get('https://api.portals.fi/v2/portal/estimate', { params: { inputToken, inputAmount, outputToken } }); return response.data; } ``` ### Python Example ```python import requests # Get token data def get_yearn_vaults(): url = 'https://api.portals.fi/v2/tokens' headers = {'Authorization': 'YOUR_API_KEY'} params = { 'platforms': 'yearn-v3', 'networks': 'ethereum', 'sortBy': 'liquidity', 'sortDirection': 'desc', 'limit': 10 } response = requests.get(url, headers=headers, params=params) return response.json()['tokens'] # Get multichain account balances def get_balances(address, networks): url = 'https://api.portals.fi/v2/account' headers = {'Authorization': 'YOUR_API_KEY'} network_params = '&'.join([f'networks={n}' for n in networks]) response = requests.get(f'{url}?owner={address}&{network_params}', headers=headers) return response.json()['balances'] # Execute swap def execute_swap(input_token, input_amount, output_token, sender): url = 'https://api.portals.fi/v2/portal' params = { 'inputToken': input_token, 'inputAmount': input_amount, 'outputToken': output_token, 'sender': sender } response = requests.get(url, params=params) return response.json() ``` ### Using with Viem (Recommended) ```typescript import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains' const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum) }) // Execute Portal transaction async function sendPortalTransaction(portalResponse) { const { tx } = portalResponse; const hash = await client.sendTransaction({ to: tx.to, data: tx.data, value: BigInt(tx.value), gas: BigInt(tx.gasLimit) }) // Wait for confirmation const receipt = await client.waitForTransactionReceipt({ hash }) return receipt } ``` --- ## 8. Error Handling & Rate Limits ### HTTP Status Codes | Code | Meaning | Action | |------|---------|--------| | 200 | Success | Process response | | 400 | Bad Request | Check parameters | | 401 | Unauthorized | Verify API key | | 403 | Forbidden | Check API key permissions | | 429 | Rate Limited | Implement backoff | | 500 | Server Error | Retry with backoff | ### Rate Limits **Data Endpoints**: - Free: 100 requests/minute - Pioneer: 500 requests/minute **Trading Endpoints** (all tiers): - 100 requests/minute ### Rate Limit Headers Every response includes: - `ratelimit-limit`: Max requests per window - `ratelimit-remaining`: Requests left - `ratelimit-reset`: Seconds until reset - `ratelimit-policy`: Policy description (e.g., "500;w=60") ### Retry Strategy ```javascript async function makeRequestWithBackoff(url, params, maxAttempts = 3) { for (let attempt = 0; attempt < maxAttempts; attempt++) { try { const response = await axios.get(url, { params }); return response.data; } catch (error) { if (error.response?.status !== 429 || attempt === maxAttempts - 1) { throw error; } const resetTime = parseInt(error.response.headers['ratelimit-reset']) * 1000; await new Promise(resolve => setTimeout(resolve, resetTime)); } } } ``` ### Common Errors **Execution Reverted** - Cause: Slippage exceeded, insufficient liquidity - Solution: Increase slippageTolerancePercentage or retry later **Transfer From Failed** - Cause: Insufficient approval or balance - Solution: Check approval with /v2/approval, verify balance **Insufficient Funds** - Cause: Not enough native token for gas - Solution: Reserve native token for gas when using "MAX" **Auto Slippage Exceeded** - Cause: Required slippage > 2.5% - Solution: Set slippageTolerancePercentage manually --- ## 9. Best Practices ### Data API Best Practices 1. **Use Filters**: Apply network, platform, liquidity filters to reduce response size 2. **Implement Pagination**: Use limit and page for large datasets 3. **Cache Appropriately**: Token data updates every few minutes, cache accordingly 4. **Strategic Polling**: Poll high-activity networks more frequently 5. **Batch Requests**: Use ids parameter to fetch multiple specific tokens ### Trading API Best Practices 1. **Always Validate**: Use validate=true (default) to prevent failed transactions 2. **Check Approvals First**: Call /v2/approval before /v2/portal 3. **Use Gasless Approvals**: Prefer permit over traditional approvals when available 4. **Handle Slippage**: Use auto-slippage or set appropriate tolerance 5. **Implement Retry Logic**: Handle rate limits and transient errors 6. **Show Transaction Steps**: Display context.steps to users for transparency 7. **Monitor Quotes**: Refresh quotes if user delays execution ### Cross-Chain Best Practices 1. **Communicate Timing**: Tell users cross-chain takes 1-10 minutes 2. **Provide Tracking**: Link to Axelar explorer for bridge monitoring 3. **Check Expiry**: Respect context.expiry timestamp, refresh if expired 4. **One Transaction**: Emphasize users only submit one tx on source chain 5. **Use Auto-Slippage**: Cross-chain has more variability, let API handle it ### Security Best Practices 1. **Secure API Keys**: Never expose in client code or version control 2. **Validate Inputs**: Check token addresses and amounts client-side 3. **User Confirmation**: Show full transaction details before execution 4. **Handle Errors**: Implement comprehensive error handling 5. **Test on Testnets**: Use testnet networks before mainnet deployment --- ## 10. Resources & Tools ### Official Resources - Main Website: https://portals.fi - API Documentation: https://build.portals.fi/docs - FAQ: https://build.portals.fi/docs/faq (45+ common questions answered) - Swagger UI: https://api.portals.fi/docs (interactive testing) - Dashboard: https://build.portals.fi/dashboard (API keys, usage stats) - Explorer: https://explorer.portals.fi (visual token discovery) - OpenAPI Schema: https://api.portals.fi/docs-json ### Developer Tools - **Viem**: https://viem.sh - TypeScript Ethereum library (recommended) - **Ethers.js**: https://docs.ethers.org/v6 - Web3 library for transaction signing - **openapi-fetch**: https://github.com/drwpow/openapi-typescript - Generate typed clients - **Axios**: https://axios-http.com - HTTP client ### Documentation Pages - FAQ: https://build.portals.fi/docs/faq (Frequently Asked Questions) - Quickstart: https://build.portals.fi/docs/quickstart - Token Prices & Metrics: https://build.portals.fi/docs/getting-tokens - Multichain Account Balances: https://build.portals.fi/docs/getting-balances - Swaps & Zaps: https://build.portals.fi/docs/portal - Simulate Transactions: https://build.portals.fi/docs/estimate - Cross-Chain Swaps & Zaps: https://build.portals.fi/docs/cross-chain - Token Approvals: https://build.portals.fi/docs/token-approvals - Historical Token Data: https://build.portals.fi/docs/token-history - Token Activity & Transactions: https://build.portals.fi/docs/token-transactions - Token Holders & Distribution: https://build.portals.fi/docs/token-holders - Error Handling: https://build.portals.fi/docs/error-handling-rate-limiting - Troubleshooting: https://build.portals.fi/docs/troubleshooting ### Concept Pages - Native Tokens: https://build.portals.fi/docs/concepts/native-tokens - Basic Tokens: https://build.portals.fi/docs/concepts/basic-tokens - Platform Tokens: https://build.portals.fi/docs/concepts/lp-tokens - Platforms: https://build.portals.fi/docs/concepts/platforms - Networks: https://build.portals.fi/docs/concepts/networks - Portals Intents: https://build.portals.fi/docs/concepts/portals - Accounts: https://build.portals.fi/docs/concepts/accounts ### Support Channels - Telegram: https://t.me/PortalsFi_Official - Discord: https://discord.gg/portals - Twitter: https://twitter.com/portals_fi - Feature Requests: https://portals.canny.io/feature-requests --- ## Complete Use Case Examples ### Use Case 1: Token Price Dashboard Build a dashboard showing top DeFi tokens by liquidity: ```javascript async function buildTokenDashboard() { // Get top 50 tokens by liquidity const response = await axios.get('https://api.portals.fi/v2/tokens', { headers: { 'Authorization': 'YOUR_API_KEY' }, params: { sortBy: 'liquidity', sortDirection: 'desc', limit: 50 } }); // Display: name, symbol, price, APY, liquidity, platform return response.data.tokens.map(token => ({ name: token.name, symbol: token.symbol, price: `$${token.price.toFixed(2)}`, apy: token.metrics?.apy ? `${token.metrics.apy}%` : 'N/A', tvl: `$${(token.liquidity / 1e6).toFixed(2)}M`, platform: token.platform, network: token.network })); } ``` ### Use Case 2: Portfolio Tracker Track user balances across all chains: ```javascript async function trackPortfolio(userAddress) { const networks = ['ethereum', 'arbitrum', 'base', 'optimism', 'polygon']; const response = await axios.get('https://api.portals.fi/v2/account', { headers: { 'Authorization': 'YOUR_API_KEY' }, params: { owner: userAddress, networks: networks } }); const totalValueUSD = response.data.balances.reduce((sum, bal) => sum + bal.balanceUSD, 0); return { totalValue: totalValueUSD, balances: response.data.balances, tokenCount: response.data.balances.length }; } ``` ### Use Case 3: Yield Optimizer Find and compare highest APY opportunities: ```javascript async function findBestYield(minLiquidity = 1000000) { const platforms = ['yearn-v3', 'morpho', 'aave-v3', 'compound-v3']; const results = await Promise.all( platforms.map(platform => axios.get('https://api.portals.fi/v2/tokens', { headers: { 'Authorization': 'YOUR_API_KEY' }, params: { platforms: platform, minLiquidity, sortBy: 'apy', sortDirection: 'desc', limit: 5 } }) ) ); // Flatten and sort all results by APY const allTokens = results.flatMap(r => r.data.tokens); return allTokens.sort((a, b) => parseFloat(b.metrics?.apy || 0) - parseFloat(a.metrics?.apy || 0)); } ``` ### Use Case 4: Swap Widget Build a token swapping interface: ```javascript async function buildSwapWidget(inputToken, inputAmount, outputToken, userAddress) { // 1. Check if approval needed const approval = await axios.get('https://api.portals.fi/v2/approval', { params: { sender: userAddress, inputToken, inputAmount } }); if (approval.data.context.shouldApprove) { // Handle approval (traditional or permit) if (approval.data.context.canPermit) { // Get permit signature from user const signature = await getPermitSignature(approval.data.permit); // Pass to portal request } else { // Execute approval transaction first await executeApproval(approval.data.approve); } } // 2. Get quote const quote = await axios.get('https://api.portals.fi/v2/portal', { params: { inputToken, inputAmount, outputToken, sender: userAddress } }); // 3. Show user: outputAmount, steps, fees displayQuote(quote.data.context); // 4. Execute on user confirmation const txHash = await executeTransaction(quote.data.tx); return txHash; } ``` ### Use Case 5: Cross-Chain Yield Migration Migrate from one chain to another for better yields: ```javascript async function migrateYield(currentPosition, targetVault, userAddress) { // currentPosition: "ethereum:0x028ec..." (Yearn on Ethereum) // targetVault: "arbitrum:0x4d9079..." (Aave on Arbitrum) const quote = await axios.get('https://api.portals.fi/v2/portal', { params: { inputToken: currentPosition, inputAmount: 'USER_BALANCE', // Get from /v2/account outputToken: targetVault, sender: userAddress, slippageTolerancePercentage: 1.5 } }); // Show user: // - Exit Yearn vault on Ethereum // - Bridge to Arbitrum // - Enter Aave vault on Arbitrum // - Expected output amount // - Estimated time: 1-10 minutes console.log('Steps:', quote.data.context.steps); console.log('Output:', quote.data.context.outputAmount); console.log('Quote expires:', new Date(quote.data.context.expiry * 1000)); return quote.data.tx; } ``` --- ## Advanced Topics ### Gasless Approvals (EIP-2612 Permit) Instead of separate approval transaction: ```javascript // 1. Get permit data const approval = await axios.get('https://api.portals.fi/v2/approval', { params: { sender, inputToken, inputAmount } }); if (approval.data.context.canPermit) { // 2. Sign permit (no gas cost) const signature = await walletClient.signTypedData({ domain: approval.data.permit.domain, types: approval.data.permit.types, primaryType: 'Permit', message: approval.data.permit.value }); // 3. Include in portal request const quote = await axios.get('https://api.portals.fi/v2/portal', { params: { inputToken, inputAmount, outputToken, sender, permitSignature: signature } }); // Single transaction now includes approval! } ``` ### Fee Sharing & Revenue Earn revenue from your integration: ```javascript const quote = await axios.get('https://api.portals.fi/v2/portal', { params: { inputToken: 'ethereum:0x000...', inputAmount: '100000000000000000', outputToken: 'ethereum:0x3175df...', sender: userAddress, partner: 'YOUR_PARTNER_ADDRESS', // Required with feePercentage feePercentage: 0.5 // 0.5% fee } }); // Fee goes to YOUR_PARTNER_ADDRESS in fee token // context.feeToken, context.feeAmount, context.feeAmountUsd ``` ### Handling Slippage ```javascript // Option 1: Auto-slippage (recommended) const quote = await getPortalQuote({ validate: true }); // Uses market-based slippage calculation, max 2.5% // Option 2: Custom slippage const quote = await getPortalQuote({ slippageTolerancePercentage: 1.0, // 1% validate: true }); // Check actual slippage used console.log('Slippage:', quote.context.slippageTolerancePercentage); console.log('Min output:', quote.context.minOutputAmount); ``` --- ## Token Discovery & Filtering ### Find Specific Protocol Tokens ```bash # Get all Curve pools on Ethereum with >$1M liquidity curl 'https://api.portals.fi/v2/tokens?platforms=curve&networks=ethereum&minLiquidity=1000000&sortBy=liquidity&sortDirection=desc' \ -H 'Authorization: YOUR_API_KEY' # Get all Morpho vaults across all networks curl 'https://api.portals.fi/v2/tokens?platforms=morpho&sortBy=apy&sortDirection=desc' \ -H 'Authorization: YOUR_API_KEY' # Search for FRAX-related tokens curl 'https://api.portals.fi/v2/tokens?search=frax&sortBy=liquidity&sortDirection=desc' \ -H 'Authorization: YOUR_API_KEY' ``` ### Find High-Yield Opportunities ```bash # Tokens with >5% APY and >$500k TVL curl 'https://api.portals.fi/v2/tokens?minApy=5&minLiquidity=500000&sortBy=apy&sortDirection=desc&limit=50' \ -H 'Authorization: YOUR_API_KEY' ``` --- ## Integration Patterns ### Pattern 1: Read-Only Dashboard - Use /v2/tokens for discovery - Use /v2/account for user balances - Use /v2/tokens/history for charts - No API key exposed to frontend ### Pattern 2: Swap Interface - Frontend: Call /v2/portal/estimate for quotes - Backend: Proxy /v2/portal requests with API key - Frontend: Execute transactions with wallet - Track with partner address ### Pattern 3: Yield Aggregator - Backend: Fetch all vault data via /v2/tokens - Compare APYs, liquidity, risk metrics - Generate zap transactions via /v2/portal - Monitor positions via /v2/account ### Pattern 4: Trading Bot - Poll /v2/tokens for price updates - Use /v2/tokens/history for strategy analysis - Execute via /v2/portal with custom slippage - Track performance via /v2/account --- ## Troubleshooting Common Issues ### Issue: "execution reverted" - Check approval is granted - Verify sufficient balance (including gas) - Increase slippage tolerance - Ensure quote hasn't expired (cross-chain) ### Issue: Rate limited (429) - Implement exponential backoff - Cache responses appropriately - Batch requests where possible - Upgrade to Pioneer plan if needed ### Issue: Invalid token address - Use correct format: `{network}:{address}` - Verify token exists on specified network - Check address is checksummed correctly - Use /v2/tokens to search for correct address ### Issue: Cross-chain quote expired - Check context.expiry timestamp - Refresh quote before execution - Cross-chain quotes typically valid for 20-30 minutes - Don't cache cross-chain quotes --- ## API Versioning & Stability - Current version: v2 - Versioned endpoints ensure backward compatibility - Breaking changes announced with 3+ months notice - Deprecation warnings in response headers - Changelog: Monitor for updates --- ## Performance Optimization ### Reduce API Calls 1. Cache token data (updates every 2-5 minutes) 2. Use pagination efficiently 3. Filter at API level, not client-side 4. Batch token fetches with ids parameter ### Optimize Response Size 1. Request only needed networks 2. Use appropriate page limits 3. Apply liquidity/APY filters 4. Sort server-side ### Minimize Latency 1. Use CDN-cached endpoints where possible 2. Implement request debouncing 3. Parallel requests for independent data 4. Connection pooling for high-volume apps --- ## Pricing & Plans ### Free Plan - 100 requests/minute - Access to /v2/tokens - Access to /v2/account - Access to /v2/portal (no key needed) - Perfect for: Small apps, prototypes, personal projects ### Pioneer Plan ($99/month) - 500 requests/minute - All Free plan features - Priority support - Perfect for: Production apps, higher traffic ### Pioneer+ Plan - All Pioneer features - Access to /v2/tokens/history - Access to /v2/tokens/transactions - Access to /v2/tokens/holders (beta) - Perfect for: Analytics platforms, trading bots, yield optimizers ### Enterprise - Custom rate limits - Dedicated support - SLA guarantees - White-label options - Contact: https://contact.portals.fi --- ## Quick Reference ### Token Amount Conversion ```javascript // Human-readable to base units (wei) const amount = ethers.parseUnits("1.5", 18); // "1500000000000000000" // Base units to human-readable const formatted = ethers.formatUnits("1500000000000000000", 18); // "1.5" ``` ### Network Identifiers - ethereum, arbitrum, base, optimism, polygon, bsc, avalanche, sonic, hyperevm ### Common Platform Identifiers - yearn-v3, morpho, aave-v3, curve, uniswap-v3, balancerv2, convex, beefy, pendle, velodrome, etherfi, lido, compound-v3 ### Rate Limit Header Names - ratelimit-limit - ratelimit-remaining - ratelimit-reset - ratelimit-policy - retry-after --- **Version**: December 2025 **Full Documentation**: https://build.portals.fi/docs **FAQ**: https://build.portals.fi/docs/faq (45+ common questions answered) **API Status**: https://status.portals.fi (if available) **Support**: Telegram (https://t.me/PortalsFi_Official) or Discord (https://discord.gg/portals) This file is optimized for Large Language Models to understand and help developers integrate with the Portals API.