# Add Address Book Entries Source: https://docs.octav.fi/api-reference/address-book/add-address-book-entries /openapi.json post /addressbook Add one or more addresses to the address book. An address already in the book keeps its existing label — this endpoint never overwrites one. Only addresses not already in the book count against the account's non-paid address quota. Returns the entire address book after the add, not just the entries sent. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # List Address Book Source: https://docs.octav.fi/api-reference/address-book/list-address-book /openapi.json get /addressbook List every address saved to the account behind your API key. Entries are keyed by address — the response carries no id field. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Remove Address Book Entry Source: https://docs.octav.fi/api-reference/address-book/remove-address-book-entry /openapi.json delete /addressbook/{address} Remove one entry from the address book. Paid and wallet-authorized entries cannot be removed through the API and return 409 ADDRESSBOOK_ENTRY_NOT_DELETABLE. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Rename Address Book Entry Source: https://docs.octav.fi/api-reference/address-book/rename-address-book-entry /openapi.json patch /addressbook/{address} Change the label on one address book entry. An address that is not in the book returns 404 ADDRESSBOOK_ENTRY_NOT_FOUND. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Agent Chains (x402) Source: https://docs.octav.fi/api-reference/agent-x402/agent-chains-x402 /openapi.json get /agent/chains List of supported blockchain networks. Paid per call via the x402 payment protocol instead of an API key. An unpaid request returns 402 with a base64 `payment-required` header carrying the payment challenge (0.025 USDC on Base, eip155:8453); an x402-capable client settles it and retries. **The API-key REST API is the default way to use Octav.** Use these only when the caller explicitly wants pay-per-call, or is an autonomous agent holding a wallet but no API key. There is no /agent/transactions — transaction history requires an API key. See [Agent Endpoints (x402)](https://docs.octav.fi/api/endpoints/agent-x402). # Agent Nav (x402) Source: https://docs.octav.fi/api-reference/agent-x402/agent-nav-x402 /openapi.json get /agent/nav Net Asset Value of a given address. Paid per call via the x402 payment protocol instead of an API key. An unpaid request returns 402 with a base64 `payment-required` header carrying the payment challenge (0.025 USDC on Base, eip155:8453); an x402-capable client settles it and retries. **The API-key REST API is the default way to use Octav.** Use these only when the caller explicitly wants pay-per-call, or is an autonomous agent holding a wallet but no API key. There is no /agent/transactions — transaction history requires an API key. See [Agent Endpoints (x402)](https://docs.octav.fi/api/endpoints/agent-x402). # Agent Portfolio (x402) Source: https://docs.octav.fi/api-reference/agent-x402/agent-portfolio-x402 /openapi.json get /agent/portfolio Wallet and protocol holdings of a given address. Paid per call via the x402 payment protocol instead of an API key. An unpaid request returns 402 with a base64 `payment-required` header carrying the payment challenge (0.025 USDC on Base, eip155:8453); an x402-capable client settles it and retries. **The API-key REST API is the default way to use Octav.** Use these only when the caller explicitly wants pay-per-call, or is an autonomous agent holding a wallet but no API key. There is no /agent/transactions — transaction history requires an API key. See [Agent Endpoints (x402)](https://docs.octav.fi/api/endpoints/agent-x402). # Agent Status (x402) Source: https://docs.octav.fi/api-reference/agent-x402/agent-status-x402 /openapi.json get /agent/status Sync status and data freshness for a given address. Paid per call via the x402 payment protocol instead of an API key. An unpaid request returns 402 with a base64 `payment-required` header carrying the payment challenge (0.025 USDC on Base, eip155:8453); an x402-capable client settles it and retries. **The API-key REST API is the default way to use Octav.** Use these only when the caller explicitly wants pay-per-call, or is an autonomous agent holding a wallet but no API key. There is no /agent/transactions — transaction history requires an API key. See [Agent Endpoints (x402)](https://docs.octav.fi/api/endpoints/agent-x402). # Agent Wallet (x402) Source: https://docs.octav.fi/api-reference/agent-x402/agent-wallet-x402 /openapi.json get /agent/wallet Wallet holdings of a given address, excluding DeFi protocol positions. Paid per call via the x402 payment protocol instead of an API key. An unpaid request returns 402 with a base64 `payment-required` header carrying the payment challenge (0.025 USDC on Base, eip155:8453); an x402-capable client settles it and retries. **The API-key REST API is the default way to use Octav.** Use these only when the caller explicitly wants pay-per-call, or is an autonomous agent holding a wallet but no API key. There is no /agent/transactions — transaction history requires an API key. See [Agent Endpoints (x402)](https://docs.octav.fi/api/endpoints/agent-x402). # Airdrop Source: https://docs.octav.fi/api-reference/airdrops/airdrop /openapi.json get /airdrop Check airdrop eligibility (Solana only). **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Token Approvals Source: https://docs.octav.fi/api-reference/approvals/token-approvals /openapi.json get /approvals/{chain} Retrieve ERC-20 token approval (allowance) records for a wallet on a given chain. **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Deposits by Index Source: https://docs.octav.fi/api-reference/beacon-validators/validator-deposits-by-index /openapi.json get /beacon/validators/deposits/index/{index} Paginated list of deposits made to a validator by index. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Deposits by Pubkey Source: https://docs.octav.fi/api-reference/beacon-validators/validator-deposits-by-pubkey /openapi.json get /beacon/validators/deposits/pubkey/{pubkey} Paginated list of deposits made to a validator by BLS pubkey. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Details by Index Source: https://docs.octav.fi/api-reference/beacon-validators/validator-details-by-index /openapi.json get /beacon/validators/details/index/{index} Get current state, lifecycle epochs, balance, and withdrawal credentials for a single validator by its index. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Details by Pubkey Source: https://docs.octav.fi/api-reference/beacon-validators/validator-details-by-pubkey /openapi.json get /beacon/validators/details/pubkey/{pubkey} Get current state, lifecycle epochs, balance, and withdrawal credentials for a single validator by its BLS pubkey. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Rewards by Index Source: https://docs.octav.fi/api-reference/beacon-validators/validator-rewards-by-index /openapi.json get /beacon/validators/rewards/index/{index} Paginated reward buckets aggregated by epoch, day, week, or month for a validator identified by index. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Rewards by Pubkey Source: https://docs.octav.fi/api-reference/beacon-validators/validator-rewards-by-pubkey /openapi.json get /beacon/validators/rewards/pubkey/{pubkey} Paginated reward buckets aggregated by epoch, day, week, or month for a validator identified by BLS pubkey. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Withdrawals by Index Source: https://docs.octav.fi/api-reference/beacon-validators/validator-withdrawals-by-index /openapi.json get /beacon/validators/withdrawals/index/{index} Paginated list of withdrawals processed for a validator by index. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Validator Withdrawals by Pubkey Source: https://docs.octav.fi/api-reference/beacon-validators/validator-withdrawals-by-pubkey /openapi.json get /beacon/validators/withdrawals/pubkey/{pubkey} Paginated list of withdrawals processed for a validator by BLS pubkey. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Add Address to Bundle Source: https://docs.octav.fi/api-reference/bundles/add-address-to-bundle /openapi.json post /bundles/{bundleId}/addresses Add one address to a bundle. The address must already be in the address book. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Create Bundle Source: https://docs.octav.fi/api-reference/bundles/create-bundle /openapi.json post /bundles Create a bundle over addresses already saved in the address book. A bundle can never be created empty, names must be unique within the account (case-sensitive), and no two bundles may hold exactly the same members. Bundles created through the API are always private. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Delete Bundle Source: https://docs.octav.fi/api-reference/bundles/delete-bundle /openapi.json delete /bundles/{bundleId} Delete a bundle. The bundle's addresses stay in the address book. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Get Bundle Source: https://docs.octav.fi/api-reference/bundles/get-bundle /openapi.json get /bundles/{bundleId} Fetch a single bundle by id. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # List Bundles Source: https://docs.octav.fi/api-reference/bundles/list-bundles /openapi.json get /bundles List every bundle belonging to the account behind your API key. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Remove Address from Bundle Source: https://docs.octav.fi/api-reference/bundles/remove-address-from-bundle /openapi.json delete /bundles/{bundleId}/addresses/{address} Remove one address from a bundle. Returns the updated bundle with status 200, not 204. Removing the last remaining member is rejected with 422 BUNDLE_EMPTY; delete the bundle instead. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Rename Bundle Source: https://docs.octav.fi/api-reference/bundles/rename-bundle /openapi.json patch /bundles/{bundleId} Rename a bundle. Names are unique per account and matched case-sensitively; a collision returns 409 BUNDLE_NAME_TAKEN. **Cost:** 1 credit per call. Automatically refunded on 4xx/5xx responses. **Access:** Requires portfolio entitlement — the same flag that unlocks `/v1/portfolio`. **Rate limit:** 360 req/min (shared with portfolio bucket) **Get your API key:** [Dev Portal](https://data.octav.fi) # Chains Source: https://docs.octav.fi/api-reference/chains/chains /openapi.json get /chains Get all supported blockchain networks. **Cost:** Free (0 credits) **Get your API key:** [Dev Portal](https://data.octav.fi) # Protocols Source: https://docs.octav.fi/api-reference/chains/protocols /openapi.json get /chains/{chainKey}/protocols List DeFi protocols available on a specific chain. **Cost:** Free (0 credits) **Get your API key:** [Dev Portal](https://data.octav.fi) # Contract Protocol Source: https://docs.octav.fi/api-reference/contract-protocol/contract-protocol /openapi.json get /contract-protocol Look up which DeFi protocol a contract address belongs to. Provide a `chain` to resolve a single protocol on that chain, or omit it to search by address across all chains. **Cost:** 5 credits per lookup. The credits are refunded if no protocol is found (404). **Get your API key:** [Dev Portal](https://data.octav.fi) # Credits Source: https://docs.octav.fi/api-reference/credits/credits /openapi.json get /credits Check your API credit balance. **Cost:** Free (0 credits) **Get your API key:** [Dev Portal](https://data.octav.fi) # Nav Source: https://docs.octav.fi/api-reference/nav/nav /openapi.json get /nav Retrieve Net Asset Value of an address. **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Historical Portfolio Source: https://docs.octav.fi/api-reference/portfolio/historical-portfolio /openapi.json get /historical Retrieve portfolio snapshots from specific dates. **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Portfolio Source: https://docs.octav.fi/api-reference/portfolio/portfolio /openapi.json get /portfolio Retrieve portfolio holdings across wallets and DeFi protocols. **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Portfolio At Block Source: https://docs.octav.fi/api-reference/portfolio/portfolio-at-block /openapi.json get /portfolio/at-block Retrieve a single EVM address's portfolio valued at a specific historical block. Requires the Portfolio at Block add-on to be enabled for your API key. **Cost:** Set monthly fee under the Portfolio at Block add-on, plus 1 credit per call **Rate limit:** 100 requests/min on a dedicated bucket **Get your API key:** [Dev Portal](https://data.octav.fi) # Status Source: https://docs.octav.fi/api-reference/status/status /openapi.json get /status Check synchronization status and data freshness for addresses. **Cost:** Free (0 credits) **Get your API key:** [Dev Portal](https://data.octav.fi) # Sync Transactions Source: https://docs.octav.fi/api-reference/sync/sync-transactions /openapi.json post /sync-transactions Manually trigger transaction synchronization for an address. **Cost:** 1 credit + 1 credit per 250 transactions indexed **Get your API key:** [Dev Portal](https://data.octav.fi) # Token Overview Source: https://docs.octav.fi/api-reference/tokens/token-overview /openapi.json get /token-overview Get detailed token breakdown by protocol (PRO only). **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Transactions Source: https://docs.octav.fi/api-reference/transactions/transactions /openapi.json get /transactions Query transaction history with powerful filtering and search. **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # List Virtual Users Source: https://docs.octav.fi/api-reference/virtual-users/list-virtual-users /openapi.json get /virtual-users List all virtual users belonging to the authenticated API user. Requires a Pro subscription — virtual users are created in the [Octav Pro](https://pro.octav.fi) app. **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Virtual Users Portfolio Source: https://docs.octav.fi/api-reference/virtual-users/virtual-users-portfolio /openapi.json get /virtual-users/portfolio Fetch portfolios for one or more virtual users. Works identically to GET /portfolio but uses virtual user addresses. Requires a Pro subscription. **Cost:** 1 credit per virtual user address **Get your API key:** [Dev Portal](https://data.octav.fi) # Wallet Source: https://docs.octav.fi/api-reference/wallet/wallet /openapi.json get /wallet Get wallet balance and basic information. **Cost:** 1 credit per call **Get your API key:** [Dev Portal](https://data.octav.fi) # Building AI Agents Source: https://docs.octav.fi/api/ai-development/building-agents Create autonomous monitoring and analytics agents with Octav API AI agents are autonomous programs that independently monitor portfolios, track transactions, and provide insights without human intervention. This guide shows you how to build production-ready agents using the Octav API. **What is an AI Agent?** - An autonomous program that makes decisions and takes actions based on blockchain data without constant human oversight *** ## Agent Types You Can Build Track portfolio changes and send alerts * Balance threshold notifications * Significant movement alerts * Daily/weekly summaries * Performance tracking Monitor blockchain activity * New transaction alerts * Large transfer detection * DeFi interaction tracking * Gas fee optimization Analyze DeFi positions * Track yield rates * Compare protocols * Alert on better opportunities * Risk assessment Automated tax tracking * Transaction categorization * Income/expense calculation * Quarterly reports * Export generation *** ## Basic Portfolio Monitor Agent Here's a complete, production-ready portfolio monitoring agent: ```python Python Agent theme={null} import requests import time from datetime import datetime from typing import Dict, List, Optional import os class PortfolioMonitorAgent: """Autonomous agent that monitors crypto portfolios using Octav API""" def __init__(self, api_key: str, check_interval: int = 300): self.api_key = api_key self.base_url = 'https://api.octav.fi' self.check_interval = check_interval # seconds self.headers = {'Authorization': f'Bearer {api_key}'} self.last_values: Dict[str, float] = {} def get_portfolio(self, address: str) -> Optional[Dict]: """Fetch current portfolio data""" try: response = requests.get( f'{self.base_url}/v1/portfolio?addresses={address}', headers=self.headers, timeout=30 ) response.raise_for_status() return response.json()[0] except requests.exceptions.RequestException as e: print(f"Error fetching portfolio: {e}") return None def check_balance_threshold(self, address: str, min_balance: float = 1000): """Alert if portfolio falls below threshold""" portfolio = self.get_portfolio(address) if not portfolio: return current_value = float(portfolio['networth']) if current_value < min_balance: self.send_alert( f"⚠️ Low Balance Alert\n" f"Address: {address[:10]}...\n" f"Current: ${current_value:,.2f}\n" f"Threshold: ${min_balance:,.2f}" ) def check_significant_change(self, address: str, threshold_pct: float = 5.0): """Alert on significant portfolio value changes""" portfolio = self.get_portfolio(address) if not portfolio: return current_value = float(portfolio['networth']) last_value = self.last_values.get(address) if last_value: change = current_value - last_value change_pct = (change / last_value) * 100 if abs(change_pct) >= threshold_pct: direction = "📈" if change > 0 else "📉" self.send_alert( f"{direction} Significant Change Detected\n" f"Address: {address[:10]}...\n" f"Change: {change_pct:+.2f}%\n" f"From: ${last_value:,.2f}\n" f"To: ${current_value:,.2f}" ) self.last_values[address] = current_value def generate_daily_summary(self, address: str): """Generate and send daily portfolio summary""" portfolio = self.get_portfolio(address) if not portfolio: return # Calculate chain distribution chains_summary = [] for chain_key, chain in portfolio['chains'].items(): chains_summary.append( f" {chain['name']}: ${float(chain['value']):,.2f}" ) # Calculate protocol distribution protocols_summary = [] for protocol_key, protocol in portfolio['assetByProtocols'].items(): protocols_summary.append( f" {protocol['name']}: ${float(protocol['value']):,.2f}" ) summary = f""" 📊 Daily Portfolio Summary Address: {address[:10]}... Total Value: ${float(portfolio['networth']):,.2f} Chains: {chr(10).join(chains_summary)} Protocols: {chr(10).join(protocols_summary)} Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} """ self.send_alert(summary.strip()) def monitor(self, addresses: List[str], config: Dict): """Main monitoring loop""" print(f"Starting portfolio monitor for {len(addresses)} addresses") print(f"Check interval: {self.check_interval}s") while True: try: for address in addresses: # Run configured checks if config.get('check_threshold'): self.check_balance_threshold( address, config.get('min_balance', 1000) ) if config.get('check_changes'): self.check_significant_change( address, config.get('change_threshold', 5.0) ) # Daily summary at midnight if config.get('daily_summary'): now = datetime.now() if now.hour == 0 and now.minute < 5: self.generate_daily_summary(address) # Respect rate limits time.sleep(2) time.sleep(self.check_interval) except KeyboardInterrupt: print("\nMonitoring stopped by user") break except Exception as e: print(f"Error in monitoring loop: {e}") time.sleep(60) # Wait before retrying def send_alert(self, message: str): """Send alert notification (implement your preferred method)""" print(f"\n{'='*50}") print(message) print(f"{'='*50}\n") # TODO: Implement your notification method: # - Email via SendGrid/Mailgun # - SMS via Twilio # - Slack/Discord webhook # - Telegram bot # - Push notification service # Usage Example if __name__ == "__main__": agent = PortfolioMonitorAgent( api_key=os.getenv('OCTAV_API_KEY'), check_interval=300 # Check every 5 minutes ) # Configure monitoring config = { 'check_threshold': True, 'min_balance': 5000, 'check_changes': True, 'change_threshold': 3.0, # Alert on 3%+ changes 'daily_summary': True } # Start monitoring addresses = [ '0x123...', # Your addresses '0x456...', ] agent.monitor(addresses, config) ``` ```javascript JavaScript Agent theme={null} const fetch = require('node-fetch'); class PortfolioMonitorAgent { constructor(apiKey, checkInterval = 300000) { this.apiKey = apiKey; this.baseUrl = 'https://api.octav.fi'; this.checkInterval = checkInterval; // milliseconds this.lastValues = new Map(); } async getPortfolio(address) { try { const response = await fetch( `${this.baseUrl}/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${this.apiKey}` } } ); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); return data[0]; } catch (error) { console.error('Error fetching portfolio:', error); return null; } } async checkBalanceThreshold(address, minBalance = 1000) { const portfolio = await this.getPortfolio(address); if (!portfolio) return; const currentValue = parseFloat(portfolio.networth); if (currentValue < minBalance) { this.sendAlert( `⚠️ Low Balance Alert\n` + `Address: ${address.substring(0, 10)}...\n` + `Current: $${currentValue.toFixed(2)}\n` + `Threshold: $${minBalance.toFixed(2)}` ); } } async checkSignificantChange(address, thresholdPct = 5.0) { const portfolio = await this.getPortfolio(address); if (!portfolio) return; const currentValue = parseFloat(portfolio.networth); const lastValue = this.lastValues.get(address); if (lastValue) { const change = currentValue - lastValue; const changePct = (change / lastValue) * 100; if (Math.abs(changePct) >= thresholdPct) { const direction = change > 0 ? '📈' : '📉'; this.sendAlert( `${direction} Significant Change Detected\n` + `Address: ${address.substring(0, 10)}...\n` + `Change: ${changePct >= 0 ? '+' : ''}${changePct.toFixed(2)}%\n` + `From: $${lastValue.toFixed(2)}\n` + `To: $${currentValue.toFixed(2)}` ); } } this.lastValues.set(address, currentValue); } async generateDailySummary(address) { const portfolio = await this.getPortfolio(address); if (!portfolio) return; // Chain distribution const chainsSummary = Object.values(portfolio.chains) .map(chain => ` ${chain.name}: $${parseFloat(chain.value).toFixed(2)}`) .join('\n'); // Protocol distribution const protocolsSummary = Object.values(portfolio.assetByProtocols) .map(protocol => ` ${protocol.name}: $${parseFloat(protocol.value).toFixed(2)}`) .join('\n'); const summary = ` 📊 Daily Portfolio Summary Address: ${address.substring(0, 10)}... Total Value: $${parseFloat(portfolio.networth).toFixed(2)} Chains: ${chainsSummary} Protocols: ${protocolsSummary} Generated: ${new Date().toLocaleString()} `.trim(); this.sendAlert(summary); } async monitor(addresses, config) { console.log(`Starting portfolio monitor for ${addresses.length} addresses`); console.log(`Check interval: ${this.checkInterval}ms`); const checkLoop = async () => { for (const address of addresses) { try { if (config.checkThreshold) { await this.checkBalanceThreshold(address, config.minBalance); } if (config.checkChanges) { await this.checkSignificantChange(address, config.changeThreshold); } // Daily summary at midnight if (config.dailySummary) { const now = new Date(); if (now.getHours() === 0 && now.getMinutes() < 5) { await this.generateDailySummary(address); } } // Rate limiting await new Promise(resolve => setTimeout(resolve, 2000)); } catch (error) { console.error(`Error monitoring ${address}:`, error); } } }; // Initial check await checkLoop(); // Set up interval setInterval(checkLoop, this.checkInterval); } sendAlert(message) { console.log('\n' + '='.repeat(50)); console.log(message); console.log('='.repeat(50) + '\n'); // TODO: Implement your notification method } } // Usage const agent = new PortfolioMonitorAgent( process.env.OCTAV_API_KEY, 300000 // 5 minutes ); const config = { checkThreshold: true, minBalance: 5000, checkChanges: true, changeThreshold: 3.0, dailySummary: true }; const addresses = ['0x123...', '0x456...']; agent.monitor(addresses, config); ``` *** ## Transaction Monitoring Agent Monitor and categorize new transactions: ```python theme={null} class TransactionMonitorAgent: """Monitor new transactions and categorize them""" def __init__(self, api_key: str): self.api_key = api_key self.base_url = 'https://api.octav.fi' self.headers = {'Authorization': f'Bearer {api_key}'} self.seen_hashes = set() def get_transactions(self, address: str, limit: int = 50) -> List[Dict]: """Fetch recent transactions""" try: response = requests.get( f'{self.base_url}/v1/transactions?addresses={address}&limit={limit}&sort=DESC', headers=self.headers ) response.raise_for_status() return response.json() except Exception as e: print(f"Error fetching transactions: {e}") return [] def check_new_transactions(self, address: str): """Check for and process new transactions""" transactions = self.get_transactions(address) new_txs = [ tx for tx in transactions if tx['hash'] not in self.seen_hashes ] if new_txs: for tx in new_txs: self.process_transaction(tx, address) self.seen_hashes.add(tx['hash']) def process_transaction(self, tx: Dict, address: str): """Process and categorize a transaction""" tx_type = tx['txType'] value = sum(float(asset.get('value', 0)) for asset in tx.get('assets', [])) # Alert on large transactions if value > 1000: self.send_alert( f"💰 Large Transaction Detected\n" f"Type: {tx_type}\n" f"Value: ${value:,.2f}\n" f"Hash: {tx['hash'][:16]}...\n" f"Chain: {tx['chainKey']}" ) # Alert on specific transaction types if tx_type in ['TRANSFEROUT', 'SWAP']: self.send_alert( f"📤 Outgoing Transaction\n" f"Type: {tx_type}\n" f"Value: ${value:,.2f}" ) elif tx_type in ['CLAIM', 'AIRDROP']: self.send_alert( f"🎁 Reward Claimed\n" f"Type: {tx_type}\n" f"Value: ${value:,.2f}" ) ``` *** ## Adding Notification Channels Implement real notifications using popular services: ```python theme={null} import os from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail def send_email_alert(self, message: str): """Send email notification via SendGrid""" email_message = Mail( from_email='alerts@yourdomain.com', to_emails='your@email.com', subject='Portfolio Alert', plain_text_content=message ) try: sg = SendGridAPIClient(os.getenv('SENDGRID_API_KEY')) response = sg.send(email_message) print(f"Email sent: {response.status_code}") except Exception as e: print(f"Error sending email: {e}") ``` ```python theme={null} import requests def send_telegram_alert(self, message: str): """Send notification via Telegram bot""" bot_token = os.getenv('TELEGRAM_BOT_TOKEN') chat_id = os.getenv('TELEGRAM_CHAT_ID') url = f"https://api.telegram.org/bot{bot_token}/sendMessage" try: response = requests.post(url, json={ 'chat_id': chat_id, 'text': message, 'parse_mode': 'Markdown' }) response.raise_for_status() except Exception as e: print(f"Error sending Telegram message: {e}") ``` ```python theme={null} import requests def send_discord_alert(self, message: str): """Send notification via Discord webhook""" webhook_url = os.getenv('DISCORD_WEBHOOK_URL') try: response = requests.post(webhook_url, json={ 'content': message }) response.raise_for_status() except Exception as e: print(f"Error sending Discord message: {e}") ``` ```python theme={null} import requests def send_slack_alert(self, message: str): """Send notification via Slack webhook""" webhook_url = os.getenv('SLACK_WEBHOOK_URL') try: response = requests.post(webhook_url, json={ 'text': message }) response.raise_for_status() except Exception as e: print(f"Error sending Slack message: {e}") ``` *** ## Deployment Options **AWS Lambda, Google Cloud Functions, Vercel Functions** Perfect for scheduled monitoring: ```python theme={null} # handler.py - AWS Lambda example import json from portfolio_monitor import PortfolioMonitorAgent def lambda_handler(event, context): agent = PortfolioMonitorAgent(os.getenv('OCTAV_API_KEY')) addresses = event.get('addresses', []) for address in addresses: agent.check_balance_threshold(address, 1000) agent.check_significant_change(address, 5.0) return { 'statusCode': 200, 'body': json.dumps('Monitoring complete') } ``` Schedule with CloudWatch Events (every 5 minutes) **Run agent as a container** ```dockerfile theme={null} FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY agent.py . CMD ["python", "agent.py"] ``` Deploy to: * AWS ECS/Fargate * Google Cloud Run * Digital Ocean App Platform * Fly.io **Traditional server deployment** Use systemd service: ```ini theme={null} # /etc/systemd/system/portfolio-monitor.service [Unit] Description=Portfolio Monitor Agent After=network.target [Service] Type=simple User=ubuntu WorkingDirectory=/home/ubuntu/portfolio-monitor ExecStart=/usr/bin/python3 agent.py Restart=always RestartSec=10 Environment="OCTAV_API_KEY=your_key_here" [Install] WantedBy=multi-user.target ``` Start with: `systemctl start portfolio-monitor` *** ## Best Practices **Respect Octav's 360 requests/minute limit:** ```python theme={null} from time import sleep from collections import deque from datetime import datetime, timedelta class RateLimiter: def __init__(self, max_requests=360, window=60): self.max_requests = max_requests self.window = window self.requests = deque() def wait_if_needed(self): now = datetime.now() cutoff = now - timedelta(seconds=self.window) # Remove old requests while self.requests and self.requests[0] < cutoff: self.requests.popleft() # Check limit if len(self.requests) >= self.max_requests: sleep_time = (self.requests[0] + timedelta(seconds=self.window) - now).total_seconds() if sleep_time > 0: sleep(sleep_time) self.requests.append(now) ``` **Implement robust error handling:** ```python theme={null} import time from requests.exceptions import RequestException def fetch_with_retry(self, url, max_retries=3): """Fetch with exponential backoff""" for attempt in range(max_retries): try: response = requests.get(url, headers=self.headers, timeout=30) response.raise_for_status() return response.json() except RequestException as e: if attempt == max_retries - 1: raise wait_time = 2 ** attempt print(f"Retry {attempt + 1}/{max_retries} in {wait_time}s") time.sleep(wait_time) ``` **Monitor API credit usage:** ```python theme={null} def check_credits(self): """Check remaining credits before heavy operations""" response = requests.get( f'{self.base_url}/v1/credits', headers=self.headers ) credits = response.json() if credits < 100: self.send_alert(f"⚠️ Low API credits: {credits} remaining") return credits ``` **Implement comprehensive logging:** ```python theme={null} import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('agent.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) logger.info(f"Monitoring {len(addresses)} addresses") ``` *** ## Next Steps Ready-to-use prompts for AI coding Integrate with ChatGPT and Claude Complete API documentation Understand API costs # Crypto Portfolio CLI Source: https://docs.octav.fi/api/ai-development/cli Query portfolio data, transactions, net worth and historical snapshots from your terminal or an AI agent workflow. The official Octav CLI lets you interact with the Octav API directly from your terminal. Built in Rust for speed, it outputs structured JSON — making it ideal for shell scripts, CI pipelines, and AI agent tool-use. **Quick Install** — One command to get started: ```bash theme={null} curl -sSf https://raw.githubusercontent.com/Octav-Labs/octav-cli/main/install.sh | sh ``` *** ## Why Use the CLI? Structured JSON output that AI agents can parse and act on — no SDK or HTTP client needed Pipe portfolio data into jq, scripts, cron jobs, or monitoring systems Written in Rust — single binary, no runtime dependencies, instant startup Built-in support for the x402 payment protocol — AI agents can pay per request without API keys *** ## Installation Works on macOS and Linux: ```bash theme={null} curl -sSf https://raw.githubusercontent.com/Octav-Labs/octav-cli/main/install.sh | sh ``` If you have Rust installed: ```bash theme={null} cargo install octav ``` Clone and build: ```bash theme={null} git clone https://github.com/Octav-Labs/octav-cli.git cd octav-cli cargo build --release cp target/release/octav /usr/local/bin/ ``` *** ## Authentication Store your API key once and it's used automatically for all commands. ```bash theme={null} # Store your API key (get one at https://data.octav.fi) octav auth set-key YOUR_API_KEY # Verify it's set octav auth show ``` The API key is resolved in priority order: 1. `--api-key` flag (highest precedence) 2. `OCTAV_API_KEY` environment variable 3. `~/.octav/config.json` config file For AI agent workflows, set the `OCTAV_API_KEY` environment variable so the agent doesn't need to manage config files. *** ## Commands ### Portfolio **Full portfolio including DeFi positions across all chains.** ```bash theme={null} octav portfolio get --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit per address **Wallet token balances only (excludes DeFi protocol positions).** ```bash theme={null} octav portfolio wallet --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit per address **Net asset value in a specified currency.** ```bash theme={null} octav portfolio nav --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 --currency EUR ``` Supported fiat currencies: `USD` (default), `EUR`, `CAD`, `AED`, `CHF`, `SGD`. Crypto: `ETH`, `SOL`, `cbBTC`, `EURC`, `BNB`. **Cost:** 1 credit per address **Aggregated token distribution across all chains for a specific date.** ```bash theme={null} octav portfolio token-overview \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --date 2024-06-01 ``` **Cost:** 1 credit per address ### Transactions **Query transaction history with filtering and pagination.** ```bash theme={null} octav transactions get \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --chain ethereum \ --type swap \ --start-date 2024-01-01 \ --end-date 2024-06-30 \ --limit 100 ``` | Flag | Description | Default | | -------------- | -------------------------- | ------- | | `--chain` | Filter by chain | all | | `--type` | Filter by transaction type | all | | `--start-date` | Start date (YYYY-MM-DD) | — | | `--end-date` | End date (YYYY-MM-DD) | — | | `--offset` | Pagination offset | `0` | | `--limit` | Results per page (max 250) | `50` | **Cost:** 1 credit per address **Trigger manual transaction synchronization.** ```bash theme={null} octav transactions sync --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit per address ### Historical **Portfolio snapshot for a specific date.** ```bash theme={null} octav historical get \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --date 2024-01-01 ``` **Cost:** 1 credit per address **Subscribe to automatic daily portfolio snapshots.** ```bash theme={null} octav historical subscribe-snapshot \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --description "Main wallet daily snapshot" ``` **Cost:** 1 credit per address ### Metadata & Specialized **Check sync status for addresses.** ```bash theme={null} octav status --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** Free **Check API credit balance.** ```bash theme={null} octav credits ``` **Cost:** Free **Check Solana airdrop eligibility.** ```bash theme={null} octav airdrop --address 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU ``` **Cost:** 1 credit **Get Polymarket prediction market positions.** ```bash theme={null} octav polymarket --address 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit ### Agent Commands (x402) For AI agents that pay per request using the [x402 payment protocol](https://www.x402.org/) — no API key required. Use these only when the user explicitly asks for x402, or when the agent has a funded wallet and no API key — otherwise use the API-key commands above. Only `wallet`, `portfolio`, `nav`, `status`, and `chains` are available over x402; everything else, including **transaction history**, needs an API key. See [Agent Endpoints (x402)](/api/endpoints/agent-x402). **Wallet holdings via x402 payment.** ```bash theme={null} octav agent wallet --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Full portfolio via x402 payment.** ```bash theme={null} octav agent portfolio --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` *** ## Using with AI Agents The CLI outputs structured JSON, making it a natural fit for AI agent tool-use. Agents can invoke `octav` commands via shell execution and parse the JSON response directly. ### Example: Claude Code / Cursor Agent ```text theme={null} "Check the portfolio value for 0xABC... using the octav CLI, then analyze the DeFi positions and suggest rebalancing opportunities." ``` The agent runs: ```bash theme={null} octav portfolio get --addresses 0xABC... --raw ``` And receives structured JSON it can reason over. ### Example: Autonomous Monitoring Script ```bash theme={null} #!/bin/bash # Daily portfolio snapshot with alerts ADDR="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" NAV=$(octav portfolio nav --addresses $ADDR --raw | jq -r '.nav') echo "Portfolio value: $NAV" # Agent can parse this output and decide on actions ``` ### Output Format All commands return JSON. Pretty-printed by default, compact with `--raw`: ```bash theme={null} # Pretty-printed (default) octav credits # => { # => "credits": 42 # => } # Compact JSON (ideal for piping / agent parsing) octav credits --raw # => {"credits":42} ``` The `--raw` flag also disables field stripping, returning the full API response. *** ## Multiple Addresses Most commands accept multiple addresses as a comma-separated list: ```bash theme={null} octav portfolio get \ --addresses 0xABC...123,0xDEF...456,7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU ``` Maximum 10 addresses per request. *** ## Error Handling Errors are returned as JSON on stdout with a non-zero exit code: ```json theme={null} { "error": { "type": "auth", "message": "Invalid API key", "status": 401 } } ``` | Error | Cause | Solution | | ---------------------- | ------------------------------------------ | -------------------------------------------------------- | | Invalid address format | Address doesn't match EVM or Solana format | Use `0x...` (40 hex chars) for EVM or base58 for Solana | | Authentication failure | Missing or invalid API key | Run `octav auth set-key` or set `OCTAV_API_KEY` | | Insufficient credits | Not enough credits for the request | Purchase more at [data.octav.fi](https://data.octav.fi/) | *** ## Resources Source code and issues Full REST API reference Credit packages and costs # The Complete Developer Guide Source: https://docs.octav.fi/api/ai-development/complete-guide Build DeFi dashboards, portfolio trackers, and AI-powered crypto agents using the Octav API, MCP Server, CLI, and x402 payments. Complete guide with code examples. Build a DeFi dashboard, alert system, or portfolio tracker with the Octav API. This guide walks through four real projects — from a vibecoded React dashboard to an autonomous AI agent — using every tool in the Octav developer ecosystem. **What you'll build:** A real-time portfolio dashboard, a transaction alert system, an AI portfolio agent, and a tax export tool — all powered by the same API. *** ## Why Octav? Building crypto apps normally means stitching together 10+ APIs, normalizing data across chains, and maintaining a patchwork of indexers. Octav replaces all of that with a single API covering 65+ blockchains. Portfolio, transactions, DeFi positions, and historical data — all from one endpoint REST API, MCP Server, CLI, x402 payments, Agent Skill, and llms.txt — pick the tool that fits your workflow Most calls cost 1 credit (\$0.025). Credits never expire. No subscriptions. Built for vibecoding — structured JSON output, LLM-friendly docs, and MCP integration ### Architecture Octav sits between the blockchain data layer and your application: ``` 65+ Blockchains (EVM + Solana) | [Octav API] <-- indexing, normalization, caching | +---------+---------+---------+ | | | | REST API MCP CLI x402 | Server | Payments | | | | Your App Claude Shell AI Agents Cursor Scripts (no API key) VS Code Cron ``` *** ## Getting Started Get your API key and make your first request in under 3 minutes. Sign up at [data.octav.fi](https://data.octav.fi/) and generate an API key. Purchase a credit package — the Starter pack (4,000 credits / \$100) is plenty for testing. Fetch the net worth of any wallet address: ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/nav?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://api.octav.fi/v1/nav?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd', { headers: { 'Authorization': `Bearer ${process.env.OCTAV_API_KEY}` } } ); const data = await response.json(); console.log(`Net Worth: $${data.nav}`); // => Net Worth: $1,235,564.43 ``` ```python Python theme={null} import requests response = requests.get( 'https://api.octav.fi/v1/nav', params={'addresses': '0x6426af179aabebe47666f345d69fd9079673f6cd'}, headers={'Authorization': f'Bearer {api_key}'} ) data = response.json() print(f"Net Worth: ${data['nav']:,.2f}") # => Net Worth: $1,235,564.43 ``` The core endpoints you'll use across all four projects: | Endpoint | What It Returns | Cost | | -------------------- | ---------------------------------- | -------- | | `/v1/portfolio` | Full portfolio with DeFi positions | 1 credit | | `/v1/nav` | Net asset value in any currency | 1 credit | | `/v1/wallet` | Token balances (no DeFi) | 1 credit | | `/v1/transactions` | Transaction history with filters | 1 credit | | `/v1/token-overview` | Token distribution across chains | 1 credit | | `/v1/historical` | Portfolio snapshot at a past date | 1 credit | | `/v1/credits` | Your remaining credits | Free | | `/v1/status` | Sync status for addresses | Free | *** ## Project 1: Real-Time Portfolio Dashboard Build a React dashboard that shows net worth, DeFi positions, and token distribution — using a prompt-first approach. ### The Vibecoding Prompt Give this prompt to Claude, Cursor, or your AI coding assistant of choice: ```text theme={null} Using the Octav API (docs: https://docs.octav.fi/llms.txt), build a React + TypeScript portfolio dashboard with TailwindCSS. Features: - Input field for wallet address (EVM 0x... or Solana base58) - Net worth display using GET /v1/nav?addresses={addr} - DeFi positions grouped by protocol using GET /v1/portfolio?addresses={addr} - Token distribution pie chart using GET /v1/token-overview?addresses={addr}&date={today} - Loading states and error handling Auth: Bearer token via OCTAV_API_KEY env var, proxied through a Next.js API route. All Octav endpoints return JSON. Portfolio response includes networth, chains, and assetByProtocols. ``` **MCP superpower:** Install the [MCP Server](/api/ai-development/mcp-server) and your AI assistant can query live portfolio data *while* building the dashboard. It sees real response shapes, not just docs. ### The Dashboard Component Here's what the AI generates (and what you'd refine): ```typescript theme={null} import { useState, useEffect } from 'react'; interface Portfolio { networth: string; chains: Record; assetByProtocols: Record; }>; }>; } interface NavData { nav: number; currency: string; } interface TokenOverview { tokens: Array<{ symbol: string; value: number; percentage: number }>; } export function PortfolioDashboard({ address }: { address: string }) { const [portfolio, setPortfolio] = useState(null); const [nav, setNav] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!address) return; async function fetchData() { setLoading(true); setError(null); try { const headers = { 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_OCTAV_API_KEY}` }; const [portfolioRes, navRes] = await Promise.all([ fetch(`https://api.octav.fi/v1/portfolio?addresses=${address}`, { headers }), fetch(`https://api.octav.fi/v1/nav?addresses=${address}`, { headers }), ]); if (!portfolioRes.ok || !navRes.ok) { throw new Error('Failed to fetch portfolio data'); } const [portfolioData, navData] = await Promise.all([ portfolioRes.json(), navRes.json(), ]); setPortfolio(portfolioData[0]); setNav(navData); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } } fetchData(); }, [address]); if (loading) return
Loading portfolio...
; if (error) return
Error: {error}
; if (!portfolio || !nav) return null; return (
{/* Net Worth */}

Net Worth

${nav.nav.toLocaleString(undefined, { maximumFractionDigits: 2 })}

{/* Chain Distribution */}
{Object.values(portfolio.chains).map((chain) => (

{chain.name}

${parseFloat(chain.value).toLocaleString(undefined, { maximumFractionDigits: 0 })}

))}
{/* DeFi Positions by Protocol */}

DeFi Positions

{Object.values(portfolio.assetByProtocols).map((protocol) => (
{protocol.name} ${parseFloat(protocol.value).toLocaleString(undefined, { maximumFractionDigits: 0 })}
{protocol.positions.map((position, i) => (
{position.type} {position.assets.map((asset, j) => (
{asset.symbol} ${parseFloat(asset.value).toLocaleString()}
))}
))}
))}
); } ``` **Security:** In production, proxy API calls through your backend. Never expose `OCTAV_API_KEY` in client-side code. Use a Next.js API route or similar server-side proxy. *** ## Project 2: Transaction Alert System Build a whale-alert style notification system using the Octav CLI and cron. No Node.js runtime, no dependencies — just bash. ### Install the CLI ```bash theme={null} curl -sSf https://raw.githubusercontent.com/Octav-Labs/octav-cli/main/install.sh | sh octav auth set-key YOUR_API_KEY ``` ### The Alert Script ```bash theme={null} #!/bin/bash # tx-alert.sh — Monitor wallets for new transactions, alert on large ones ADDRESSES="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" THRESHOLD_USD=1000 STATE_DIR="$HOME/.octav/state" LOG_FILE="$HOME/.octav/logs/tx-alert.log" mkdir -p "$STATE_DIR" "$(dirname "$LOG_FILE")" for ADDR in $(echo "$ADDRESSES" | tr ',' '\n'); do STATE_FILE="$STATE_DIR/last-tx-${ADDR:0:8}.txt" LAST_SEEN="" [ -f "$STATE_FILE" ] && LAST_SEEN=$(cat "$STATE_FILE") # Fetch recent transactions RESULT=$(octav transactions get --addresses "$ADDR" --limit 20 --raw 2>&1) if [ $? -ne 0 ]; then echo "[$(date)] ERROR fetching $ADDR: $RESULT" >> "$LOG_FILE" continue fi LATEST_TX=$(echo "$RESULT" | jq -r '.transactions[0].hash // empty') [ -z "$LATEST_TX" ] && continue # Skip if no new transactions [ "$LATEST_TX" = "$LAST_SEEN" ] && continue # Process new transactions echo "$RESULT" | jq -r --arg last "$LAST_SEEN" --argjson threshold "$THRESHOLD_USD" ' .transactions | if $last == "" then .[:5] else [limit(20; .[] | select(.hash != $last))] end | .[] | select( [.assets[]? | .value // 0 | tonumber] | add > $threshold ) | "[\(.date)] \(.txType) $\([.assets[]? | .value // 0 | tonumber] | add | floor) on \(.chainKey) — \(.hash[:16])..." ' | while read -r line; do echo "$line" >> "$LOG_FILE" # macOS notification osascript -e "display notification \"$line\" with title \"Octav Alert\"" 2>/dev/null done # Update state echo "$LATEST_TX" > "$STATE_FILE" done ``` ### Schedule It ```bash theme={null} # Check every 10 minutes */10 * * * * /path/to/tx-alert.sh ``` Add with: ```bash theme={null} (crontab -l 2>/dev/null; echo "*/10 * * * * $HOME/scripts/tx-alert.sh") | crontab - ``` Save as `~/Library/LaunchAgents/fi.octav.tx-alert.plist`: ```xml theme={null} Label fi.octav.tx-alert ProgramArguments /bin/bash /Users/YOU/scripts/tx-alert.sh StartInterval 600 StandardOutPath /tmp/tx-alert.stdout StandardErrorPath /tmp/tx-alert.stderr ``` ```bash theme={null} launchctl load ~/Library/LaunchAgents/fi.octav.tx-alert.plist ``` ### Customize with AI Give Claude Code this prompt to extend the script: ```text theme={null} I have a bash script that monitors crypto wallets using the octav CLI. It currently sends macOS notifications. Modify it to: 1. Send alerts to a Slack webhook (URL in SLACK_WEBHOOK env var) 2. Filter to only swap and transfer transactions 3. Add a daily summary at midnight with total transaction count and volume ``` *** ## Project 3: AI Portfolio Agent Connect the Octav MCP Server to your AI assistant and build an autonomous monitoring agent. ### Set Up MCP Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` Go to **Cursor Settings > MCP** and add: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` Add to your VS Code `settings.json`: ```json theme={null} { "mcp": { "servers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } } ``` ```bash theme={null} claude mcp add octav -- npx -y octav-api-mcp ``` Then set the API key via your shell profile: ```bash theme={null} export OCTAV_API_KEY="your-api-key-here" ``` ### Natural Language Queries Once connected, ask your AI assistant questions like: ```text theme={null} "What's my total exposure to Aave across all chains for 0xABC...?" ``` ```text theme={null} "Show me all swap transactions over $1,000 on Arbitrum in the last 30 days" ``` ```text theme={null} "Compare my portfolio value today vs. 30 days ago and tell me which chains gained the most" ``` The MCP server exposes 14 tools — your AI assistant can call `octav_get_portfolio`, `octav_get_transactions`, `octav_get_nav`, and more directly from conversation. ### Python Monitoring Agent For autonomous monitoring, build a Python agent: ```python theme={null} import requests import time import os from datetime import datetime, timedelta class OctavPortfolioAgent: """Autonomous portfolio monitor using the Octav API""" def __init__(self, api_key: str): self.api_key = api_key self.base_url = 'https://api.octav.fi/v1' self.headers = {'Authorization': f'Bearer {api_key}'} self.snapshots: dict[str, float] = {} def get_nav(self, address: str, currency: str = 'USD') -> dict: """Get net asset value""" resp = requests.get( f'{self.base_url}/nav', params={'addresses': address, 'currency': currency}, headers=self.headers, timeout=30, ) resp.raise_for_status() return resp.json() def get_portfolio(self, address: str) -> dict: """Get full portfolio with DeFi positions""" resp = requests.get( f'{self.base_url}/portfolio', params={'addresses': address}, headers=self.headers, timeout=30, ) resp.raise_for_status() return resp.json()[0] def get_transactions(self, address: str, **filters) -> list: """Get transaction history with optional filters""" params = {'addresses': address, **filters} resp = requests.get( f'{self.base_url}/transactions', params=params, headers=self.headers, timeout=30, ) resp.raise_for_status() return resp.json() def check_credits(self) -> int: """Check remaining API credits""" resp = requests.get( f'{self.base_url}/credits', headers=self.headers, ) return resp.json().get('credits', 0) def monitor(self, addresses: list[str], interval: int = 300, threshold_pct: float = 5.0): """Main monitoring loop — checks portfolio and alerts on changes""" print(f"Monitoring {len(addresses)} address(es) every {interval}s") print(f"Alert threshold: {threshold_pct}% change") print(f"Credits remaining: {self.check_credits()}") while True: for addr in addresses: try: nav = self.get_nav(addr) current = nav['nav'] previous = self.snapshots.get(addr) if previous: change_pct = ((current - previous) / previous) * 100 if abs(change_pct) >= threshold_pct: print(f"\n{'='*50}") print(f"ALERT: {addr[:10]}... changed {change_pct:+.2f}%") print(f" ${previous:,.2f} -> ${current:,.2f}") print(f" {datetime.now().isoformat()}") print(f"{'='*50}\n") self.snapshots[addr] = current time.sleep(2) # rate limit courtesy except requests.RequestException as e: print(f"Error checking {addr[:10]}...: {e}") time.sleep(interval) if __name__ == '__main__': agent = OctavPortfolioAgent(os.environ['OCTAV_API_KEY']) agent.monitor( addresses=['0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68'], interval=300, threshold_pct=3.0, ) ``` ### x402 Agent Payments For autonomous AI agents that don't have API keys, use the x402 payment protocol. The agent pays per request with USDC — no key management needed. This is not the default path. Use the API-key REST API unless the user explicitly asked for x402, or the agent has a funded wallet and no API key. Only `wallet`, `portfolio`, `nav`, `status`, and `chains` are exposed over x402 — **transaction history is not**. See [Agent Endpoints (x402)](/api/endpoints/agent-x402). ```python theme={null} # x402 agent call — no API key required # The agent's wallet pays via HTTP 402 payment protocol import requests response = requests.get( 'https://api.octav.fi/v1/agent/wallet?addresses=0x742d35Cc...', # No Authorization header needed — payment handled via x402 ) # If 402 is returned, the agent's x402-compatible HTTP client # automatically handles the micropayment and retries data = response.json() ``` Via the CLI: ```bash theme={null} # No API key needed — pays with x402 octav agent wallet --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 octav agent portfolio --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` *** ## Project 4: Tax & Accounting Export Tool Export categorized transaction history to CSV for tax reporting. ```python theme={null} import requests import csv import os from datetime import datetime class TaxExporter: """Export transaction history to CSV for tax reporting""" def __init__(self, api_key: str): self.api_key = api_key self.base_url = 'https://api.octav.fi/v1' self.headers = {'Authorization': f'Bearer {api_key}'} # Transaction type categories for tax purposes TAX_CATEGORIES = { 'SWAP': 'Trade', 'TRANSFERIN': 'Receive', 'TRANSFEROUT': 'Send', 'CLAIM': 'Income', 'AIRDROP': 'Income', 'STAKE': 'DeFi', 'UNSTAKE': 'DeFi', 'DEPOSIT': 'DeFi', 'WITHDRAW': 'DeFi', 'BORROW': 'DeFi', 'REPAY': 'DeFi', 'APPROVE': 'Other', } def fetch_all_transactions(self, address: str, start_date: str, end_date: str) -> list: """Fetch all transactions with pagination""" all_txs = [] offset = 0 limit = 250 while True: resp = requests.get( f'{self.base_url}/transactions', params={ 'addresses': address, 'startDate': start_date, 'endDate': end_date, 'limit': limit, 'offset': offset, 'sort': 'ASC', }, headers=self.headers, timeout=30, ) resp.raise_for_status() data = resp.json() txs = data if isinstance(data, list) else data.get('transactions', []) if not txs: break all_txs.extend(txs) if len(txs) < limit: break offset += limit return all_txs def export_csv(self, address: str, year: int, output_path: str): """Export a full year of transactions to CSV""" start_date = f'{year}-01-01' end_date = f'{year}-12-31' print(f"Fetching transactions for {address[:10]}... ({start_date} to {end_date})") transactions = self.fetch_all_transactions(address, start_date, end_date) print(f"Found {len(transactions)} transactions") with open(output_path, 'w', newline='') as f: writer = csv.writer(f) writer.writerow([ 'Date', 'Type', 'Tax Category', 'Chain', 'Asset', 'Amount', 'Value (USD)', 'Fee (USD)', 'Transaction Hash', ]) for tx in transactions: tx_type = tx.get('txType', 'UNKNOWN') tax_category = self.TAX_CATEGORIES.get(tx_type, 'Other') fee_usd = sum(float(f.get('value', 0)) for f in tx.get('fees', [])) for asset in tx.get('assets', []): writer.writerow([ tx.get('date', ''), tx_type, tax_category, tx.get('chainKey', ''), asset.get('symbol', ''), asset.get('balance', ''), asset.get('value', ''), f'{fee_usd:.2f}', tx.get('hash', ''), ]) print(f"Exported to {output_path}") if __name__ == '__main__': exporter = TaxExporter(os.environ['OCTAV_API_KEY']) exporter.export_csv( address='0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68', year=2025, output_path='crypto-taxes-2025.csv', ) ``` ### Historical Snapshots For year-end portfolio valuations, use the historical endpoint with subscribe-snapshot for automatic daily recording: ```bash theme={null} # Subscribe to daily snapshots (one-time setup) octav historical subscribe-snapshot \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --description "Tax reporting - main wallet" # Later, pull a specific date's snapshot octav historical get \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --date 2025-12-31 ``` *** ## Developer Toolkit Overview Choose the right tool for your use case: | Tool | Best For | Setup | Auth | | ------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------- | ------------------- | | **REST API** *(default)* | Web apps, backends, integrations — all 25 endpoints | Any HTTP client | API key | | **MCP Server** | AI assistants (Claude, Cursor, VS Code) | `npx octav-api-mcp` | API key | | **CLI** | Shell scripts, cron jobs, terminal workflows | `curl \| sh` or `cargo install octav` | API key or env var | | **x402 Payments** | Autonomous agents with a wallet and no API key — 5 endpoints, no transactions | No setup | Agent wallet (USDC) | | **Agent Skill** | Claude Code, Codex, ChatGPT | `npx skills add Octav-Labs/octav-api-skill` | API key | | **llms.txt** | Feed docs to any LLM | Point to URL | N/A | Direct HTTP calls to `https://api.octav.fi/v1/`. Works with any language or framework. 65+ chains, 1-credit calls, structured JSON responses. ```bash theme={null} curl -X GET "https://api.octav.fi/v1/portfolio?addresses=0xABC..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` 14 tools accessible through natural language in Claude Desktop, Cursor, VS Code, and Claude Code. ```bash theme={null} npx octav-api-mcp ``` [Full MCP docs](/api/ai-development/mcp-server) Rust binary with structured JSON output. Ideal for shell scripts and AI agent tool-use. ```bash theme={null} octav portfolio nav --addresses 0xABC... --raw | jq '.nav' ``` [Full CLI docs](/api/ai-development/cli) HTTP 402 payment protocol for AI agents. No API keys — the agent pays per request with USDC. ```bash theme={null} octav agent portfolio --addresses 0xABC... ``` Install once and your AI agent knows the full API: ```bash theme={null} npx skills add Octav-Labs/octav-api-skill ``` [View on skills.sh](https://skills.sh/Octav-Labs/octav-api-skill) Feed structured API docs directly to any LLM: ```text theme={null} https://docs.octav.fi/llms.txt ``` [Learn more](/api/ai-development/llms-integration) *** ## Advanced Patterns ### Multi-Wallet Aggregation Query up to 10 addresses in a single API call (still just 1 credit per address): ```python theme={null} addresses = '0xABC...,0xDEF...,0x123...' response = requests.get( f'https://api.octav.fi/v1/portfolio?addresses={addresses}', headers={'Authorization': f'Bearer {api_key}'} ) # Returns an array — one portfolio object per address portfolios = response.json() total_nav = sum(float(p['networth']) for p in portfolios) ``` ### Rate Limit Handling The API allows 360 requests per minute. Implement exponential backoff for reliability: ```python theme={null} import time from requests.exceptions import RequestException def fetch_with_retry(url: str, headers: dict, max_retries: int = 3): for attempt in range(max_retries): try: resp = requests.get(url, headers=headers, timeout=30) if resp.status_code == 429: wait = int(resp.headers.get('Retry-After', 2 ** attempt)) time.sleep(wait) continue resp.raise_for_status() return resp.json() except RequestException: if attempt == max_retries - 1: raise time.sleep(2 ** attempt) ``` ### Dust Filtering Filter out small token balances that clutter portfolio views: ```python theme={null} portfolio = response.json()[0] # Filter tokens worth less than $1 meaningful_tokens = { key: protocol for key, protocol in portfolio['assetByProtocols'].items() if float(protocol['value']) > 1.0 } ``` ### Webhook-Style Polling Combine the CLI with cron for webhook-like behavior without running a server: ```bash theme={null} # Check every 5 minutes, alert on changes */5 * * * * /path/to/portfolio-monitor.sh # Daily snapshot at 9am 0 9 * * * /path/to/daily-snapshot.sh # Weekly report on Sundays 0 10 * * 0 /path/to/weekly-report.sh ``` See [CLI Automations](/cli/automations) for complete, production-ready scripts. *** ## What's Next Complete endpoint documentation with interactive playground Connect AI assistants to live portfolio data Production-ready cron scripts for monitoring and exports Credit packages starting at \$100 for 4,000 calls **Ready to start?** Get your API key at [data.octav.fi](https://data.octav.fi/) and build your first project in minutes. Join the [Discord](https://discord.com/invite/qvcknAa73A) if you need help. # Crypto API for AI Agents & LLMs Source: https://docs.octav.fi/api/ai-development/llms-integration Make the Octav API discoverable to AI agents, LLMs and autonomous programs through llms.txt and standardised documentation formats. Make the Octav API easily discoverable and usable by AI agents, LLMs, and autonomous programs through standardized documentation formats. **llms.txt Standard** - We support the [llms.txt](https://llmstxt.org/) format for LLM-friendly documentation *** ## Agent Skill (Recommended) The fastest way to integrate Octav into any AI agent. Install the official skill and your agent instantly knows the full API: ```bash theme={null} npx skills add Octav-Labs/octav-api-skill ``` Works with Claude Code, Codex, ChatGPT, and any agent that supports the [Agent Skills](https://agentskills.io) standard. The skill covers all endpoints, authentication, pagination patterns, TypeScript types, error handling, and credit optimization — no need to paste documentation manually. *** ## What are AI Agents? AI agents are autonomous programs designed to carry out specific tasks or simulations with a degree of independence. They can: * Process inputs and execute logic * Interact with APIs and external services * Make decisions based on data * Achieve predefined goals autonomously ### Common Use Cases AI agents that help users: * Track portfolio performance * Get balance updates * Analyze DeFi positions * Receive alerts on changes Automated trading systems that: * Monitor wallet activity * Track transaction patterns * Analyze on-chain behavior * Generate trading signals Data analysis tools that: * Aggregate cross-chain data * Calculate metrics * Identify trends * Generate reports Alert systems that: * Watch for transactions * Monitor balances * Detect anomalies * Send notifications *** ## LLMs.txt Integration The `llms.txt` format makes web content easily parsable by Large Language Models. Access Octav's LLM-friendly documentation: ``` https://docs.octav.fi/llms.txt ``` Point your AI agent or LLM to this URL to automatically understand the Octav API structure and capabilities ### What's Included The llms.txt file contains: * **API Overview** - Introduction and key features * **Authentication** - How to access the API * **Endpoints** - All available endpoints with descriptions * **Data Models** - Response structures and types * **Code Examples** - Usage patterns * **Best Practices** - Integration guidelines *** ## Quick Start for AI Agents Fetch and parse the llms.txt documentation: ```python theme={null} import requests # Load LLM-friendly docs docs = requests.get('https://docs.octav.fi/llms.txt').text # Feed to your LLM prompt = f""" Using this API documentation: {docs} How can I get the portfolio for address 0x123...? """ ``` Obtain authentication credentials: 1. Sign up at [data.octav.fi](https://data.octav.fi/) 2. Generate an API key 3. Purchase credits for API calls Use the API in your agent: ```python theme={null} # Example: Portfolio checking agent def check_portfolio(address, api_key): headers = {'Authorization': f'Bearer {api_key}'} response = requests.get( f'https://api.octav.fi/v1/portfolio?addresses={address}', headers=headers ) return response.json() # Agent logic portfolio = check_portfolio('0x123...', API_KEY) networth = portfolio[0]['networth'] print(f"Portfolio value: ${networth}") ``` Process data intelligently: ```python theme={null} def analyze_portfolio(portfolio_data): data = portfolio_data[0] analysis = { 'total_value': float(data['networth']), 'chains': list(data['chains'].keys()), 'protocols': list(data['assetByProtocols'].keys()), 'chain_count': len(data['chains']) } return analysis ``` *** ## Example AI Agent Here's a complete example of a portfolio monitoring agent: ```python Python Agent theme={null} import requests import time from datetime import datetime class PortfolioAgent: def __init__(self, api_key): self.api_key = api_key self.base_url = 'https://api.octav.fi' self.headers = {'Authorization': f'Bearer {api_key}'} def get_portfolio(self, address): """Fetch portfolio data for an address""" response = requests.get( f'{self.base_url}/v1/portfolio?addresses={address}', headers=self.headers ) return response.json()[0] def analyze_holdings(self, portfolio): """Analyze portfolio composition""" analysis = { 'timestamp': datetime.now().isoformat(), 'total_value': float(portfolio['networth']), 'chains': {}, 'protocols': {}, 'top_assets': [] } # Analyze by chain for chain_key, chain in portfolio['chains'].items(): analysis['chains'][chain['name']] = float(chain['value']) # Analyze by protocol for protocol_key, protocol in portfolio['assetByProtocols'].items(): analysis['protocols'][protocol['name']] = float(protocol['value']) return analysis def monitor(self, address, check_interval=60): """Monitor portfolio and report changes""" last_value = None while True: portfolio = self.get_portfolio(address) current_value = float(portfolio['networth']) if last_value is not None: change = current_value - last_value change_pct = (change / last_value) * 100 if abs(change_pct) > 1: # Alert on 1% change print(f"🚨 Portfolio changed by {change_pct:.2f}%") print(f" New value: ${current_value:,.2f}") last_value = current_value time.sleep(check_interval) # Usage agent = PortfolioAgent('YOUR_API_KEY') analysis = agent.analyze_holdings( agent.get_portfolio('0x123...') ) print(analysis) ``` ```javascript JavaScript Agent theme={null} class PortfolioAgent { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://api.octav.fi'; } async getPortfolio(address) { const response = await fetch( `${this.baseUrl}/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${this.apiKey}` } } ); const data = await response.json(); return data[0]; } analyzeHoldings(portfolio) { const analysis = { timestamp: new Date().toISOString(), totalValue: parseFloat(portfolio.networth), chains: {}, protocols: {}, topAssets: [] }; // Analyze by chain Object.entries(portfolio.chains).forEach(([key, chain]) => { analysis.chains[chain.name] = parseFloat(chain.value); }); // Analyze by protocol Object.entries(portfolio.assetByProtocols).forEach(([key, protocol]) => { analysis.protocols[protocol.name] = parseFloat(protocol.value); }); return analysis; } async monitor(address, checkInterval = 60000) { let lastValue = null; setInterval(async () => { const portfolio = await this.getPortfolio(address); const currentValue = parseFloat(portfolio.networth); if (lastValue !== null) { const change = currentValue - lastValue; const changePct = (change / lastValue) * 100; if (Math.abs(changePct) > 1) { console.log(`🚨 Portfolio changed by ${changePct.toFixed(2)}%`); console.log(` New value: $${currentValue.toFixed(2)}`); } } lastValue = currentValue; }, checkInterval); } } // Usage const agent = new PortfolioAgent('YOUR_API_KEY'); const analysis = await agent.analyzeHoldings( await agent.getPortfolio('0x123...') ); console.log(analysis); ``` *** ## Use Cases for AI Agents Build chat-based portfolio assistants: ```python theme={null} # Example: ChatGPT-style portfolio assistant def portfolio_chat_handler(user_message, address, api_key): agent = PortfolioAgent(api_key) portfolio = agent.get_portfolio(address) if "balance" in user_message.lower(): return f"Your portfolio is worth ${portfolio['networth']}" elif "chains" in user_message.lower(): chains = ", ".join(portfolio['chains'].keys()) return f"You have assets on: {chains}" elif "top holding" in user_message.lower(): # Find largest position max_protocol = max( portfolio['assetByProtocols'].items(), key=lambda x: float(x[1]['value']) ) return f"Your top holding is {max_protocol[1]['name']}: ${max_protocol[1]['value']}" # User: "What's my balance?" response = portfolio_chat_handler( "What's my balance?", "0x123...", API_KEY ) print(response) # "Your portfolio is worth $12,345.67" ``` Send notifications on portfolio changes: ```python theme={null} class AlertAgent(PortfolioAgent): def __init__(self, api_key, webhook_url): super().__init__(api_key) self.webhook_url = webhook_url def send_alert(self, message): requests.post( self.webhook_url, json={'text': message} ) def check_thresholds(self, address, thresholds): portfolio = self.get_portfolio(address) value = float(portfolio['networth']) if value < thresholds['min']: self.send_alert( f"⚠️ Portfolio below ${thresholds['min']:,}" ) if value > thresholds['max']: self.send_alert( f"🎉 Portfolio above ${thresholds['max']:,}" ) # Usage agent = AlertAgent(API_KEY, SLACK_WEBHOOK) agent.check_thresholds('0x123...', { 'min': 10000, 'max': 100000 }) ``` Aggregate data across multiple addresses: ```python theme={null} class AggregatorAgent(PortfolioAgent): def aggregate_portfolios(self, addresses): total_value = 0 all_chains = set() all_protocols = set() for address in addresses: portfolio = self.get_portfolio(address) total_value += float(portfolio['networth']) all_chains.update(portfolio['chains'].keys()) all_protocols.update(portfolio['assetByProtocols'].keys()) return { 'total_value': total_value, 'unique_chains': len(all_chains), 'unique_protocols': len(all_protocols), 'address_count': len(addresses) } # Usage agent = AggregatorAgent(API_KEY) summary = agent.aggregate_portfolios([ '0x123...', '0x456...', '0x789...' ]) print(f"Combined portfolio: ${summary['total_value']:,.2f}") ``` *** ## Best Practices Respect API rate limits (360 requests/minute): ```python theme={null} import time from collections import deque class RateLimitedAgent(PortfolioAgent): def __init__(self, api_key, max_rpm=360): super().__init__(api_key) self.max_rpm = max_rpm self.requests = deque() def _check_rate_limit(self): now = time.time() # Remove requests older than 1 minute while self.requests and self.requests[0] < now - 60: self.requests.popleft() if len(self.requests) >= self.max_rpm: sleep_time = 60 - (now - self.requests[0]) time.sleep(sleep_time) def get_portfolio(self, address): self._check_rate_limit() self.requests.append(time.time()) return super().get_portfolio(address) ``` Handle API errors gracefully: ```python theme={null} def safe_api_call(self, func, *args, max_retries=3): for attempt in range(max_retries): try: return func(*args) except requests.exceptions.RequestException as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt) # Exponential backoff ``` Monitor and manage API credits: ```python theme={null} def check_credits(self): response = requests.get( f'{self.base_url}/v1/credits', headers=self.headers ) credits = response.json() if credits < 100: print(f"⚠️ Low credits: {credits}") return credits ``` *** ## Related Resources Get your API key Understand credit costs Main data endpoint Learn about the standard # Crypto Portfolio MCP Server Source: https://docs.octav.fi/api/ai-development/mcp-server Connect Claude, Cursor, Codex and VS Code to live crypto portfolio data through the Octav MCP server — holdings, transactions, net worth and history. The official Octav MCP server lets AI assistants query portfolio data, transaction history, net worth, and historical snapshots across 20+ blockchains — directly from your conversation. **Quick Install** — Run the MCP server with no installation required: ```bash theme={null} npx octav-api-mcp ``` *** ## Features Multi-chain portfolio aggregation * Wallet balances and DeFi positions * Net worth in multiple currencies * Token distribution overview Complete transaction records * Filter by chain, type, and date range * Manual sync triggers * Up to 250 results per query Time-series portfolio data * Point-in-time snapshots * Automatic daily subscriptions * Performance tracking Additional capabilities * Solana airdrop eligibility * Polymarket positions * x402 agent payment support *** ## Installation No installation required — run directly: ```bash theme={null} npx octav-api-mcp ``` Install globally: ```bash theme={null} npm install -g octav-api-mcp ``` Then run: ```bash theme={null} octav-api-mcp ``` Install globally: ```bash theme={null} pnpm add -g octav-api-mcp ``` Then run: ```bash theme={null} octav-api-mcp ``` *** ## Configuration You'll need an Octav API key. Get one at [data.octav.fi](https://data.octav.fi/). ### Claude Desktop Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` Edit `%APPDATA%\Claude\claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` ### Cursor Go to **Cursor Settings > MCP** and add a new global MCP server: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` ### VS Code (Copilot) Add to your VS Code `settings.json`: ```json theme={null} { "mcp": { "servers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } } ``` ### Claude Code Add the MCP server to your project with the Claude Code CLI: ```bash theme={null} claude mcp add octav -- npx -y octav-api-mcp ``` Then set the API key as an environment variable or pass it via your shell profile. *** ## Available Tools The MCP server exposes 14 tools organized by category. Most tools cost **1 credit per address**. ### Portfolio & Holdings **Complete portfolio including wallet holdings and DeFi positions across 20+ blockchains.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | -------------------------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses (EVM `0x...` or Solana base58). Max 10. | **Cost:** 1 credit per address ``` "Show me the full portfolio for 0x123..." ``` **Wallet token balances only (excludes DeFi protocol positions).** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** 1 credit per address ``` "What tokens does 0x123... hold?" ``` **Total net worth in a specified currency.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `currency` | string | No | Fiat: `USD`, `EUR`, `CAD`, `AED`, `CHF`, `SGD`. Crypto: `ETH`, `SOL`, `cbBTC`, `EURC`, `BNB`. Defaults to `USD`. | **Cost:** 1 credit per address ``` "What's the total net worth of 0x123... in EUR?" ``` **Aggregated token distribution across all chains.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `date` | string | Yes | Snapshot date in `YYYY-MM-DD` format. | **Cost:** 1 credit per address ``` "Show me the token breakdown for 0x123... as of 2025-01-15" ``` ### Transactions **Query transaction history with filtering and pagination.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | --------------------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `chain` | string | No | Filter by chain (e.g., `ethereum`, `solana`). | | `type` | string | No | Filter by type (e.g., `transfer`, `swap`, `stake`). | | `startDate` | string | No | Start date (`YYYY-MM-DD`). | | `endDate` | string | No | End date (`YYYY-MM-DD`). | | `limit` | number | No | Results per request (1–250, default 50). | | `offset` | number | No | Pagination offset (default 0). | **Cost:** 1 credit per address ``` "Show me the last 20 swaps on Ethereum for 0x123..." ``` **Trigger manual transaction synchronization for immediate indexing.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** 1 credit per address ``` "Sync the latest transactions for 0x123..." ``` ### Historical & Snapshots **Portfolio snapshot for a specific past date.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ---------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `date` | string | Yes | Date in `YYYY-MM-DD` format. | **Cost:** 1 credit per address ``` "What was my portfolio worth on 2024-12-31?" ``` **Subscribe to automatic daily portfolio snapshots.** | Parameter | Type | Required | Description | | ------------- | --------- | -------- | ------------------------------------ | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `description` | string | No | Optional label for the subscription. | **Cost:** 1,200 credits ``` "Subscribe 0x123... to daily snapshots" ``` ### Metadata **Check sync status of addresses across all chains.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** Free ``` "Is 0x123... fully synced?" ``` **Check API credit balance and usage.** No parameters required. **Cost:** Free ``` "How many API credits do I have left?" ``` ### Specialized **Check airdrop eligibility for a Solana address.** | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------- | | `address` | string | Yes | Solana wallet address (base58). | **Cost:** 1 credit ``` "Check airdrop eligibility for my Solana wallet" ``` **Get Polymarket prediction market positions.** | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------------------- | | `address` | string | Yes | Ethereum wallet address (`0x...`). | **Cost:** 1 credit ``` "Show my Polymarket positions for 0x123..." ``` **Wallet holdings via x402 payment protocol for AI agents with automatic payment.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** Paid via HTTP 402 payment protocol **Full portfolio via x402 payment protocol for AI agents with automatic payment.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** Paid via HTTP 402 payment protocol Prefer the API-key tools above. Reach for `octav_agent_*` only when the user explicitly asks for x402, or when the agent has a funded wallet and no API key. They cover only wallet, portfolio, nav, status, and chains — **transaction history is not available over x402**. See [Agent Endpoints (x402)](/api/endpoints/agent-x402). *** ## Example Prompts Once the MCP server is connected, you can ask your AI assistant questions like: ``` "What's the total value of my portfolio at 0xABC...?" ``` ``` "Show me all swap transactions on Arbitrum for the past month" ``` ``` "Compare my portfolio value today vs. 30 days ago" ``` ``` "Check if my Solana wallet is eligible for any airdrops" ``` ``` "How many API credits do I have left?" ``` *** ## Error Handling The MCP server returns clear error messages for common issues: | Error | Cause | Solution | | ---------------------- | ------------------------------------------ | -------------------------------------------------------- | | Invalid address format | Address doesn't match EVM or Solana format | Use `0x...` (40 hex chars) for EVM or base58 for Solana | | Authentication failure | Missing or invalid API key | Check your `OCTAV_API_KEY` environment variable | | Insufficient credits | Not enough credits for the request | Purchase more at [data.octav.fi](https://data.octav.fi/) | | Rate limit exceeded | Too many requests per minute | Wait and retry — the server includes retry guidance | *** ## Resources Source code and issues Full REST API reference Credit packages and costs # Build Crypto Apps With AI Source: https://docs.octav.fi/api/ai-development/overview Use AI as a development partner to build crypto portfolio trackers, analytics dashboards and blockchain data apps on the Octav API. Use AI as your development partner to build crypto portfolio trackers, analytics dashboards, and blockchain data applications through conversation and iteration. Access AI-optimized Octav API documentation via llms.txt for ChatGPT, Claude, and other LLMs Ready-to-use AI prompts for building common crypto applications with Octav Connect Claude Desktop, Cursor, and VS Code to the Octav API via the Model Context Protocol Query portfolio data from your terminal or integrate into AI agent shell workflows *** ## Why Build with AI + Octav? Rather than just code completion, AI helps you architect, implement, and optimize blockchain applications that leverage Octav's comprehensive portfolio and transaction data. ### What You Can Build Multi-chain portfolio dashboards * Real-time balance tracking * DeFi position monitoring * Net worth calculations * Historical performance Advanced blockchain analytics * Transaction categorization * Protocol usage patterns * Chain distribution analysis * Yield tracking Autonomous monitoring systems * Portfolio alerts * Balance notifications * Transaction monitoring * Automated reporting Financial tracking tools * Transaction history export * Fee tracking * Income/expense categorization * Multi-wallet aggregation *** ## Octav AI Integrations ### llms.txt Format Access structured, AI-friendly Octav API documentation: ``` https://docs.octav.fi/llms.txt ``` Point your AI agent or LLM to this URL to automatically understand the Octav API structure and capabilities **What's Included:** * Complete API endpoint reference * Authentication and pricing details * Supported chains and protocols * Transaction types and categorization * Code examples in multiple languages * Data models and response structures ### Integration Methods **Claude Code, Codex, ChatGPT** Install the Octav API skill to give your AI agent full knowledge of the API: ```bash theme={null} npx skills add Octav-Labs/octav-api-skill ``` Once installed, your agent automatically knows how to use every Octav endpoint, handle authentication, paginate results, and optimize credit usage. [View on skills.sh →](https://skills.sh/Octav-Labs/octav-api-skill) **ChatGPT, Claude, Perplexity** Simply reference the llms.txt URL in your conversation: ``` "Using the Octav API documentation at https://docs.octav.fi/llms.txt, help me build a portfolio tracker for Ethereum and Base" ``` The LLM will fetch and parse the documentation automatically. **Claude Desktop, Cursor, VS Code** Install the official MCP server to give AI assistants direct access to the Octav API: ```bash theme={null} npx octav-api-mcp ``` 14 tools covering portfolio data, transactions, historical snapshots, airdrops, and more — all accessible through natural conversation. [View MCP Server docs →](/api/ai-development/mcp-server) **Terminal, Scripts, AI Agents** Install the Octav CLI for direct terminal access to portfolio data: ```bash theme={null} curl -sSf https://raw.githubusercontent.com/Octav-Labs/octav-cli/main/install.sh | sh ``` Structured JSON output — ideal for shell scripts, cron jobs, and AI agent tool-use via shell execution. [View CLI docs →](/api/ai-development/cli) **Cursor, VS Code, Windsurf** Configure your IDE to access Octav docs: 1. Add Octav API docs as a context source 2. Reference in prompts: "Check Octav docs for portfolio endpoint" 3. Get accurate code generation based on actual API structure **Autonomous Systems** Build agents that: * Fetch documentation programmatically * Generate API calls based on requirements * Monitor portfolios autonomously * Send alerts based on conditions See [Building AI Agents](/api/ai-development/building-agents) for details. *** ## What Octav Provides for AI Development ### Comprehensive Blockchain Data **Real-time holdings across 65+ chains** * Multi-chain aggregation * DeFi protocol positions * Token balances and values * Net worth calculations * Virtual user portfolios (Pro) Perfect for: Portfolio dashboards, wealth tracking, multi-wallet aggregation **Complete transaction records with smart categorization** * Automatic transaction typing (swaps, transfers, DeFi actions) * Fee tracking * Search and filtering * Date range queries * Multi-chain support Perfect for: Tax tools, spending analysis, transaction explorers **Time-series portfolio snapshots** * Daily portfolio values * Performance tracking * Historical comparisons * Date-based queries Perfect for: Performance analytics, trend analysis, reporting **Protocol-level position tracking** * Lending positions * Liquidity pools * Staking positions * Yield farming * Vault positions Perfect for: DeFi dashboards, yield tracking, risk analysis *** ## Getting Started with AI Development 1. Sign up at [data.octav.fi](https://data.octav.fi/) 2. Generate an API key 3. Purchase credits (see [Pricing](/api/pricing)) Pick the tool that fits your workflow: * **ChatGPT/Claude**: Quick prototyping and exploration * **Cursor**: Full IDE integration for serious development * **GitHub Copilot**: Code completion while you type * **Custom Agents**: Autonomous monitoring systems Use our [Quick Start Guide](/api/ai-development/quick-start) for ready-to-use prompts: ``` "Using Octav API, create a Next.js portfolio tracker that shows: - Real-time balance across Ethereum, Base, and Arbitrum - DeFi positions from Aave and Uniswap - Transaction history with filtering - Historical performance chart" ``` AI can help with deployment too: * Environment variable setup * Build optimization * Error handling * Production monitoring *** ## Example: Building with AI Here's a realistic example of building a portfolio tracker with AI assistance: ```text Initial Prompt theme={null} "I want to build a crypto portfolio tracker using the Octav API. Requirements: - Support multiple wallets - Show balances across Ethereum, Base, and Arbitrum - Display DeFi positions (lending, liquidity pools) - Show transaction history with search - Calculate total net worth Tech stack: Next.js, TypeScript, TailwindCSS Please help me: 1. Design the architecture 2. Set up the project structure 3. Implement the core features" ``` ```typescript Generated Code Example theme={null} // AI generates this based on Octav API docs import { useState, useEffect } from 'react'; interface Portfolio { address: string; networth: string; chains: Record; assetByProtocols: Record; } export function usePortfolio(addresses: string[]) { const [portfolio, setPortfolio] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchPortfolio() { try { const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${addresses.join(',')}`, { headers: { 'Authorization': `Bearer ${process.env.OCTAV_API_KEY}` } } ); if (!response.ok) throw new Error('Failed to fetch portfolio'); const data = await response.json(); setPortfolio(data); } catch (err) { setError(err as Error); } finally { setLoading(false); } } fetchPortfolio(); }, [addresses]); return { portfolio, loading, error }; } ``` ```typescript Iterative Improvements theme={null} // Then ask AI to add features: "Add error handling with retry logic" "Add loading states and skeleton UI" "Add filtering by chain" "Add transaction history integration" // AI generates incremental improvements based on context ``` *** ## Best Practices for AI + Octav **Give AI the full picture:** ``` "I'm building a DeFi yield tracker using Octav API. Current stack: Next.js, TypeScript Already implemented: Portfolio fetching Now need: Yield position tracking across Aave, Compound, Uniswap" ``` Better context = better code generation **Use real API responses:** ``` "Here's the actual portfolio response from Octav: [paste JSON] Help me extract all lending positions and calculate total yield" ``` Real data helps AI understand the structure **Build in stages:** 1. Basic feature: "Fetch and display portfolio" 2. Add details: "Show protocol breakdown" 3. Add polish: "Add loading states and error handling" 4. Optimize: "Add caching and performance improvements" **Always review generated code:** * Check API endpoint URLs * Verify parameter names * Test error handling * Validate data types * Review security (API key handling) *** ## Common Use Cases ``` "Build a portfolio dashboard with Octav that: - Aggregates multiple wallet addresses - Shows total value by chain - Displays top holdings - Shows DeFi positions separately - Updates every 5 minutes Use React hooks for data fetching and state management" ``` ``` "Create a transaction history page using Octav that: - Shows all transactions for an address - Filters by type (swap, transfer, DeFi) - Filters by date range - Shows transaction details on click - Exports to CSV Include pagination and search functionality" ``` ``` "Build a DeFi position tracker that: - Shows all lending positions from Aave, Compound - Displays liquidity pool positions from Uniswap - Calculates total yield - Shows health factors for leveraged positions - Alerts on low health factor Use Octav's protocol position data" ``` ``` "Create a tax export tool that: - Fetches all transactions for a year - Categories by type (income, expense, trade) - Calculates fees in USD - Exports to CSV format compatible with tax software - Handles multiple addresses Use Octav's transaction categorization" ``` *** ## What to Read Next Jump right in with [ready-to-use prompts](/api/ai-development/quick-start) for common applications Learn how to [integrate with AI tools](/api/ai-development/llms-integration) like ChatGPT and Claude Create [autonomous monitoring systems](/api/ai-development/building-agents) with Octav data Master [effective prompting](/api/ai-development/quick-start) for better results *** ## Need Help? Ask questions about AI development with Octav Complete API reference and guides Working examples in multiple languages Understand API costs and credit usage # Quick Start with AI Source: https://docs.octav.fi/api/ai-development/quick-start Ready-to-use prompts for building crypto applications with Octav API and AI assistance Jump-start your development with these proven AI prompts for common blockchain applications. Each prompt is designed to work with ChatGPT, Claude, or your favorite AI coding assistant. **Copy and customize** these prompts for your specific needs. Replace placeholders like `[your requirements]` with your actual requirements. *** ## Portfolio Tracker Build a multi-chain portfolio dashboard: ```text theme={null} I want to build a cryptocurrency portfolio tracker using the Octav API at https://docs.octav.fi/llms.txt Requirements: - Track multiple wallet addresses - Show balances across Ethereum, Base, Arbitrum, and Polygon - Display total net worth in USD - Show breakdown by chain and protocol - Real-time price updates - Responsive design for mobile Tech Stack: - Next.js 14 with App Router - TypeScript - TailwindCSS - Recharts for visualizations Please: 1. Design the component architecture 2. Create a portfolio data fetching hook 3. Build the main dashboard component with charts 4. Add error handling and loading states 5. Include API key management (environment variables) The Octav API endpoint is GET https://api.octav.fi/v1/portfolio?addresses={addresses} It returns portfolio data including networth, chains, and asset breakdown. ``` *** ## Transaction History Viewer Create a filterable transaction explorer: ```text theme={null} Build a transaction history viewer using Octav API (docs: https://docs.octav.fi/llms.txt) Features Needed: - Display all transactions for a wallet address - Filter by transaction type (SWAP, TRANSFERIN, TRANSFEROUT, STAKE, etc.) - Filter by date range - Filter by chain (Ethereum, Arbitrum, Base, etc.) - Search functionality - Pagination (show 50 transactions per page) - Export to CSV UI Requirements: - Table view with sortable columns - Transaction type badges with colors - Click transaction to see full details - Mobile-responsive Tech Stack: - React with TypeScript - TailwindCSS - React Table or TanStack Table The API endpoint is GET https://api.octav.fi/v1/transactions?addresses={address} It supports these query parameters: - txTypes: filter by transaction type - networks: filter by chain - startDate/endDate: date range - limit/offset: pagination Generate complete code with proper TypeScript types based on the Octav API response structure. ``` *** ## DeFi Position Tracker Monitor lending and liquidity positions: ```text theme={null} Create a DeFi position tracker using the Octav API. Track These Position Types: - Lending positions (Aave, Compound) - Liquidity pools (Uniswap, Curve) - Staking positions - Yield farming positions - Vault positions (Yearn, etc.) Display: - Protocol name and logo - Position type - Total value in USD - Individual assets in each position - Chain where position exists - Health factor (if applicable for lending) Requirements: - Group positions by protocol - Show total value across all DeFi positions - Calculate percentage of portfolio in DeFi - Alert if health factor is low (under 1.5) Tech: Next.js, TypeScript, shadcn/ui components Reference the Octav API docs at https://docs.octav.fi/llms.txt for the portfolio endpoint response structure. The assetByProtocols object contains all protocol positions including LENDING, LIQUIDITYPOOL, STAKED, FARMING, VAULT, etc. ``` *** ## Multi-Wallet Aggregator Aggregate data across multiple addresses: ```text theme={null} Build a multi-wallet aggregator dashboard with Octav API. Functionality: - Add/remove multiple wallet addresses - Show combined net worth - Display holdings aggregated across all wallets - Show which wallets hold which assets - Identify duplicate holdings across wallets - Calculate percentage allocation per wallet Features: - Save wallet list to local storage - Export combined portfolio to CSV - Show top holdings across all wallets - Display total by chain across all addresses UI Components: - Wallet management sidebar - Combined portfolio overview - Individual wallet cards - Asset allocation chart - Chain distribution chart Stack: React, TypeScript, Chart.js or Recharts, TailwindCSS Call the portfolio endpoint once per wallet address and aggregate the responses client-side: https://api.octav.fi/v1/portfolio?addresses={address} Build components to aggregate and display the combined data. ``` *** ## Token Balance Monitor Simple balance tracking with alerts: ```text theme={null} Create a token balance monitoring tool using Octav API. Core Features: - Monitor specific token balances (ETH, USDC, USDT, etc.) - Set up balance alerts (notify when balance goes above/below threshold) - Track balance changes over time - Support multiple chains - Show current price and USD value Alert Types: - Balance drops below X tokens - Balance exceeds Y tokens - Balance changes by Z% - New tokens received Technical Requirements: - Polling interval: every 30 seconds - Store historical balance data - Browser notifications for alerts - Sound notifications (optional) - Email notifications (optional via webhook) Tech: Next.js, TypeScript, React hooks for polling Use Octav's /v1/wallet endpoint for real-time balance data: https://api.octav.fi/v1/wallet?addresses={address} Create a useBalanceMonitor hook that polls the API and triggers alerts. ``` *** ## Tax Export Tool Generate tax reports from transaction history: ```text theme={null} Create a cryptocurrency tax export tool using Octav's transaction data. Features: - Fetch all transactions for a tax year - Categorize transactions by type: * Income (AIRDROP, CLAIM, REWARDS) * Expenses (fees, gas) * Trades (SWAP) * Transfers (TRANSFERIN, TRANSFEROUT) - Calculate total fees paid in USD - Export to CSV format for tax software - Support multiple wallets - Date range selector CSV Columns: - Date & Time - Transaction Type - Asset Sent - Amount Sent - Asset Received - Amount Received - Fee (USD) - Transaction Hash - Chain Tech: React, TypeScript, date-fns for date handling, papaparse for CSV generation Use Octav Transactions API: https://api.octav.fi/v1/transactions?addresses={address}&startDate=2024-01-01&endDate=2024-12-31 The API provides transaction categorization and fee data automatically. ``` *** ## Airdrop Checker Check for claimable airdrops: ```text theme={null} Build an airdrop checker for Solana addresses using Octav API. Features: - Check if a Solana address has claimable airdrops - Show airdrop token name and amount - Display USD value of claimable tokens - Show unlock date for locked airdrops - Provide claim links - Support multiple addresses Display Info: - Token name and symbol - Claimable amount - Current value in USD - Unlock date (if locked) - Claimable status (ready or locked) - Link to claim page Tech: Next.js, TypeScript, TailwindCSS Use Octav Airdrop endpoint (Solana only): https://api.octav.fi/v1/airdrop?addresses={solana_address} Create a clean UI showing all airdrops with status badges and claim buttons. ``` *** ## Portfolio Performance Chart Visualize portfolio performance over time: ```text theme={null} Create a portfolio performance chart using Octav's historical data. Chart Requirements: - Line chart showing net worth over time - Date range selector (7d, 30d, 90d, 1y, all) - Hover to see exact value and date - Show percentage change from start to current - Show highest and lowest values in period - Mobile responsive Additional Features: - Chain breakdown over time (stacked area chart) - Protocol allocation changes - Top gaining/losing assets Tech: Next.js, TypeScript, Recharts or Chart.js Note: Historical data requires subscription. Use: https://api.octav.fi/v1/historical?addresses={address}&date={YYYY-MM-DD} Fetch data for multiple dates and build the time series. Handle cases where historical data isn't available. ``` *** ## Best Practices for Using These Prompts **Don't use prompts verbatim:** * Add your specific requirements * Specify your exact tech stack * Include design preferences * Add any constraints (mobile-first, accessibility, etc.) **Give AI more information:** ``` "I'm building a portfolio tracker for a mobile app. Users are crypto beginners, so keep UI simple. Need to support dark mode. Budget: 10,000 API calls per month" ``` More context = better code generation **Build in stages:** 1. Start with basic functionality 2. Ask AI to add features one at a time 3. Request improvements and optimizations 4. Add error handling and edge cases ``` "The portfolio tracker works! Now add: - Loading skeleton screens - Error states with retry button - Refresh button to update data" ``` **Always review AI-generated code:** * Check API endpoint URLs are correct * Verify parameter names match Octav API * Test error handling * Review security (API key protection) * Test with real data *** ## Next Steps Learn how to integrate with AI tools Create autonomous monitoring systems Complete API documentation Working code samples # API Authentication & API Keys Source: https://docs.octav.fi/api/authentication Authenticate Octav API requests with bearer API keys: how to create, use, rotate and store them securely. All Octav API requests require authentication using API keys. Learn how to create, use, and manage your API keys securely. **Get Your API Key** — Create and manage API keys at [data.octav.fi](https://data.octav.fi/) *** ## Getting Your API Key Navigate to [data.octav.fi](https://data.octav.fi/) and log in to your Octav account Go to the API Keys section and click **Create New API Key** Give your key a descriptive name to identify its purpose (e.g., "Production App", "Development") Copy and securely store your API key immediately - you won't be able to see it again **Keep Your Keys Secret** — Never share your API keys or commit them to version control. Treat them like passwords. *** ## Using Your API Key Include your API key in the `Authorization` header of every API request as a Bearer token. ### Header Format ``` Authorization: Bearer YOUR_API_KEY ``` ### Example Requests ```bash cURL theme={null} curl -X GET https://api.octav.fi/v1/portfolio?addresses=0x123... \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/portfolio?addresses=0x123...', { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }); const data = await response.json(); ``` ```python Python theme={null} import requests headers = { 'Authorization': 'Bearer YOUR_API_KEY' } response = requests.get( 'https://api.octav.fi/v1/portfolio', params={'addresses': '0x123...'}, headers=headers ) data = response.json() ``` ```typescript TypeScript theme={null} const response = await fetch('https://api.octav.fi/v1/portfolio?addresses=0x123...', { headers: { 'Authorization': `Bearer ${process.env.OCTAV_API_KEY}` } }); const data: Portfolio = await response.json(); ``` *** ## API Key Security ### Best Practices Store API keys in environment variables, never hardcode them in your source code. ```bash .env theme={null} OCTAV_API_KEY=your_api_key_here ``` ```javascript theme={null} // Access in your code const apiKey = process.env.OCTAV_API_KEY; ``` Periodically create new API keys and revoke old ones to maintain security: 1. Create a new API key in the developer portal 2. Update your applications with the new key 3. Revoke the old key once migration is complete Create different API keys for each application or environment: * **Production** - For live applications * **Staging** - For testing environment * **Development** - For local development * **CI/CD** - For automated testing This allows you to revoke access to specific applications without affecting others. Regularly review API key usage in the developer portal: * Track credit consumption * Monitor request patterns * Identify unusual activity * Set up usage alerts ### Revoking Compromised Keys If you suspect an API key has been compromised: Go to [data.octav.fi](https://data.octav.fi/), find the compromised key, and click **Revoke** Generate a new API key with a different name Update all applications using the old key with the new key Review usage logs to understand how the key may have been compromised *** ## Rate Limiting The Octav API enforces rate limits to ensure service stability and fair usage. **360 requests per minute per API key** Higher limits available upon request - contact us for enterprise needs ### Rate Limit Headers Every API response includes rate limit information in the headers: ``` X-RateLimit-Limit: 360 X-RateLimit-Remaining: 355 X-RateLimit-Reset: 1672531200 ``` *** ## Error Responses ### 401 Unauthorized Missing or invalid API key. ```json Response theme={null} { "error": "Unauthorized", "message": "Invalid or missing API key" } ``` ```bash Solution theme={null} # Check that your API key is included in the Authorization header curl -X GET https://api.octav.fi/v1/portfolio?addresses=0x123... \ -H "Authorization: Bearer YOUR_ACTUAL_API_KEY" ``` ### 403 Forbidden API key doesn't have access to the requested resource. ```json theme={null} { "error": "Forbidden", "message": "API key does not have access to this resource" } ``` **Solution:** Verify your subscription plan includes access to this endpoint (e.g., Token Overview requires Octav PRO). ### 429 Too Many Requests Rate limit exceeded. ```json Response theme={null} { "error": "Rate limit exceeded", "message": "You have exceeded your rate limit", "retry_after": 60 } ``` ```javascript Handling Rate Limits theme={null} async function fetchWithRetry(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') || 60; await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); continue; } return response; } throw new Error('Max retries exceeded'); } ``` *** ## Testing Your API Key Verify your API key is working correctly with a test request: ```bash cURL theme={null} # The /v1/credits endpoint costs 0 credits and is perfect for testing curl -X GET https://api.octav.fi/v1/credits \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }); if (response.ok) { const credits = await response.json(); console.log(`✓ Authentication successful! Remaining credits: ${credits}`); } else { console.error('✗ Authentication failed'); } ``` ```python Python theme={null} import requests response = requests.get( 'https://api.octav.fi/v1/credits', headers={'Authorization': 'Bearer YOUR_API_KEY'} ) if response.status_code == 200: credits = response.json() print(f'✓ Authentication successful! Remaining credits: {credits}') else: print('✗ Authentication failed') ``` **Free Testing** — The `/v1/credits` and `/v1/status` endpoints are free to call (0 credits), making them ideal for testing authentication. *** ## Next Steps Make your first API call Retrieve portfolio data Learn about credit costs Manage your API keys # Address Book Source: https://docs.octav.fi/api/endpoints/addressbook List, add, rename, and remove the wallet addresses saved to your key Manage the addresses saved to your API key — the same list you see in [Octav Pro](https://pro.octav.fi). Entries are keyed by address. **Cost:** 1 credit per call **Interactive Playground:** Test these endpoints in the [API Playground](/api-reference/addressbook). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## The entry object Wallet address. Your name for the address. `FREE`, `LITE`, or `PRO`. When the plan lapses. `null` on `FREE`. Whether the plan is currently active. ```json theme={null} { "address": "0xe760ad8df0a54aafd95e3bc271b65d224abeaa57", "label": "Treasury", "plan": "PRO", "expiresAt": "2026-11-01T00:00:00.000Z", "isPaid": true } ``` *** ## List Entries ### Endpoint ```bash theme={null} GET https://api.octav.fi/v1/addressbook ``` ### Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/addressbook" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/addressbook', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const { data } = await response.json(); ``` ### Example Response ```json theme={null} { "data": [ { "address": "0xe760ad8df0a54aafd95e3bc271b65d224abeaa57", "label": "Treasury", "plan": "PRO", "expiresAt": "2026-11-01T00:00:00.000Z", "isPaid": true }, { "address": "EPa1MBU21RjhoyZTEDcDj4yLVemtzxpVy35rVpgTcoV3", "label": "", "plan": "FREE", "expiresAt": null, "isPaid": false } ] } ``` *** ## Add Entries ### Endpoint ```bash theme={null} POST https://api.octav.fi/v1/addressbook ``` ### Parameters 1–100 entries per request. Up to 50 addresses without a paid plan — contact us to raise it. Wallet address. Must be unique within the request. Up to 255 characters. ### Example ```bash cURL theme={null} curl -X POST "https://api.octav.fi/v1/addressbook" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "entries": [ { "address": "0xe760ad8df0a54aafd95e3bc271b65d224abeaa57", "label": "Treasury" } ] }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/addressbook', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ entries: [{ address: '0xe760ad8df0a54aafd95e3bc271b65d224abeaa57', label: 'Treasury' }] }) }); const { data } = await response.json(); ``` ### Response Returns the full address book. *** ## Rename Entry ### Endpoint ```bash theme={null} PATCH https://api.octav.fi/v1/addressbook/{address} ``` ### Parameters Wallet address. Up to 255 characters. `""` clears it. ### Example ```bash cURL theme={null} curl -X PATCH "https://api.octav.fi/v1/addressbook/0xe760ad8df0a54aafd95e3bc271b65d224abeaa57" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "label": "Cold Wallet" }' ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.octav.fi/v1/addressbook/${address}`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ label: 'Cold Wallet' }) } ); const { data } = await response.json(); ``` ### Example Response ```json theme={null} { "data": { "address": "0xe760ad8df0a54aafd95e3bc271b65d224abeaa57", "label": "Cold Wallet", "plan": "PRO", "expiresAt": "2026-11-01T00:00:00.000Z", "isPaid": true } } ``` *** ## Remove Entry ### Endpoint ```bash theme={null} DELETE https://api.octav.fi/v1/addressbook/{address} ``` ### Parameters Wallet address. ### Example ```bash cURL theme={null} curl -X DELETE "https://api.octav.fi/v1/addressbook/0xe760ad8df0a54aafd95e3bc271b65d224abeaa57" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} await fetch(`https://api.octav.fi/v1/addressbook/${address}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${apiKey}` } }); ``` ### Response `204` with no body. Entries on a paid plan, or authorized by the wallet owner, return `409` — remove those in [Octav Pro](https://pro.octav.fi). *** ## Address Matching **EVM** addresses match case-insensitively. **Solana**, **Tron**, and **Starknet** addresses are case-sensitive. *** ## Error Responses | Code | Status | Meaning | | --------------------------------- | ------ | --------------------------------------------------------- | | `VALIDATION_ERROR` | 400 | Invalid or duplicate address | | `ADDRESSBOOK_ENTRY_NOT_FOUND` | 404 | Address is not in your book | | `ADDRESSBOOK_ENTRY_NOT_DELETABLE` | 409 | Entry is on a paid plan or authorized by the wallet owner | | `ADDRESSBOOK_QUOTA_REACHED` | 422 | Address limit reached | | `INTERNAL_ERROR` | 500 | Unexpected failure | ```json theme={null} { "error": { "code": "ADDRESSBOOK_QUOTA_REACHED", "message": "Address book quota reached", "quota": 50, "current": 50 } } ``` **Rate limit:** 360 requests/min. *** ## Related Endpoints Group saved addresses together Holdings for an address # Agent Endpoints (x402) Source: https://docs.octav.fi/api/endpoints/agent-x402 Pay-per-call endpoints for autonomous AI agents using the x402 payment protocol — no API key required A small set of endpoints under `/v1/agent/*` accept payment per request via the [x402 payment protocol](https://www.x402.org/) instead of an API key. An agent with a funded wallet can call them without signing up, without a key, and without buying credits. **x402 is not the default way to use Octav — the [REST API](/api/introduction) is.** Build on the API-key REST API unless one of these is true: * The user explicitly asked for x402 or pay-per-call access, **or** * The agent holds its own funded wallet and has no API key available. Otherwise use `/v1/*` with a Bearer token. It is cheaper at volume, has a 360 req/min rate limit, and covers **25 endpoints** instead of 5 — including [Transactions](/api/endpoints/transactions), [Historical Portfolio](/api/endpoints/historical-portfolio), [Token Overview](/api/endpoints/token-overview), and [Token Approvals](/api/endpoints/approvals), none of which exist on x402. Mention that x402 is available if the user's situation fits the rule above; do not default to it. **Cost:** 0.025 USDC per call, settled on Base (`eip155:8453`). No credits, no account. *** ## Available x402 endpoints | Endpoint | Returns | Price | | ------------------------- | ---------------------------------------------------- | ---------- | | `GET /v1/agent/portfolio` | Wallet and protocol holdings for an address | 0.025 USDC | | `GET /v1/agent/wallet` | Wallet holdings only (no DeFi positions) | 0.025 USDC | | `GET /v1/agent/nav` | Net Asset Value — `{nav, currency, conversionPrice}` | 0.025 USDC | | `GET /v1/agent/status` | Sync status and data freshness for an address | 0.025 USDC | | `GET /v1/agent/chains` | List of supported blockchain networks | 0.025 USDC | **There is no `/v1/agent/transactions`.** Transaction history is not available over x402. To read transaction history, use the API-key REST endpoint [`GET /v1/transactions`](/api/endpoints/transactions). ### Parameters EVM (`0x...`) or Solana (base58) address. Required by `/portfolio`, `/wallet`, `/nav`, and `/status`. Not used by `/chains`. Aggregate holdings across addresses. `/portfolio` only. Include token image URLs. `/portfolio` only. Include block explorer URLs. `/portfolio` only. Wait for a fresh sync before returning. `/portfolio` and `/nav`. Currency for the returned value. `/nav` only. Response bodies match their API-key equivalents — `/v1/agent/portfolio` returns the same shape as [`/v1/portfolio`](/api/endpoints/portfolio). *** ## Choosing an access method **Default to the REST API with an API key.** Reach for x402 only when the user explicitly asks for it, or when the agent has a funded wallet and cannot hold an API key. ``` Does the user explicitly want x402 / pay-per-call? ├─ Yes ─────────────────────────────────► use /v1/agent/* └─ No Does the agent have its own funded wallet AND no API key? ├─ Yes ─────────────────────────────► use /v1/agent/*, and tell the │ user transactions need a key └─ No ──────────────────────────────► use /v1/* with a Bearer token (mention x402 exists if relevant) ``` Both paths reach the same data. They differ in coverage and in what you need to get started. | | REST API key (`/v1/*`) — default | x402 (`/v1/agent/*`) | | ------------------------ | ------------------------------------------------- | ---------------------------------------------- | | **Endpoints** | 25 | 5 | | **Transaction history** | [Yes](/api/endpoints/transactions) | Not available | | **Historical snapshots** | [Yes](/api/endpoints/historical-portfolio) | Not available | | **Setup** | Account + API key + credits | Funded wallet (USDC on Base) | | **Cost** | From \$0.020 per credit ([pricing](/api/pricing)) | 0.025 USDC per call | | **Rate limit** | 360 req/min | Per payment | | **Best for** | Applications, backends, anything needing history | Autonomous agents with a wallet and no API key | Building an application — a dashboard, a tracker, a tax tool, anything that reads transaction history? Use the **API key REST API**. Get a key at [data.octav.fi](https://data.octav.fi/) and start with the [Quickstart](/api/quickstart). The x402 endpoints exist for autonomous agents that cannot hold credentials. *** ## How x402 works A request without payment returns HTTP `402 Payment Required` with a `payment-required` header containing a base64-encoded JSON payment challenge. Your x402 client reads the challenge, signs a USDC transfer, and retries the request with the payment attached. ```bash theme={null} # Unpaid request — returns 402 with the payment challenge curl -i "https://api.octav.fi/v1/agent/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" ``` The decoded challenge looks like this: ```json theme={null} { "x402Version": 2, "error": "Payment required", "resource": { "url": "/v1/agent/portfolio", "description": "Get the wallet and protocol holdings of a given address", "mimeType": "application/json" }, "accepts": [ { "scheme": "exact", "network": "eip155:8453", "amount": "25000", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "payTo": "0x35BDDe2E5F25418c4a630751603c991138Af5253", "maxTimeoutSeconds": 300, "extra": { "name": "USD Coin", "version": "2" } } ] } ``` `amount` is in USDC base units — `25000` = 0.025 USDC. `asset` is USDC on Base. *** ## Example Request ```bash CLI theme={null} # The Octav CLI handles the x402 payment flow for you octav agent portfolio --addresses 0x6426af179aabebe47666f345d69fd9079673f6cd octav agent wallet --addresses 0x6426af179aabebe47666f345d69fd9079673f6cd ``` ```javascript JavaScript theme={null} import { wrapFetchWithPayment } from 'x402-fetch'; import { privateKeyToAccount } from 'viem/accounts'; const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY); const fetchWithPayment = wrapFetchWithPayment(fetch, account); // The wrapper catches the 402, pays, and retries automatically const response = await fetchWithPayment( 'https://api.octav.fi/v1/agent/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd' ); const portfolio = await response.json(); console.log('Net worth:', portfolio[0].networth); ``` ```python Python theme={null} import requests, base64, json url = 'https://api.octav.fi/v1/agent/portfolio' params = {'addresses': '0x6426af179aabebe47666f345d69fd9079673f6cd'} response = requests.get(url, params=params) if response.status_code == 402: challenge = json.loads(base64.b64decode(response.headers['payment-required'])) print('Pay', int(challenge['accepts'][0]['amount']) / 1e6, 'USDC on Base') # Sign the payment with your x402 client, then retry with the payment header attached ``` *** ## Error Responses Expected on every unpaid request — this is the x402 handshake, not a failure. Read the `payment-required` header, settle the payment, and retry. The `/v1/agent/*` path does not exist. Only `portfolio`, `wallet`, `nav`, `status`, and `chains` are exposed over x402. **Solution:** If you need transactions, historical data, approvals, or token overview, use the [API-key REST endpoints](/api/introduction) instead. *** ## Related The full 25-endpoint API reference Transaction history — API key only `octav agent` commands with x402 built in `octav_agent_portfolio` and `octav_agent_wallet` tools # Solana Airdrop Eligibility API Source: https://docs.octav.fi/api/endpoints/airdrop Check claimable airdrops for any Solana address, including token details, unlock dates and claim links. Retrieve information about claimable airdrops for a Solana address, including token details, unlock dates, and claim links. **Cost:** 1 credit per call **Solana Only** - This endpoint currently supports Solana (SOL) addresses only **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/airdrops/airdrop). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash theme={null} GET https://api.octav.fi/v1/airdrop ``` ### Parameters Solana wallet address to check for airdrops ``` addresses=J8fo6fHGTD4egvuFE4RRQaBipfcHy5F5YVCcZmmZG6gR ``` *** ## Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/airdrop?addresses=J8fo6fHGTD4egvuFE4RRQaBipfcHy5F5YVCcZmmZG6gR" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const address = 'J8fo6fHGTD4egvuFE4RRQaBipfcHy5F5YVCcZmmZG6gR'; const response = await fetch( `https://api.octav.fi/v1/airdrop?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const airdrops = await response.json(); console.log(`Total airdrop value: $${airdrops[0].networth}`); ``` ```python Python theme={null} address = 'J8fo6fHGTD4egvuFE4RRQaBipfcHy5F5YVCcZmmZG6gR' response = requests.get( 'https://api.octav.fi/v1/airdrop', params={'addresses': address}, headers={'Authorization': f'Bearer {api_key}'} ) airdrops = response.json() print(f"Total airdrop value: ${airdrops[0]['networth']}") ``` ```typescript TypeScript theme={null} const address = 'J8fo6fHGTD4egvuFE4RRQaBipfcHy5F5YVCcZmmZG6gR'; const response = await fetch( `https://api.octav.fi/v1/airdrop?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const airdrops: Airdrop[] = await response.json(); console.log(`Total airdrop value: $${airdrops[0].networth}`); ``` *** ## Response Returns an array of portfolio objects containing airdrop data. The structure follows the same format as the [Portfolio endpoint](/api/endpoints/portfolio), but filtered to show only airdrop positions. ### Top-Level Fields The Solana wallet address Total value of claimable airdrops in USD Current price conversion rates for major tokens (SOL, ETH, BTC) Timestamp of last data update (milliseconds since epoch) Airdrop positions organized by protocol Each protocol contains an `AIRDROP` key with: * `protocolPositions[]`: Array of airdrop positions * `totalValue`: Total USD value of airdrops from this protocol * `unlockAt`: Unix timestamp when tokens unlock Chain-level summary (currently Solana only) ### Airdrop Asset Fields Each airdrop asset includes: Amount of tokens claimable Token symbol (e.g., "sns") Token full name USD value of the airdrop Current token price in USD Whether the airdrop is currently claimable URL to claim the airdrop Unix timestamp when tokens become available Token contract address Link to view token on blockchain explorer *** ## Example Response ```json theme={null} [ { "address": "J8fo6fHGTD4egvuFE4RRQaBipfcHy5F5YVCcZmmZG6gR", "networth": "29.9896511359350402985808", "conversionRates": { "SOL": "169.19", "cbBTC": "115164", "ETH": "3654.21" }, "lastUpdated": "1754497718973", "assetByProtocols": { "sns": { "name": "SNS", "key": "sns", "value": "29.9896511359350402985808", "chains": { "solana": { "protocolPositions": { "AIRDROP": { "name": "Airdrop", "totalValue": "29.9896511359350402985808", "unlockAt": "1747137600", "protocolPositions": [ { "name": "Airdrop", "value": "29.9896511359350402985808", "unlockAt": "1747137600", "assets": [ { "balance": "14671.9255", "symbol": "sns", "name": "solana name service", "value": "29.9896511359350402985808", "price": "0.0020440160451970016", "contract": "SNS8DJbHc34nKySHVhLGMUUE72ho6igvJaxtq9T3cX3", "chainKey": "solana", "isClaimable": true, "link": "https://airdrop.sns.id/", "unlockAt": "1747137600", "explorerUrl": "https://solscan.io/token/SNS8DJbHc34nKySHVhLGMUUE72ho6igvJaxtq9T3cX3", "imgSmall": "https://images.octav.fi/tokens/small/SNS8DJbHc34nKySHVhLGMUUE72ho6igvJaxtq9T3cX3_logo.png", "imgLarge": "https://images.octav.fi/tokens/small/SNS8DJbHc34nKySHVhLGMUUE72ho6igvJaxtq9T3cX3_logo.png" } ] } ] } } } } } }, "chains": { "solana": { "name": "Solana", "key": "solana", "value": "29.9896511359350402985808" } } } ] ``` *** ## Use Cases Get all claimable airdrops for an address: ```javascript theme={null} async function checkAirdrops(address) { const response = await fetch( `https://api.octav.fi/v1/airdrop?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [data] = await response.json(); console.log(`Total airdrop value: $${parseFloat(data.networth).toFixed(2)}`); // Iterate through all protocols Object.values(data.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { const airdropData = chain.protocolPositions.AIRDROP; if (airdropData) { airdropData.protocolPositions.forEach(position => { position.assets.forEach(asset => { const unlockDate = new Date(parseInt(asset.unlockAt) * 1000); console.log(` Token: ${asset.name} (${asset.symbol}) Amount: ${parseFloat(asset.balance).toFixed(4)} Value: $${parseFloat(asset.value).toFixed(2)} Claimable: ${asset.isClaimable ? 'Yes' : 'No'} Unlock Date: ${unlockDate.toLocaleDateString()} Claim Link: ${asset.link} `); }); }); } }); }); } ``` Show only currently claimable airdrops: ```javascript theme={null} async function getClaimableAirdrops(address) { const response = await fetch( `https://api.octav.fi/v1/airdrop?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [data] = await response.json(); const claimable = []; Object.values(data.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { const airdropData = chain.protocolPositions.AIRDROP; if (airdropData) { airdropData.protocolPositions.forEach(position => { position.assets.forEach(asset => { if (asset.isClaimable) { claimable.push({ name: asset.name, symbol: asset.symbol, amount: asset.balance, value: asset.value, claimLink: asset.link }); } }); }); } }); }); return claimable; } const claimable = await getClaimableAirdrops(address); console.log(`${claimable.length} airdrops ready to claim`); ``` Monitor total airdrop value over time: ```javascript theme={null} async function trackAirdropValue(address) { const response = await fetch( `https://api.octav.fi/v1/airdrop?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [data] = await response.json(); const totalValue = parseFloat(data.networth); // Calculate value by protocol const valueByProtocol = {}; Object.entries(data.assetByProtocols).forEach(([key, protocol]) => { valueByProtocol[protocol.name] = parseFloat(protocol.value); }); return { totalValue, valueByProtocol, timestamp: new Date(parseInt(data.lastUpdated)) }; } const snapshot = await trackAirdropValue(address); console.log(`Total: $${snapshot.totalValue.toFixed(2)}`); console.log('By Protocol:', snapshot.valueByProtocol); ``` Find airdrops with upcoming unlock dates: ```javascript theme={null} async function getUpcomingUnlocks(address, daysAhead = 30) { const response = await fetch( `https://api.octav.fi/v1/airdrop?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [data] = await response.json(); const now = Date.now() / 1000; const futureDate = now + (daysAhead * 24 * 60 * 60); const upcoming = []; Object.values(data.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { const airdropData = chain.protocolPositions.AIRDROP; if (airdropData) { airdropData.protocolPositions.forEach(position => { position.assets.forEach(asset => { const unlockTime = parseInt(asset.unlockAt); if (unlockTime > now && unlockTime <= futureDate) { upcoming.push({ name: asset.name, symbol: asset.symbol, value: asset.value, unlockDate: new Date(unlockTime * 1000), daysUntilUnlock: Math.ceil((unlockTime - now) / (24 * 60 * 60)) }); } }); }); } }); }); return upcoming.sort((a, b) => a.unlockDate - b.unlockDate); } const upcoming = await getUpcomingUnlocks(address, 30); upcoming.forEach(airdrop => { console.log(`${airdrop.symbol} unlocks in ${airdrop.daysUntilUnlock} days`); }); ``` *** ## Best Practices Airdrop eligibility can change frequently: * Check daily for active addresses * Set up alerts for new airdrops * Monitor unlock dates for upcoming claims Not all addresses will have airdrops: ```javascript theme={null} const [data] = await response.json(); if (!data.assetByProtocols || Object.keys(data.assetByProtocols).length === 0) { console.log('No airdrops found for this address'); return; } ``` Always verify claim URLs before using: * Check that domains match official project sites * Verify on project's official social media * Be cautious of phishing attempts Monitor unlock timestamps: ```javascript theme={null} const unlockDate = new Date(parseInt(asset.unlockAt) * 1000); const isUnlocked = unlockDate <= new Date(); ``` Set reminders for upcoming unlocks to claim promptly *** ## Response Structure Notes The airdrop endpoint returns data in the same structure as the Portfolio endpoint: * Follows the complete portfolio schema * Filtered to show only AIRDROP protocol positions * Includes all standard fields (chains, assets, protocols) * Same nested structure for consistency Airdrop positions are identified by the `AIRDROP` key: ```javascript theme={null} const airdropPositions = chain.protocolPositions.AIRDROP; ``` This key exists within the protocol's chain data All time fields use Unix timestamps (seconds since epoch): * `unlockAt`: When tokens become available * `lastUpdated`: When data was last synced (milliseconds) Convert to JavaScript Date: ```javascript theme={null} const date = new Date(parseInt(unlockAt) * 1000); ``` *** ## Related Endpoints Get complete portfolio including airdrops Detailed token breakdown across chains # Token Approvals Source: https://docs.octav.fi/api/endpoints/approvals Retrieve ERC-20 token approval records for a wallet on a given chain Retrieve ERC-20 token approval (allowance) records for a wallet on a specific chain. Each approval represents a transaction where the wallet granted a spender contract permission to transfer tokens on its behalf. **Cost:** 1 credit per call (WALLET credits) **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/approvals). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/approvals/:chain ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/approvals/ethereum?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&limit=25" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Path Parameters Chain identifier Supported chains: `arbitrum`, `avalanche`, `base`, `binance`, `ethereum`, `fantom`, `gnosis`, `linea`, `optimism`, `polygon` See [Supported Blockchains](/docs/supported-blockchains) for details *** ## Required Parameters Wallet address to fetch approvals for ``` addresses=0x6426af179aabebe47666f345d69fd9079673f6cd ``` *** ## Optional Parameters Number of results per page * **Minimum:** 1 * **Maximum:** 100 * **Default:** 25 Cursor string from a previous response to fetch the next page ``` cursor=eyJhbGciOiJIUzI1NiJ9... ``` *** ## Response ### Top-Level Fields Cursor for the next page. `null` or absent when there are no more results. Number of items returned in this page. Array of token approval records. ### Approval Object Each item in `items` contains: Block number where the approval transaction was mined. ISO 8601 timestamp of the block. Transaction hash of the approval. Address of the token contract that was approved. Wallet address that granted the approval. Spender address that received the approval. Raw approved amount (not decimal-adjusted). A max `uint256` value indicates unlimited approval. Enriched token metadata. Present only when the token is tracked in the Octav asset database. Contains: `name`, `symbol`, `balance`, `price`, `value`, `decimal`, `chain`, `contract`, `imgSmall`, `imgLarge` Follows the standard `AssetViewModel` shape used across other Octav API responses. *** ## Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/approvals/ethereum?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&limit=25" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const chain = 'ethereum'; const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const params = new URLSearchParams({ addresses: address, limit: '25' }); const response = await fetch( `https://api.octav.fi/v1/approvals/${chain}?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const data = await response.json(); console.log(`Found ${data.items.length} approvals`); ``` ```python Python theme={null} import requests chain = 'ethereum' address = '0x6426af179aabebe47666f345d69fd9079673f6cd' response = requests.get( f'https://api.octav.fi/v1/approvals/{chain}', params={ 'addresses': address, 'limit': 25 }, headers={'Authorization': f'Bearer {api_key}'} ) data = response.json() print(f"Found {len(data['items'])} approvals") ``` ```typescript TypeScript theme={null} interface Asset { name: string; symbol: string; balance: string; price: string; value: string; decimal: string; chain: string; contract: string; imgSmall?: string; imgLarge?: string; } interface Approval { block: string; timestamp: string; hash: string; contract: string; from: string; to: string; amount: string; asset?: Asset; } interface ApprovalsResponse { cursor: string | null; pageSize: number; items: Approval[]; } const chain = 'ethereum'; const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const params = new URLSearchParams({ addresses: address, limit: '25' }); const response = await fetch( `https://api.octav.fi/v1/approvals/${chain}?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const data: ApprovalsResponse = await response.json(); console.log(`Found ${data.items.length} approvals`); ``` *** ## Example Response ```json theme={null} { "cursor": "eyJhbGciOiJIUzI1NiJ9...", "pageSize": 25, "items": [ { "block": "19432156", "timestamp": "2024-03-15T10:23:47.000Z", "hash": "0xa1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "contract": "0x6b175474e89094c44da98b954eedeac495271d0f", "from": "0x6426af179aabebe47666f345d69fd9079673f6cd", "to": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", "amount": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "asset": { "name": "Dai Stablecoin", "symbol": "DAI", "balance": "1500.25", "price": "0.9998", "value": "1499.95", "decimal": "18", "chain": "ethereum", "contract": "0x6b175474e89094c44da98b954eedeac495271d0f", "imgSmall": "https://assets.coingecko.com/coins/images/9956/small/dai-multi-collateral-mcd.png", "imgLarge": "https://assets.coingecko.com/coins/images/9956/large/dai-multi-collateral-mcd.png" } }, { "block": "19215432", "timestamp": "2024-02-10T14:05:22.000Z", "hash": "0xf9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8", "contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "from": "0x6426af179aabebe47666f345d69fd9079673f6cd", "to": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", "amount": "50000000000", "asset": { "name": "USD Coin", "symbol": "USDC", "balance": "10234.50", "price": "1.0001", "value": "10235.52", "decimal": "6", "chain": "ethereum", "contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "imgSmall": "https://assets.coingecko.com/coins/images/6319/small/USD_Coin_icon.png", "imgLarge": "https://assets.coingecko.com/coins/images/6319/large/USD_Coin_icon.png" } } ] } ``` *** ## Pagination Use cursor-based pagination to iterate through results: 1. Make an initial request with just `limit` (or use the default of 25) 2. If the response contains a non-null `cursor`, pass it as the `cursor` query parameter in the next request 3. When `cursor` is `null` or absent, you have reached the end of the results ```javascript theme={null} async function getAllApprovals(chain, address) { const approvals = []; let cursor = null; do { const params = new URLSearchParams({ addresses: address, limit: '100' }); if (cursor) params.set('cursor', cursor); const response = await fetch( `https://api.octav.fi/v1/approvals/${chain}?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const data = await response.json(); approvals.push(...data.items); cursor = data.cursor; } while (cursor); return approvals; } ``` *** ## Use Cases Review all active token approvals for security: ```javascript theme={null} const MAX_UINT256 = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; async function auditApprovals(chain, address) { const approvals = await getAllApprovals(chain, address); const unlimited = approvals.filter(a => a.amount === MAX_UINT256); const limited = approvals.filter(a => a.amount !== MAX_UINT256); console.log(`Total approvals: ${approvals.length}`); console.log(`Unlimited approvals: ${unlimited.length}`); console.log(`Limited approvals: ${limited.length}`); // Flag unlimited approvals for review unlimited.forEach(approval => { console.log( `UNLIMITED: ${approval.asset?.symbol || approval.contract} -> ${approval.to}` ); }); return { unlimited, limited }; } ``` Scan approvals across all supported chains: ```javascript theme={null} const CHAINS = [ 'arbitrum', 'avalanche', 'base', 'binance', 'ethereum', 'fantom', 'gnosis', 'linea', 'optimism', 'polygon' ]; async function getMultiChainApprovals(address) { const results = {}; for (const chain of CHAINS) { const approvals = await getAllApprovals(chain, address); if (approvals.length > 0) { results[chain] = approvals; console.log(`${chain}: ${approvals.length} approvals`); } } return results; } ``` Calculate the total value of tokens with active approvals: ```javascript theme={null} async function calculateApprovalExposure(chain, address) { const approvals = await getAllApprovals(chain, address); let totalExposure = 0; approvals.forEach(approval => { if (approval.asset) { totalExposure += parseFloat(approval.asset.value); console.log( `${approval.asset.symbol}: $${approval.asset.value} approved to ${approval.to}` ); } }); console.log(`Total exposure: $${totalExposure.toFixed(2)}`); return totalExposure; } ``` Group approvals by spender contract: ```javascript theme={null} async function analyzeSpenders(chain, address) { const approvals = await getAllApprovals(chain, address); const spenders = {}; approvals.forEach(approval => { if (!spenders[approval.to]) { spenders[approval.to] = []; } spenders[approval.to].push(approval); }); // Display spender summary Object.entries(spenders) .sort((a, b) => b[1].length - a[1].length) .forEach(([spender, apps]) => { const tokens = apps .map(a => a.asset?.symbol || 'Unknown') .join(', '); console.log(`${spender}: ${apps.length} tokens (${tokens})`); }); return spenders; } ``` *** ## Error Responses Invalid parameters provided. ```json theme={null} { "message": "chain must be one of: arbitrum, avalanche, base, binance, ethereum, fantom, gnosis, linea, optimism, polygon" } ``` **Common causes:** * Invalid or unsupported chain in URL path * Missing `addresses` query parameter * `limit` out of range (must be 1-100) Authentication failed. ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **Solution:** Check your API key in the Authorization header Insufficient credits. ```json theme={null} { "error": "Insufficient credits", "message": "Please purchase more credits to continue" } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) Upstream data provider is unavailable. ```json theme={null} { "message": "Failed to fetch token approvals from upstream provider" } ``` **Solution:** Retry after a short delay. If the issue persists, the upstream provider may be experiencing an outage. *** ## Related Endpoints View current portfolio holdings Query full transaction history Get wallet balances across protocols Detailed token breakdown by protocol # Beacon Validators Source: https://docs.octav.fi/api/endpoints/beacon-validators Ethereum validator details, rewards, withdrawals, and deposits sourced from the Ethereum beacon chain Query Ethereum beacon chain data for individual validators by index or BLS pubkey. Mainnet only. Beacon validator endpoints are an **add-on**, billed at a set monthly fee separate from your API credit pool. Contact sales to enable beacon access for your account. **Cost:** Set monthly fee under the beacon add-on — no per-call credit deduction. **Rate limit:** 100 requests/min, on a dedicated beacon bucket (does not count against your portfolio rate limit). *** ## Path parameters All endpoints accept one of two path-parameter forms: | Parameter | Type | Description | | --------- | ------- | -------------------------------------------------------------------------------------- | | `:index` | integer | Non-negative validator index | | `:pubkey` | string | 48-byte BLS pubkey, hex-encoded with `0x` prefix (96 hex characters). Case-insensitive | > **Note:** BLS pubkeys are **validator identifiers**, not wallet addresses. *** ## Validator Details Get current state, lifecycle epochs, effective balance, and withdrawal credentials for a single validator. ### Endpoints ```bash theme={null} GET https://api.octav.fi/v1/beacon/validators/details/index/:index GET https://api.octav.fi/v1/beacon/validators/details/pubkey/:pubkey ``` ### Response Core validator identity and status. Validator index BLS pubkey (lowercase 0x-prefixed) Whether the validator has been slashed Validator lifecycle status. One of: `pending` | `active_ongoing` | `active_exiting` | `active_slashed` | `exited_unslashed` | `exited_slashed` | `withdrawal_possible` | `withdrawal_done` Epoch-level lifecycle milestones. Fields are `null` when not yet reached. Epoch when the validator became active Unix timestamp of activation Epoch when the validator exited (or will exit) Epoch when withdrawal becomes possible Validator balances as wei strings. Current balance in wei Effective balance in wei (used for reward/penalty calculations) Withdrawal credential info. `address` is only present for 0x01-prefix validators. Credential prefix byte (0 = BLS, 1 = EVM address) Full credential hex string EVM withdrawal address (0x01 only, otherwise null) ### Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/beacon/validators/details/index/123456" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://api.octav.fi/v1/beacon/validators/details/index/123456', { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const details = await response.json(); console.log(`Status: ${details.validator.status}`); console.log(`Actual balance: ${details.balance.actual} wei`); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.octav.fi/v1/beacon/validators/details/index/123456', headers={'Authorization': f'Bearer {api_key}'} ) details = response.json() print(f"Status: {details['validator']['status']}") ``` ```json theme={null} { "validator": { "index": 123456, "pubkey": "0xb03d97937ae39b38a44f90141dcf7f0420c3b9b21caee1a7603046bc9237f5c96b61714474249dafe0a20af583a4fc07", "slashed": false, "status": "active_ongoing" }, "lifecycle": { "activationEpoch": 34567, "activationTimestamp": 1672531200, "exitEpoch": null, "withdrawableEpoch": null }, "balance": { "actual": "32045123456789", "effective": "32000000000000" }, "withdrawalCredentials": { "prefix": 1, "credential": "0x0100000000000000000000000a8c8aaa3f2ddf1b22a3f5b2a2e9b7d8c6f4e1a2", "address": "0x0a8c8aaa3f2ddf1b22a3f5b2a2e9b7d8c6f4e1a2" } } ``` *** ## Validator Rewards Paginated reward buckets aggregated by `epoch`, `day`, `week`, or `month` for a time range. Typical use case: pull daily rewards for a known validator pubkey for portfolio reporting (Lido, Beaver, etc.). **Data availability:** Rewards are available from **the Merge** (epoch 146875, September 15, 2022) onward. If you need data going further back, contact us. ### Endpoints ```bash theme={null} GET https://api.octav.fi/v1/beacon/validators/rewards/index/:index GET https://api.octav.fi/v1/beacon/validators/rewards/pubkey/:pubkey ``` ### Query Parameters Determines how `rangeFrom` / `rangeTo` are interpreted. * `timestamp` — values are Unix seconds. Max range: 31,536,000 seconds (1 year). * `epoch` — values are beacon epoch numbers. Max range: 82,125 epochs (1 year). Start of the range (inclusive). Must be ≥ 0 and ≤ `rangeTo`. Interpreted as Unix seconds or epoch number per `rangeType`. End of the range (inclusive). Must be ≥ 0. Interpreted as Unix seconds or epoch number per `rangeType`. Maximum 1-year range. Cap is **31,536,000 seconds** when `rangeType=timestamp`, **82,125 epochs** when `rangeType=epoch`. For better performance, keep ranges as small as your use case allows. Bucket size for aggregation. One of: `epoch` | `day` | `week` | `month` Pagination offset (number of records to skip). Number of records to return. Max `10`. ### Response Array of reward buckets. First epoch in this bucket Last epoch in this bucket Unix timestamp of bucket start Unix timestamp of bucket end ISO date string of bucket start ISO date string of bucket end Consensus layer reward in wei Maximum possible consensus reward in wei Ratio of actual to potential consensus reward (0–1), or null if not computable Execution layer (MEV + tips) reward in wei Number of attestations included on-chain Number of attestations missed Sync committee participations Sync committee slots missed Blocks proposed Block proposals missed Blocks proposed with MEV Effective balance at the end of the bucket, in wei Current pagination offset Records per page Total number of matching buckets Total number of pages ### Example ```bash cURL (timestamp mode) theme={null} curl "https://api.octav.fi/v1/beacon/validators/rewards/index/123456?rangeType=timestamp&rangeFrom=1704067200&rangeTo=1706745600&granularity=day&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```bash cURL (epoch mode) theme={null} curl "https://api.octav.fi/v1/beacon/validators/rewards/index/123456?rangeType=epoch&rangeFrom=231200&rangeTo=231424&granularity=epoch&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} // timestamp mode const params = new URLSearchParams({ rangeType: 'timestamp', rangeFrom: '1704067200', rangeTo: '1706745600', granularity: 'day', limit: '10' }); const response = await fetch( `https://api.octav.fi/v1/beacon/validators/rewards/index/123456?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const rewards = await response.json(); rewards.data.forEach(bucket => { console.log(`${bucket.dateStart}: consensus ${bucket.consensusReward} wei`); }); ``` ```python Python theme={null} import requests # epoch mode response = requests.get( 'https://api.octav.fi/v1/beacon/validators/rewards/index/123456', params={ 'rangeType': 'epoch', 'rangeFrom': 231200, 'rangeTo': 231424, 'granularity': 'epoch', 'limit': 10 }, headers={'Authorization': f'Bearer {api_key}'} ) data = response.json() for bucket in data['data']: print(f"{bucket['dateStart']}: {bucket['consensusReward']} wei") ``` ```json theme={null} { "data": [ { "epochStart": 231200, "epochEnd": 231424, "timestampStart": 1704067200, "timestampEnd": 1704153600, "dateStart": "2024-01-01", "dateEnd": "2024-01-02", "consensusReward": "2450000000000", "consensusPotentialReward": "2500000000000", "consensusEfficiency": 0.98, "executionReward": "125000000000", "attestationsIncluded": 224, "attestationsMissed": 0, "syncParticipations": 0, "syncMissed": 0, "proposedBlocks": 0, "missedBlocks": 0, "mevBlocks": 0, "endEffectiveBalance": "32000000000000" } ], "offset": 0, "limit": 10, "totalRows": 31, "pages": 4 } ``` *** ## Validator Withdrawals Paginated list of withdrawals processed for a validator. ### Endpoints ```bash theme={null} GET https://api.octav.fi/v1/beacon/validators/withdrawals/index/:index GET https://api.octav.fi/v1/beacon/validators/withdrawals/pubkey/:pubkey ``` ### Query Parameters Pagination offset. Number of records to return. Max `10`. ### Response Array of withdrawal events. Slot in which the withdrawal was processed Epoch containing the slot Unix timestamp of the withdrawal Withdrawn amount in wei EVM address that received the withdrawal Validator index Current pagination offset Records per page Total number of withdrawals Total number of pages ### Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/beacon/validators/withdrawals/index/123456?limit=10&offset=0" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://api.octav.fi/v1/beacon/validators/withdrawals/index/123456?limit=10&offset=0', { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const withdrawals = await response.json(); console.log(`Total withdrawals: ${withdrawals.totalRows}`); ``` ```json theme={null} { "data": [ { "slot": 8901234, "epoch": 278163, "timestamp": 1706745600, "amount": "32000000000000", "address": "0x0a8c8aaa3f2ddf1b22a3f5b2a2e9b7d8c6f4e1a2", "index": 123456 } ], "offset": 0, "limit": 10, "totalRows": 3, "pages": 1 } ``` *** ## Validator Deposits Paginated list of deposits made to a validator. ### Endpoints ```bash theme={null} GET https://api.octav.fi/v1/beacon/validators/deposits/index/:index GET https://api.octav.fi/v1/beacon/validators/deposits/pubkey/:pubkey ``` ### Query Parameters Pagination offset. Number of records to return. Max `10`. ### Response Array of deposit events. Slot in which the deposit was included Epoch containing the slot Unix timestamp of the deposit Deposited amount in wei Global deposit contract index Full withdrawal credential hex string EVM withdrawal address (0x01 prefix only, otherwise null) Current pagination offset Records per page Total number of deposits Total number of pages ### Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/beacon/validators/deposits/pubkey/0xb03d97937ae39b38a44f90141dcf7f0420c3b9b21caee1a7603046bc9237f5c96b61714474249dafe0a20af583a4fc07?limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const pubkey = '0xb03d97937ae39b38a44f90141dcf7f0420c3b9b21caee1a7603046bc9237f5c96b61714474249dafe0a20af583a4fc07'; const response = await fetch( `https://api.octav.fi/v1/beacon/validators/deposits/pubkey/${pubkey}?limit=10`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const deposits = await response.json(); console.log(`Total deposits: ${deposits.totalRows}`); ``` ```json theme={null} { "data": [ { "slot": 1234567, "epoch": 38579, "timestamp": 1634567890, "amount": "32000000000000000000", "depositIndex": 456789, "withdrawalCredentials": "0x0100000000000000000000000a8c8aaa3f2ddf1b22a3f5b2a2e9b7d8c6f4e1a2", "withdrawalAddress": "0x0a8c8aaa3f2ddf1b22a3f5b2a2e9b7d8c6f4e1a2" } ], "offset": 0, "limit": 10, "totalRows": 1, "pages": 1 } ``` *** ## Error Responses Input validation failed (Joi structured error). ```json theme={null} { "error": "\"index\" must be a non-negative integer" } ``` Common causes: invalid `:index` or `:pubkey` format, missing required query params, `rangeFrom` > `rangeTo`, range exceeding 1-year cap, invalid `rangeType` or `granularity` value. Missing or invalid Bearer token. ```json theme={null} { "message": "Unauthorized" } ``` Valid token but account does not have enterprise beacon access. ```json theme={null} { "error": "Beacon access requires enterprise subscription. Contact sales." } ``` Validator does not exist on mainnet. ```json theme={null} { "error": "Validator not found for index 99999999" } ``` Upstream beacon data error. ``` Error fetching validator details ``` *** ## Use Cases Aggregate daily rewards across a set of validators and convert wei to ETH for portfolio reporting. ```javascript theme={null} async function getDailyStakingYield(pubkeys, fromTs, toTs) { const buckets = {}; for (const pubkey of pubkeys) { const params = new URLSearchParams({ rangeType: 'timestamp', rangeFrom: String(fromTs), rangeTo: String(toTs), granularity: 'day', limit: '10' }); const response = await fetch( `https://api.octav.fi/v1/beacon/validators/rewards/pubkey/${pubkey}?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); data.forEach(b => { const total = BigInt(b.consensusReward) + BigInt(b.executionReward); buckets[b.dateStart] = (buckets[b.dateStart] ?? 0n) + total; }); } return Object.entries(buckets).map(([date, wei]) => ({ date, eth: Number(wei) / 1e18 })); } ``` Read the canonical `validator.status` value to drive lifecycle UI or alerting. ```javascript theme={null} export const ValidatorStatus = { Pending: 'pending', ActiveOngoing: 'active_ongoing', ActiveExiting: 'active_exiting', ActiveSlashed: 'active_slashed', ExitedUnslashed: 'exited_unslashed', ExitedSlashed: 'exited_slashed', WithdrawalPossible: 'withdrawal_possible', WithdrawalDone: 'withdrawal_done', } as const; async function getValidatorStatus(index) { const response = await fetch( `https://api.octav.fi/v1/beacon/validators/details/index/${index}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { validator } = await response.json(); if (validator.status === ValidatorStatus.ActiveSlashed || validator.status === ValidatorStatus.ExitedSlashed) { alert(`Validator ${validator.index} has been slashed`); } return validator.status; } ``` Paginate every withdrawal for a validator and bucket totals by recipient address (useful for tax reporting and proof-of-reserves). ```javascript theme={null} async function getWithdrawalsByAddress(index) { const totals = {}; let offset = 0; while (true) { const response = await fetch( `https://api.octav.fi/v1/beacon/validators/withdrawals/index/${index}?limit=10&offset=${offset}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data, totalRows } = await response.json(); data.forEach(w => { totals[w.address] = (totals[w.address] ?? 0n) + BigInt(w.amount); }); offset += data.length; if (offset >= totalRows || data.length === 0) break; } return Object.entries(totals).map(([address, wei]) => ({ address, eth: Number(wei) / 1e18 })); } ``` *** ## Best Practices `validator.status`, `lifecycle.*`, and `withdrawalCredentials.*` change on the order of minutes-to-hours, not seconds. Cache details responses for several minutes to cut latency and load. ```javascript theme={null} const detailsCache = new Map(); // index -> { value, expiresAt } const TTL_MS = 5 * 60 * 1000; async function getValidatorDetails(index) { const cached = detailsCache.get(index); if (cached && cached.expiresAt > Date.now()) return cached.value; const response = await fetch( `https://api.octav.fi/v1/beacon/validators/details/index/${index}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const value = await response.json(); detailsCache.set(index, { value, expiresAt: Date.now() + TTL_MS }); return value; } ``` Reward buckets at `granularity=epoch` over a one-year range can yield \~82,000 rows. Match the granularity to the range so a single query returns a meaningful page. | Range | Suggested granularity | | ----------------- | --------------------- | | \< 1 day | `epoch` | | 1–30 days | `day` | | 1–6 months | `week` | | 6 months – 1 year | `month` | Going finer than this still works, but you'll need to paginate (`limit` is capped at 10) and stitch results together. Reward and balance fields are returned as decimal strings in wei. Summing across long ranges or many validators can exceed `Number.MAX_SAFE_INTEGER` — parse with `BigInt`, divide by `10^18` only at the display step. ```javascript theme={null} const totalWei = rewards.data.reduce( (sum, b) => sum + BigInt(b.consensusReward) + BigInt(b.executionReward), 0n ); const totalEth = Number(totalWei) / 1e18; ``` `limit` is capped at **10** for rewards, withdrawals, and deposits. Loop on `offset` until you've consumed `totalRows`. ```javascript theme={null} async function fetchAll(url) { const all = []; let offset = 0; while (true) { const sep = url.includes('?') ? '&' : '?'; const response = await fetch( `${url}${sep}limit=10&offset=${offset}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data, totalRows } = await response.json(); all.push(...data); offset += data.length; if (offset >= totalRows || data.length === 0) break; } return all; } ``` *** ## Related Endpoints Get full portfolio holdings for a wallet address View on-chain transaction history # Bundles Source: https://docs.octav.fi/api/endpoints/bundles Group saved addresses into named bundles and manage their membership Group addresses into named bundles — the same bundles you see in [Octav Pro](https://pro.octav.fi). **Cost:** 1 credit per call Every address in a bundle must already be in your [address book](/api/endpoints/addressbook). **Interactive Playground:** Test these endpoints in the [API Playground](/api-reference/bundles). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## The bundle object Bundle identifier. Bundle name. Addresses in the bundle. ```json theme={null} { "id": "7714a4a5-487b-4b36-99ba-537e9cca8e62", "name": "Client A", "addresses": [ "0xe760ad8df0a54aafd95e3bc271b65d224abeaa57", "0x05d2e386525b0495e952f6945d0fa2480ed5b056" ] } ``` Bundles created through the API are private. Sharing settings are managed in [Octav Pro](https://pro.octav.fi). *** ## List Bundles ### Endpoint ```bash theme={null} GET https://api.octav.fi/v1/bundles ``` ### Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/bundles" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/bundles', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const { data } = await response.json(); ``` ### Example Response ```json theme={null} { "data": [ { "id": "7714a4a5-487b-4b36-99ba-537e9cca8e62", "name": "Client A", "addresses": ["0xe760ad8df0a54aafd95e3bc271b65d224abeaa57"] } ] } ``` *** ## Create Bundle ### Endpoint ```bash theme={null} POST https://api.octav.fi/v1/bundles ``` ### Parameters 1–255 characters. Must be unique. 1–100 addresses per request, each already in your address book. A bundle holds up to 10. ### Example ```bash cURL theme={null} curl -X POST "https://api.octav.fi/v1/bundles" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Client A", "addresses": ["0xe760ad8df0a54aafd95e3bc271b65d224abeaa57"] }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/bundles', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Client A', addresses: ['0xe760ad8df0a54aafd95e3bc271b65d224abeaa57'] }) }); const { data } = await response.json(); ``` ### Response `201` with the new bundle. *** ## Get Bundle ### Endpoint ```bash theme={null} GET https://api.octav.fi/v1/bundles/{bundleId} ``` ### Parameters Bundle identifier. ### Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.octav.fi/v1/bundles/${bundleId}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); ``` *** ## Rename Bundle ### Endpoint ```bash theme={null} PATCH https://api.octav.fi/v1/bundles/{bundleId} ``` ### Parameters Bundle identifier. 1–255 characters. Must be unique. ### Example ```bash cURL theme={null} curl -X PATCH "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Client A — Q1" }' ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.octav.fi/v1/bundles/${bundleId}`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Client A — Q1' }) } ); const { data } = await response.json(); ``` *** ## Delete Bundle ### Endpoint ```bash theme={null} DELETE https://api.octav.fi/v1/bundles/{bundleId} ``` ### Parameters Bundle identifier. ### Example ```bash cURL theme={null} curl -X DELETE "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} await fetch(`https://api.octav.fi/v1/bundles/${bundleId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${apiKey}` } }); ``` ### Response `204` with no body. The addresses stay in your address book. *** ## Add Address to Bundle ### Endpoint ```bash theme={null} POST https://api.octav.fi/v1/bundles/{bundleId}/addresses ``` ### Parameters Bundle identifier. Wallet address, already in your address book. ### Example ```bash cURL theme={null} curl -X POST "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62/addresses" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "0x05d2e386525b0495e952f6945d0fa2480ed5b056" }' ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.octav.fi/v1/bundles/${bundleId}/addresses`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ address: '0x05d2e386525b0495e952f6945d0fa2480ed5b056' }) } ); const { data } = await response.json(); ``` ### Response Returns the updated bundle. *** ## Remove Address from Bundle ### Endpoint ```bash theme={null} DELETE https://api.octav.fi/v1/bundles/{bundleId}/addresses/{address} ``` ### Parameters Bundle identifier. Wallet address. ### Example ```bash cURL theme={null} curl -X DELETE "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62/addresses/0x05d2e386525b0495e952f6945d0fa2480ed5b056" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.octav.fi/v1/bundles/${bundleId}/addresses/${address}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); ``` ### Response Returns the updated bundle with `200`. Removing the last member returns `422` — delete the bundle instead. *** ## Limits | Limit | Default | | -------------------- | ------- | | Bundles | 5 | | Addresses per bundle | 10 | Contact us to raise these. Bundle names must be unique and match case-sensitively. Two bundles cannot hold exactly the same addresses. *** ## Error Responses | Code | Status | Meaning | | ------------------------------------ | ------ | ------------------------------------ | | `VALIDATION_ERROR` | 400 | Invalid or duplicate address | | `BUNDLE_NOT_FOUND` | 404 | No such bundle | | `BUNDLE_NAME_TAKEN` | 409 | Name already used | | `BUNDLE_MEMBERS_ALREADY_BUNDLED` | 409 | Another bundle holds these addresses | | `BUNDLE_QUOTA_REACHED` | 422 | Bundle limit reached | | `BUNDLE_SIZE_LIMIT_EXCEEDED` | 422 | Address limit reached | | `BUNDLE_EMPTY` | 422 | A bundle cannot be empty | | `BUNDLE_ADDRESS_NOT_IN_ADDRESS_BOOK` | 422 | Address is not in your address book | | `INTERNAL_ERROR` | 500 | Unexpected failure | ```json theme={null} { "error": { "code": "BUNDLE_ADDRESS_NOT_IN_ADDRESS_BOOK", "message": "Not in the address book: 0x05d2e386...", "addresses": ["0x05d2e386525b0495e952f6945d0fa2480ed5b056"] } } ``` **Rate limit:** 360 requests/min. *** ## Related Endpoints Manage saved addresses Holdings for an address # Chains Source: https://docs.octav.fi/api/endpoints/chains Get all supported blockchain networks Retrieve a list of all blockchain networks supported by Octav, including chain metadata, icons, and explorer URLs. Results are sorted alphabetically by name. **Cost:** Free (0 credits) **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/chains). Get your API key at [data.octav.fi](https://data.octav.fi/) **Tip:** Use this endpoint to discover valid `chainKey` values for other endpoints like [Protocols](/api/endpoints/protocols). The chain list is cached for 24 hours — newly added chains may take up to 24h to appear. *** ## Endpoint ```bash theme={null} GET https://api.octav.fi/v1/chains ``` No parameters required. *** ## Response Returns an array of supported chain objects. ### Chain Fields Blockchain network ID (e.g. `"1"` for Ethereum mainnet) Short identifier used in API requests (e.g. `"ethereum"`, `"solana"`, `"arbitrum"`) Human-readable chain name (e.g. `"Ethereum"`, `"Solana"`) Native token symbol (e.g. `"ETH"`, `"SOL"`) Hex color code for UI display (e.g. `"#627EEA"`) URL to small chain icon URL to large chain icon Block explorer URL template for tokens Block explorer URL template for transactions Block explorer URL template for addresses Blockscout explorer URL for addresses (null if unavailable) Blockscout explorer URL for tokens (null if unavailable) Blockscout explorer URL for transactions (null if unavailable) Whether portfolio tracking is supported on this chain Whether transaction history is supported on this chain *** ## Example Request ```bash cURL theme={null} curl "https://api.octav.fi/v1/chains" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/chains', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const chains = await response.json(); console.log(`Supported chains: ${chains.length}`); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.octav.fi/v1/chains', headers={'Authorization': f'Bearer {api_key}'} ) chains = response.json() print(f'Supported chains: {len(chains)}') ``` ```typescript TypeScript theme={null} interface Chain { chainId: string; key: string; name: string; symbol: string; color: string; imgSmall: string; imgLarge: string; explorerTokenUrl: string; explorerTransactionUrl: string; explorerAddressUrl: string; blockscoutExplorerAddressUrl: string | null; blockscoutExplorerTokenUrl: string | null; blockscoutExplorerTransactionUrl: string | null; isPortfolioSupported: boolean; isTransactionsSupported: boolean; } const response = await fetch('https://api.octav.fi/v1/chains', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const chains: Chain[] = await response.json(); console.log(`Supported chains: ${chains.length}`); ``` *** ## Example Response ```json theme={null} [ { "chainId": "1", "key": "ethereum", "name": "Ethereum", "symbol": "ETH", "color": "#627EEA", "imgSmall": "https://images.octav.fi/chains/ethereum_icon.svg", "imgLarge": "https://images.octav.fi/chains/ethereum_icon.svg", "explorerTokenUrl": "https://etherscan.io/token/", "explorerTransactionUrl": "https://etherscan.io/tx/", "explorerAddressUrl": "https://etherscan.io/address/", "blockscoutExplorerAddressUrl": null, "blockscoutExplorerTokenUrl": null, "blockscoutExplorerTransactionUrl": null, "isPortfolioSupported": true, "isTransactionsSupported": true }, { "chainId": "42161", "key": "arbitrum", "name": "Arbitrum", "symbol": "ETH", "color": "#28A0F0", "imgSmall": "https://images.octav.fi/chains/arbitrum_icon.svg", "imgLarge": "https://images.octav.fi/chains/arbitrum_icon.svg", "explorerTokenUrl": "https://arbiscan.io/token/", "explorerTransactionUrl": "https://arbiscan.io/tx/", "explorerAddressUrl": "https://arbiscan.io/address/", "blockscoutExplorerAddressUrl": null, "blockscoutExplorerTokenUrl": null, "blockscoutExplorerTransactionUrl": null, "isPortfolioSupported": true, "isTransactionsSupported": true } ] ``` *** ## Use Cases Populate a chain dropdown in your UI: ```javascript theme={null} const response = await fetch('https://api.octav.fi/v1/chains', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const chains = await response.json(); // Filter to chains with portfolio support const portfolioChains = chains.filter(c => c.isPortfolioSupported); // Build selector options const options = portfolioChains.map(chain => ({ value: chain.key, label: chain.name, icon: chain.imgSmall, color: chain.color })); ``` Look up valid chain keys for use with other endpoints: ```javascript theme={null} const response = await fetch('https://api.octav.fi/v1/chains', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const chains = await response.json(); // Get all valid chain keys const chainKeys = chains.map(c => c.key); console.log('Valid chain keys:', chainKeys.join(', ')); // Check if a chain key is valid before querying protocols function isValidChain(key) { return chainKeys.includes(key); } ``` Generate block explorer links for addresses and transactions: ```javascript theme={null} const response = await fetch('https://api.octav.fi/v1/chains', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const chains = await response.json(); const chainMap = Object.fromEntries(chains.map(c => [c.key, c])); function getExplorerUrl(chainKey, type, hash) { const chain = chainMap[chainKey]; if (!chain) return null; switch (type) { case 'address': return chain.explorerAddressUrl + hash; case 'tx': return chain.explorerTransactionUrl + hash; case 'token': return chain.explorerTokenUrl + hash; } } // Example const url = getExplorerUrl('ethereum', 'address', '0x6426af...'); // => "https://etherscan.io/address/0x6426af..." ``` *** ## Error Responses Authentication failed. ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **Solution:** Check your API key in the Authorization header Rate limit exceeded. ```json theme={null} { "error": "Rate limit exceeded", "message": "You have exceeded your rate limit", "retry_after": 60 } ``` **Solution:** Wait for the specified time or implement retry logic *** ## Related Endpoints List DeFi protocols available on a chain Get portfolio holdings across chains Full chain support matrix Query transaction history by chain # Contract Protocol Source: https://docs.octav.fi/api/endpoints/contract-protocol Look up which DeFi protocol a contract address belongs to Resolve a contract address to the DeFi protocol it belongs to. Provide a `chain` to look up a single protocol on that chain, or omit it to search the address across every chain Octav knows it on. **Cost:** 5 credits per lookup. The credits are **refunded** if no protocol is found (404). **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/contract-protocol/contract-protocol). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/contract-protocol ``` ```bash Example theme={null} curl "https://api.octav.fi/v1/contract-protocol?contract=0x1f98431c8ad98523631ae4a59f267346ea31f984&chain=ethereum" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Parameters The contract address to look up. Accepts an EVM address (`0x…`) or a Solana base58 address. ``` contract=0x1f98431c8ad98523631ae4a59f267346ea31f984 ``` Chain key (e.g. `ethereum`, `arbitrum`, `base`, `polygon`, `solana`). Match a chain's `key` field from the [Chains](/api/endpoints/chains) endpoint. If omitted, the endpoint returns **every chain** the address is known on. *** ## Response The response shape depends on whether you provide `chain`: * **`chain` provided** → a single `{ protocol }` object. * **`chain` omitted** → a `{ protocols }` array, where each entry is additionally tagged with its `chainKey`. The array is unordered. Because the top-level field differs (`protocol` vs `protocols`), branch on which key is present rather than assuming a fixed shape. ### Protocol Fields Protocol display name (e.g. `"Uniswap V3"`) Protocol key (e.g. `"uniswap-v3"`) URL to small protocol icon URL to large protocol icon Chain the match belongs to. **Only present** in the array response (when `chain` is omitted). *** ## Example Request ```bash cURL theme={null} # Single chain curl "https://api.octav.fi/v1/contract-protocol?contract=0x1f98431c8ad98523631ae4a59f267346ea31f984&chain=ethereum" \ -H "Authorization: Bearer YOUR_API_KEY" # All chains (address only) curl "https://api.octav.fi/v1/contract-protocol?contract=0x1f98431c8ad98523631ae4a59f267346ea31f984" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} async function getContractProtocol(contract, chain) { const params = new URLSearchParams({ contract }); if (chain) params.set('chain', chain); const response = await fetch( `https://api.octav.fi/v1/contract-protocol?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const data = await response.json(); // Single match when chain is given, array of matches otherwise return data.protocol ?? data.protocols; } const single = await getContractProtocol( '0x1f98431c8ad98523631ae4a59f267346ea31f984', 'ethereum' ); const allChains = await getContractProtocol( '0x1f98431c8ad98523631ae4a59f267346ea31f984' ); ``` ```python Python theme={null} import requests def get_contract_protocol(contract, chain=None): params = {'contract': contract} if chain: params['chain'] = chain response = requests.get( 'https://api.octav.fi/v1/contract-protocol', params=params, headers={'Authorization': f'Bearer {api_key}'} ) data = response.json() # Single match when chain is given, array of matches otherwise return data.get('protocol') or data.get('protocols') single = get_contract_protocol( '0x1f98431c8ad98523631ae4a59f267346ea31f984', chain='ethereum' ) all_chains = get_contract_protocol( '0x1f98431c8ad98523631ae4a59f267346ea31f984' ) ``` ```typescript TypeScript theme={null} interface Protocol { name: string; key: string; imgSmall: string; imgLarge: string; } interface ProtocolWithChain extends Protocol { chainKey: string; } type SingleResponse = { protocol: Protocol }; type MultiResponse = { protocols: ProtocolWithChain[] }; async function getContractProtocol( contract: string, chain?: string ): Promise { const params = new URLSearchParams({ contract }); if (chain) params.set('chain', chain); const response = await fetch( `https://api.octav.fi/v1/contract-protocol?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const data: SingleResponse | MultiResponse = await response.json(); return 'protocol' in data ? data.protocol : data.protocols; } ``` *** ## Example Response ```json chain provided (single match) theme={null} { "protocol": { "name": "Uniswap V3", "key": "uniswap-v3", "imgSmall": "https://images.octav.fi/...small.png", "imgLarge": "https://images.octav.fi/...large.png" } } ``` ```json chain omitted (all matches) theme={null} { "protocols": [ { "name": "Uniswap V3", "key": "uniswap-v3", "imgSmall": "https://images.octav.fi/...small.png", "imgLarge": "https://images.octav.fi/...large.png", "chainKey": "ethereum" }, { "name": "Uniswap V3", "key": "uniswap-v3", "imgSmall": "https://images.octav.fi/...small.png", "imgLarge": "https://images.octav.fi/...large.png", "chainKey": "arbitrum" } ] } ``` *** ## Error Responses The `contract` parameter is missing. ```json theme={null} { "error": "Validation Failed", "message": "\"contract\" is required" } ``` Missing/invalid API key, or your key has no access to this endpoint. ```json theme={null} { "message": "Unauthorized" } ``` **Solution:** Check your API key in the Authorization header. Insufficient credits. ```json theme={null} { "message": "Insufficient credits", "creditsNeeded": 5 } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) No protocol matches the contract address. **The credits are refunded.** ```json theme={null} { "message": "No protocol found for contract 0x1f98... [on ethereum]" } ``` Rate limit exceeded. ```json theme={null} { "message": "Rate limit exceeded", "retryAfter": 60 } ``` **Solution:** Wait for the specified time or implement retry logic. Unexpected server error. ```json theme={null} { "message": "Error while searching for contract protocol" } ``` *** ## Related Endpoints List all DeFi protocols on a specific chain Get all supported chains and their keys Get portfolio holdings by protocol View all protocol type categories # Credits Source: https://docs.octav.fi/api/endpoints/credits Check your remaining credit balance Check your remaining API credit balance at any time without consuming credits. **Cost:** Free (0 credits) **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/credits). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash theme={null} GET https://api.octav.fi/v1/credits ``` ## No parameters required. ## Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/credits" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const credits = await response.json(); console.log(`Remaining credits: ${credits}`); ``` ```python Python theme={null} response = requests.get( 'https://api.octav.fi/v1/credits', headers={'Authorization': f'Bearer {api_key}'} ) credits = response.json() print(f'Remaining credits: {credits}') ``` ```typescript TypeScript theme={null} const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const credits: number = await response.json(); console.log(`Remaining credits: ${credits}`); ``` *** ## Response Returns a simple number representing your remaining credits. ```json theme={null} 19033 ``` *** ## Use Cases Check credits before expensive operations: ```javascript theme={null} async function checkCreditsBeforeSync(address) { // Check available credits const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const credits = await response.json(); console.log(`Available credits: ${credits}`); // Estimate sync cost (rough estimate) const statusResponse = await fetch( `https://api.octav.fi/v1/status?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [status] = await statusResponse.json(); if (credits < 50) { console.warn('Low credits! Consider purchasing more.'); return false; } // Proceed with sync await fetch('https://api.octav.fi/v1/sync-transactions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: [address] }) }); return true; } ``` Set up alerts for low credit balance: ```javascript theme={null} async function monitorCredits(threshold = 1000) { const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const credits = await response.json(); if (credits < threshold) { console.warn(`⚠️ Low credit balance: ${credits}`); console.warn(`Purchase more at https://data.octav.fi/`); // Send notification (email, Slack, etc.) await sendAlert({ message: `API credits low: ${credits} remaining`, severity: 'warning' }); } else { console.log(`✓ Credit balance healthy: ${credits}`); } return credits; } // Run periodically setInterval(() => monitorCredits(1000), 3600000); // Every hour ``` Track credit consumption over time: ```javascript theme={null} class CreditTracker { constructor(apiKey) { this.apiKey = apiKey; this.snapshots = []; } async snapshot() { const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${this.apiKey}` } }); const credits = await response.json(); const snapshot = { timestamp: new Date().toISOString(), credits }; this.snapshots.push(snapshot); return snapshot; } getUsageSince(timestamp) { const snapshots = this.snapshots.filter( s => new Date(s.timestamp) >= new Date(timestamp) ); if (snapshots.length < 2) return 0; return snapshots[0].credits - snapshots[snapshots.length - 1].credits; } async getCurrentUsageRate() { // Take snapshots 1 minute apart const snapshot1 = await this.snapshot(); await new Promise(resolve => setTimeout(resolve, 60000)); const snapshot2 = await this.snapshot(); const creditsUsed = snapshot1.credits - snapshot2.credits; const creditsPerHour = creditsUsed * 60; console.log(`Usage rate: ${creditsUsed} credits/min`); console.log(`Projected: ${creditsPerHour} credits/hour`); console.log(`Days remaining: ${(snapshot2.credits / (creditsPerHour * 24)).toFixed(1)}`); return { creditsPerMinute: creditsUsed, creditsPerHour, daysRemaining: snapshot2.credits / (creditsPerHour * 24) }; } } const tracker = new CreditTracker(apiKey); const usage = await tracker.getCurrentUsageRate(); ``` Implement credit budgets: ```javascript theme={null} class CreditBudget { constructor(apiKey, dailyBudget) { this.apiKey = apiKey; this.dailyBudget = dailyBudget; this.startCredits = null; this.startDate = null; } async initialize() { const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${this.apiKey}` } }); this.startCredits = await response.json(); this.startDate = new Date(); console.log(`Budget initialized: ${this.dailyBudget} credits/day`); console.log(`Starting balance: ${this.startCredits}`); } async checkBudget() { const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${this.apiKey}` } }); const currentCredits = await response.json(); const usedToday = this.startCredits - currentCredits; const remainingBudget = this.dailyBudget - usedToday; console.log(`Used today: ${usedToday}/${this.dailyBudget}`); console.log(`Remaining budget: ${remainingBudget}`); if (remainingBudget <= 0) { throw new Error('Daily credit budget exceeded!'); } return { used: usedToday, remaining: remainingBudget, percentage: (usedToday / this.dailyBudget * 100).toFixed(2) }; } } const budget = new CreditBudget(apiKey, 1000); // 1000 credits/day await budget.initialize(); const status = await budget.checkBudget(); ``` *** ## Best Practices Since this endpoint is free, check it liberally: * Before expensive operations * In application health checks * When displaying usage stats to users Implement automated alerts: * Alert at 20% remaining * Warning at 10% remaining * Critical at 5% remaining Monitor consumption patterns: * Track daily usage * Forecast when credits will run out * Purchase credits before running low *** ## Related Endpoints View credit packages and pricing Check sync status (also free) Purchase more credits # Historical Portfolio Source: https://docs.octav.fi/api/endpoints/historical-portfolio Retrieve portfolio snapshots from specific dates Access historical portfolio data for a subscribed address, enabling time-based analysis and portfolio performance tracking. **Subscription Required** — You must subscribe to an address before retrieving historical data. Contact support or use the dashboard. **Cost:** 1 credit per call **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/portfolio/historical-portfolio). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash theme={null} GET https://api.octav.fi/v1/historical ``` ### Parameters EVM or SOL wallet address Date in YYYY-MM-DD format (e.g., `2024-11-01`) *** ## Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/historical?addresses=0x6426af...&date=2024-11-01" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.octav.fi/v1/historical?addresses=${address}&date=2024-11-01`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolio = await response.json(); console.log(`Net worth: $${portfolio.networth}`); ``` ```python Python theme={null} response = requests.get( 'https://api.octav.fi/v1/historical', params={'addresses': address, 'date': '2024-11-01'}, headers={'Authorization': f'Bearer {api_key}'} ) portfolio = response.json() print(f"Net worth: ${portfolio['networth']}") ``` *** ## Response ## Returns the same structure as the [Portfolio endpoint](/api/endpoints/portfolio), but with data as it existed on the specified date. ## Use Cases ```javascript theme={null} async function trackPerformance(address, days) { const snapshots = []; for (let i = 0; i < days; i++) { const date = new Date(); date.setDate(date.getDate() - i); const dateStr = date.toISOString().split('T')[0]; const response = await fetch( `https://api.octav.fi/v1/historical?addresses=${address}&date=${dateStr}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); snapshots.push(await response.json()); } return snapshots; } ``` ```javascript theme={null} const [portfolio1, portfolio2] = await Promise.all([ fetch(`https://api.octav.fi/v1/historical?addresses=${address}&date=2024-10-01`, { headers: { 'Authorization': `Bearer ${apiKey}` } }).then(r => r.json()), fetch(`https://api.octav.fi/v1/historical?addresses=${address}&date=2024-11-01`, { headers: { 'Authorization': `Bearer ${apiKey}` } }).then(r => r.json()) ]); const change = parseFloat(portfolio2.networth) - parseFloat(portfolio1.networth); console.log(`30-day change: $${change.toFixed(2)}`); ``` *** ## Subscription To enable historical tracking: 1. Login to [data.octav.fi](https://data.octav.fi/) 2. Navigate to Historical Tracking 3. Add addresses for daily snapshots Or contact support via [Discord](https://discord.com/invite/qvcknAa73A) *** ## Related Endpoints Current portfolio state Historical token breakdown # Nav Source: https://docs.octav.fi/api/endpoints/nav Retrieve Net Asset Value of any address Get the Net Asset Value of an address across multiple chains. **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/nav). Get your API key at [data.octav.fi](https://data.octav.fi/) **Cost:** 1 credit per call *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/nav ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/nav?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Parameters EVM or SOL wallet address to retrieve net asset value data for ``` addresses=0x6426af179aabebe47666f345d69fd9079673f6cd ``` Wait for fresh data if cache is stale * `false`: Return cached data immediately (recommended) * `true`: Wait for sync if data is older than 1 minute Currency in which to return the Net Asset Value **Fiat currencies:** `USD`, `EUR`, `CAD`, `AED`, `CHF`, `SGD` **Crypto currencies:** `ETH`, `SOL`, `cbBTC`, `EURC`, `BNB` When a crypto currency is specified, the NAV is converted using on-chain conversion rates from each portfolio's holdings, with a weighted average price across wallets. ``` currency=EUR ``` *** ## Response The Net Asset Value in the requested currency The currency code of the returned value (e.g., USD, EUR, ETH) The conversion price used to calculate the NAV. For fiat currencies, this is the exchange rate from USD. For crypto currencies, this is the weighted average price in USD across all queried wallets. *** ## Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/nav?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const response = await fetch( `https://api.octav.fi/v1/nav?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const data = await response.json(); console.log(`Net Asset Value: ${data.currency} ${data.nav}`); ``` ```python Python theme={null} import requests address = '0x6426af179aabebe47666f345d69fd9079673f6cd' response = requests.get( 'https://api.octav.fi/v1/nav', params={ 'addresses': address }, headers={'Authorization': f'Bearer {api_key}'} ) data = response.json() print(f"Net Worth: {data['currency']} {data['nav']}") ``` ```typescript TypeScript theme={null} interface NavResponse { nav: number; currency: string; conversionPrice: number; } const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const response = await fetch( `https://api.octav.fi/v1/nav?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const data: NavResponse = await response.json(); console.log(`Net Asset Value: ${data.currency} ${data.nav}`); ``` *** ## Example Response ```json USD (default) theme={null} { "nav": 1235564.43434, "currency": "USD", "conversionPrice": 1 } ``` ```json ETH theme={null} { "nav": 617.78, "currency": "ETH", "conversionPrice": 2000.12 } ``` *** ## Data Freshness The Nav endpoint uses intelligent caching to balance data freshness with performance: **Cache Duration:** 1 minute **When data is less than 1 minute old:** * Cached data returned immediately * Response time: under 100ms **When data is more than 1 minute old:** * Cached data returned immediately * Background sync initiated for next request * Next request gets fresh data **With waitForSync=true:** * Waits for sync if data is stale * Returns data less than 1 minute old * Response time: Variable (1-10 seconds) **For most use cases:** * Use default `waitForSync=false` * Data fresher than 1 minute is sufficient * Fast response times **For real-time tracking:** * Set `waitForSync=true` when you need the absolute latest data * Accept longer response times * Consider rate limits **For background updates:** * Call endpoint periodically to keep cache warm * Background sync ensures next request is fresh ## Error Responses Invalid parameters provided. ```json theme={null} { "error": "Bad Request", "message": "addresses parameter is required" } ``` **Common causes:** * Missing `addresses` parameter * Invalid address format * Too many addresses in single request Authentication failed. ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **Solution:** Check your API key in the Authorization header Rate limit exceeded. ```json theme={null} { "error": "Rate limit exceeded", "message": "You have exceeded your rate limit", "retry_after": 60 } ``` **Solution:** Wait for the specified time or implement retry logic Insufficient credits. ```json theme={null} { "error": "Insufficient credits", "message": "Please purchase more credits to continue" } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) *** ## Related Endpoints View transaction history for these addresses Get detailed token breakdown by protocol View portfolio value at specific dates Check when portfolio was last synced # Portfolio Source: https://docs.octav.fi/api/endpoints/portfolio Retrieve portfolio holdings across wallets and DeFi protocols Get comprehensive portfolio data for a blockchain address including assets, protocol positions, and net worth across multiple chains. **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/portfolio). Get your API key at [data.octav.fi](https://data.octav.fi/) **Cost:** 1 credit per call *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/portfolio ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Parameters EVM or SOL wallet address to retrieve portfolio data for ``` addresses=0x6426af179aabebe47666f345d69fd9079673f6cd ``` Include image URLs for chains, assets, and protocols Useful for displaying logos in your application UI Include blockchain explorer URLs for assets and transactions Links to Etherscan, Arbiscan, etc. for easy navigation Wait for fresh data if cache is stale * `false`: Return cached data immediately (recommended) * `true`: Wait for sync if data is older than 1 minute *** ## Response ### Portfolio Object The wallet address Total portfolio net worth in USD Available cash balance Income generated today Expenses incurred today Total fees in native asset Total fees in USD Last sync timestamp (milliseconds since epoch) Unrealized profit/loss (if available, otherwise "N/A") Realized profit/loss (if available, otherwise "N/A") Total cost basis of holdings (if available, otherwise "N/A") Assets organized by protocol (wallet, lending, staking, etc.), keyed by protocol identifier. Each protocol contains: * `key`: Protocol identifier * `name`: Protocol display name * `value`: Total value in USD * `chains`: Object keyed by chain identifier. Each chain holds a `protocolPositions` object whose entries contain the `assets[]` array. Asset path: `assetByProtocols..chains..protocolPositions..assets[]` Each asset contains `symbol`, `name`, `balance`, `price`, `value`, `decimal`, `contract`, `chainKey`, and `chainContract`. The on-chain token address is `contract` (native assets use the zero address); `chainContract` is the `:` composite key. Per-chain totals, keyed by chain identifier. Each chain contains: * `key`: Chain identifier (e.g., "ethereum", "arbitrum") * `name`: Chain display name * `chainId`: Numeric chain ID * `value`: Total value on this chain * `valuePercentile`: Share of total net worth on this chain * `totalCostBasis`, `totalClosedPnl`, `totalOpenPnl`: Cost basis and P\&L (or "N/A") *** ## Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolio = await response.json(); console.log(`Net Worth: $${portfolio.networth}`); ``` ```python Python theme={null} import requests address = '0x6426af179aabebe47666f345d69fd9079673f6cd' response = requests.get( 'https://api.octav.fi/v1/portfolio', params={ 'addresses': address, 'includeImages': True }, headers={'Authorization': f'Bearer {api_key}'} ) portfolio = response.json() print(f"Net Worth: ${portfolio['networth']}") ``` ```typescript TypeScript theme={null} interface Asset { balance: string; symbol: string; name: string; price: string; value: string; decimal: string; contract: string; chainKey: string; chainContract: string; } interface ProtocolPosition { name: string; assets: Asset[]; totalValue: string; } interface ProtocolChain { key: string; name: string; value: string; protocolPositions: Record; } interface Protocol { key: string; name: string; value: string; chains: Record; } interface Portfolio { address: string; networth: string; cashBalance: string; lastUpdated: string; assetByProtocols: Record; chains: Record; } const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolio: Portfolio = await response.json(); console.log(`Net Worth: $${portfolio.networth}`); ``` *** ## Example Response ```json theme={null} { "address": "0x6426af179aabebe47666f345d69fd9079673f6cd", "cashBalance": "0", "lastUpdated": "1715173392020", "networth": "25123.45", "assetByProtocols": { "wallet": { "name": "Wallet", "key": "wallet", "value": "25123.45", "chains": { "ethereum": { "name": "Ethereum", "key": "ethereum", "value": "24962.30", "protocolPositions": { "WALLET": { "name": "wallet", "assets": [ { "balance": "5.5", "symbol": "eth", "name": "ethereum", "price": "3200.50", "value": "17602.75", "decimal": "18", "contract": "0x0000000000000000000000000000000000000000", "chainKey": "ethereum", "chainContract": "ethereum:0x0000000000000000000000000000000000000000" }, { "balance": "7359.55", "symbol": "usdc", "name": "usd coin", "price": "1.00", "value": "7359.55", "decimal": "6", "contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "chainKey": "ethereum", "chainContract": "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } ], "protocolPositions": [], "totalValue": "24962.30", "unlockAt": "0" } } }, "arbitrum": { "name": "Arbitrum", "key": "arbitrum", "value": "161.15", "protocolPositions": { "WALLET": { "name": "wallet", "assets": [ { "balance": "161.15", "symbol": "usdc", "name": "usd coin", "price": "1.00", "value": "161.15", "decimal": "6", "contract": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "chainKey": "arbitrum", "chainContract": "arbitrum:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" } ], "protocolPositions": [], "totalValue": "161.15", "unlockAt": "0" } } } } } }, "chains": { "ethereum": { "name": "Ethereum", "key": "ethereum", "chainId": "1", "value": "24962.30", "valuePercentile": "99.36", "totalCostBasis": "N/A", "totalClosedPnl": "N/A", "totalOpenPnl": "N/A" }, "arbitrum": { "name": "Arbitrum", "key": "arbitrum", "chainId": "42161", "value": "161.15", "valuePercentile": "0.64", "totalCostBasis": "N/A", "totalClosedPnl": "N/A", "totalOpenPnl": "N/A" } } } ``` *** ## Data Freshness The Portfolio endpoint uses intelligent caching to balance data freshness with performance: **Cache Duration:** 1 minute **When data is less than 1 minute old:** * Cached data returned immediately * Response time: under 100ms **When data is more than 1 minute old:** * Cached data returned immediately * Background sync initiated for next request * Next request gets fresh data **With waitForSync=true:** * Waits for sync if data is stale * Returns data less than 1 minute old * Response time: Variable (1-10 seconds) **For most use cases:** * Use default `waitForSync=false` * Data fresher than 1 minute is sufficient * Fast response times **For real-time tracking:** * Set `waitForSync=true` when you need the absolute latest data * Accept longer response times * Consider rate limits **For background updates:** * Call endpoint periodically to keep cache warm * Background sync ensures next request is fresh *** ## Use Cases Display portfolio overview with net worth and asset breakdown: ```javascript theme={null} const portfolio = await fetchPortfolio(address); // Display summary console.log(`Net Worth: $${portfolio.networth}`); console.log(`Daily P&L: $${portfolio.dailyIncome - portfolio.dailyExpense}`); // List protocols Object.values(portfolio.assetByProtocols).forEach(protocol => { console.log(`${protocol.name}: $${protocol.value}`); }); // Chain distribution Object.values(portfolio.chains).forEach(chain => { console.log(`${chain.name}: $${chain.value}`); }); ``` Track specific assets across all protocols: ```javascript theme={null} function getAssetTotal(portfolio, symbol) { let total = 0; let balance = 0; Object.values(portfolio.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { Object.values(chain.protocolPositions).forEach(position => { position.assets .filter(asset => asset.symbol === symbol) .forEach(asset => { total += parseFloat(asset.value); balance += parseFloat(asset.balance); }); }); }); }); return { symbol, balance, value: total }; } const ethTotal = getAssetTotal(portfolio, 'eth'); console.log(`Total ETH: ${ethTotal.balance} ($${ethTotal.value})`); ``` Analyze DeFi protocol exposure: ```javascript theme={null} function getProtocolExposure(portfolio) { const protocols = Object.values(portfolio.assetByProtocols); const totalValue = parseFloat(portfolio.networth); return protocols.map(protocol => ({ name: protocol.name, value: parseFloat(protocol.value), percentage: (parseFloat(protocol.value) / totalValue * 100).toFixed(2) })).sort((a, b) => b.value - a.value); } const exposure = getProtocolExposure(portfolio); exposure.forEach(p => { console.log(`${p.name}: $${p.value} (${p.percentage}%)`); }); ``` *** ## Error Responses Invalid parameters provided. ```json theme={null} { "error": "Bad Request", "message": "addresses parameter is required" } ``` **Common causes:** * Missing `addresses` parameter * Invalid address format Authentication failed. ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **Solution:** Check your API key in the Authorization header Rate limit exceeded. ```json theme={null} { "error": "Rate limit exceeded", "message": "You have exceeded your rate limit", "retry_after": 60 } ``` **Solution:** Wait for the specified time or implement retry logic Insufficient credits. ```json theme={null} { "error": "Insufficient credits", "message": "Please purchase more credits to continue" } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) *** ## Related Endpoints View transaction history for this address Get detailed token breakdown by protocol View portfolio value at specific dates Check when portfolio was last synced # Portfolio At Block Source: https://docs.octav.fi/api/endpoints/portfolio-at-block Retrieve a single address's portfolio valued at a specific historical block Get a portfolio for a single EVM address with every balance and price pinned to a specific historical block, instead of the current state. The Portfolio at Block endpoint is an **add-on**, billed at a set monthly fee separate from your API credit pool, plus 1 credit per call. Contact sales to enable Portfolio at Block access for your account. **Cost:** Set monthly fee under the Portfolio at Block add-on, plus 1 credit per call. **Rate limit:** 100 requests/min, on a dedicated bucket (does not count against your portfolio rate limit). *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/portfolio/at-block ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/portfolio/at-block?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&chainKey=ethereum&blockNumber=19000000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Parameters A single EVM wallet address to retrieve portfolio data for. Only one address is accepted per call. ``` addresses=0x6426af179aabebe47666f345d69fd9079673f6cd ``` The chain the block belongs to. One of `ethereum` or `monad`. ``` chainKey=ethereum ``` The block number to value the portfolio at. Must be a positive integer. ``` blockNumber=19000000 ``` *** ## Response Returns the same [Portfolio](/api/endpoints/portfolio) shape, with every asset balance and price valued as of `blockNumber`, plus a top-level `blockNumber` field. This endpoint does not accept `includeImages`, `includeExplorerUrls`, or `includeNFTs` — image, explorer URL, and NFT fields are never included in the response. The block number the portfolio was valued at (echoes the `blockNumber` query parameter, returned as a string) The wallet address Total portfolio net worth in USD, valued at `blockNumber` Available cash balance at `blockNumber` Assets organized by protocol (wallet, lending, staking, etc.), keyed by protocol identifier — same structure as the [Portfolio](/api/endpoints/portfolio) endpoint, valued at `blockNumber`. Per-chain totals, keyed by chain identifier. Only the chain named by `chainKey` is present, since a block belongs to one chain. *** ## Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/portfolio/at-block?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&chainKey=ethereum&blockNumber=19000000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const response = await fetch( `https://api.octav.fi/v1/portfolio/at-block?addresses=${address}&chainKey=ethereum&blockNumber=19000000`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [portfolio] = await response.json(); console.log(`Net Worth at block ${portfolio.blockNumber}: $${portfolio.networth}`); ``` ```python Python theme={null} import requests address = '0x6426af179aabebe47666f345d69fd9079673f6cd' response = requests.get( 'https://api.octav.fi/v1/portfolio/at-block', params={ 'addresses': address, 'chainKey': 'ethereum', 'blockNumber': 19000000 }, headers={'Authorization': f'Bearer {api_key}'} ) portfolio = response.json()[0] print(f"Net Worth at block {portfolio['blockNumber']}: ${portfolio['networth']}") ``` ```typescript TypeScript theme={null} interface PortfolioAtBlock { address: string; networth: string; cashBalance: string; blockNumber: string; assetByProtocols: Record; chains: Record; } const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const response = await fetch( `https://api.octav.fi/v1/portfolio/at-block?addresses=${address}&chainKey=ethereum&blockNumber=19000000`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [portfolio]: PortfolioAtBlock[] = await response.json(); console.log(`Net Worth at block ${portfolio.blockNumber}: $${portfolio.networth}`); ``` *** ## Example Response ```json theme={null} [ { "address": "0x6426af179aabebe47666f345d69fd9079673f6cd", "cashBalance": "0", "blockNumber": "19000000", "networth": "38068.19", "assetByProtocols": { "wallet": { "name": "Wallet", "key": "wallet", "value": "24962.30", "chains": { "ethereum": { "name": "Ethereum", "key": "ethereum", "value": "24962.30", "protocolPositions": { "WALLET": { "name": "wallet", "assets": [ { "balance": "5.5", "symbol": "eth", "name": "ethereum", "price": "3200.50", "value": "17602.75", "decimal": "18", "contract": "0x0000000000000000000000000000000000000000", "chainKey": "ethereum", "chainContract": "ethereum:0x0000000000000000000000000000000000000000" }, { "balance": "7359.55", "symbol": "usdc", "name": "usd coin", "price": "1.00", "value": "7359.55", "decimal": "6", "contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "chainKey": "ethereum", "chainContract": "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } ], "protocolPositions": [], "totalValue": "24962.30", "unlockAt": "0" } } } } }, "lido": { "name": "LIDO", "key": "lido", "value": "13105.89", "totalCostBasis": "N/A", "totalClosedPnl": "N/A", "totalOpenPnl": "N/A", "chains": { "ethereum": { "name": "Ethereum", "key": "ethereum", "value": "13105.89", "totalCostBasis": "N/A", "totalClosedPnl": "N/A", "totalOpenPnl": "N/A", "protocolPositions": { "STAKED": { "assets": [], "name": "Staked", "protocolPositions": [ { "assets": [ { "balance": "4.2", "chainContract": "ethereum:0xae7ab96520de3a18e5e111b5eaab095312d7fe84", "chainKey": "ethereum", "contract": "0xae7ab96520de3a18e5e111b5eaab095312d7fe84", "decimal": "18", "isClaimable": null, "link": "", "name": "liquid staked ether 2.0", "openPnl": "N/A", "price": "3120.45", "priceSource": "octav", "symbol": "steth", "totalCostBasis": "N/A", "unlockAt": "0", "value": "13105.89" } ], "borrowAssets": [], "dexAssets": [], "marginAssets": [], "baseAssets": [], "quoteAssets": [], "side": "", "healthRate": "0", "name": "steth", "rewardAssets": [], "supplyAssets": [], "collateralizeNFTAssets": [], "totalCostBasis": "N/A", "totalClosedPnl": "N/A", "totalOpenPnl": "N/A", "unlockAt": "0", "value": "13105.89", "poolAddress": "", "vaultAddress": "", "siteUrl": "" } ], "totalOpenPnl": "N/A", "totalCostBasis": "N/A", "totalValue": "13105.89", "unlockAt": "0" } } } } } }, "chains": { "ethereum": { "name": "Ethereum", "key": "ethereum", "chainId": "1", "value": "38068.19", "valuePercentile": "100", "totalCostBasis": "N/A", "totalClosedPnl": "N/A", "totalOpenPnl": "N/A" } } } ] ``` *** ## Use Cases Value a wallet exactly as it stood at a known block — useful for tax lot reconstruction, snapshot audits, or governance-vote eligibility checks: ```javascript theme={null} async function getPortfolioAtBlock(address, blockNumber) { const response = await fetch( `https://api.octav.fi/v1/portfolio/at-block?addresses=${address}&chainKey=ethereum&blockNumber=${blockNumber}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [portfolio] = await response.json(); return portfolio; } const snapshot = await getPortfolioAtBlock('0x6426af...', 19000000); console.log(`Net worth at block ${snapshot.blockNumber}: $${snapshot.networth}`); ``` Diff net worth across two blocks (e.g. before/after a known event): ```javascript theme={null} const [before, after] = await Promise.all([ fetch(`https://api.octav.fi/v1/portfolio/at-block?addresses=${address}&chainKey=ethereum&blockNumber=19000000`, { headers: { 'Authorization': `Bearer ${apiKey}` } }).then(r => r.json()).then(([p]) => p), fetch(`https://api.octav.fi/v1/portfolio/at-block?addresses=${address}&chainKey=ethereum&blockNumber=19100000`, { headers: { 'Authorization': `Bearer ${apiKey}` } }).then(r => r.json()).then(([p]) => p) ]); const change = parseFloat(after.networth) - parseFloat(before.networth); console.log(`Change between blocks: $${change.toFixed(2)}`); ``` *** ## Error Responses Input validation failed (Joi structured error). ```json theme={null} { "error": "Validation Failed", "details": { "query": [ { "message": "\"chainKey\" must be one of [ethereum]", "path": ["chainKey"], "type": "any.only" } ] } } ``` **Common causes:** * Missing `addresses`, `chainKey`, or `blockNumber` * `chainKey` is not `ethereum` (the only currently supported chain) * `blockNumber` is not a positive integer * `addresses` is not a single valid EVM address Missing/invalid API key, **or** your key does not have the Portfolio at Block add-on enabled. ```json theme={null} { "message": "Unauthorized" } ``` **Solution:** Check your API key in the Authorization header. If the key is valid, contact sales to enable the Portfolio at Block add-on. Rate limit exceeded (100 requests/min on this endpoint's dedicated bucket). ```json theme={null} { "error": "Rate limit exceeded", "message": "You have exceeded your rate limit", "retry_after": 60 } ``` **Solution:** Wait for the specified time or implement retry logic. Insufficient credits. ```json theme={null} { "error": "Insufficient credits", "message": "Please purchase more credits to continue" } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) Upstream failure fetching Portfolio at Block data. ``` Error fetching block-pinned portfolio information ``` *** ## Related Endpoints Current portfolio state Portfolio snapshot for a specific calendar date List all supported blockchain networks Check when portfolio was last synced # Protocols Source: https://docs.octav.fi/api/endpoints/protocols List DeFi protocols available on a specific chain Retrieve a paginated list of DeFi protocols available on a specific blockchain network. Use the `chainKey` from the [Chains](/api/endpoints/chains) endpoint to specify which chain to query. **Cost:** Free (0 credits) **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/protocols). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/chains/:chainKey/protocols ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/chains/ethereum/protocols?page=1&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Parameters The chain identifier (e.g. `ethereum`, `solana`, `arbitrum`). Must match a chain's `key` field from the [Chains](/api/endpoints/chains) endpoint. ``` /v1/chains/ethereum/protocols ``` Page number for pagination * **Minimum:** 1 Number of protocols per page * **Minimum:** 1 * **Maximum:** 100 *** ## Response Returns a paginated list of protocol objects. ### Top-Level Fields Array of protocol objects (see fields below) Pagination metadata: * `page` — Current page number * `limit` — Items per page * `hasMore` — Whether more pages are available ### Protocol Fields Unique protocol identifier Protocol key (e.g. `"aave"`, `"uniswap v3"`) Display name (e.g. `"Aave"`, `"Uniswap V3"`) URL to small protocol icon URL to large protocol icon `true` if this is a user-created custom protocol. Custom protocols only appear on page 1 and are listed before system protocols. *** ## Example Request ```bash cURL theme={null} curl "https://api.octav.fi/v1/chains/ethereum/protocols?page=1&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const chainKey = 'ethereum'; const response = await fetch( `https://api.octav.fi/v1/chains/${chainKey}/protocols?page=1&limit=20`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data: protocols, pagination } = await response.json(); console.log(`Found ${protocols.length} protocols (hasMore: ${pagination.hasMore})`); ``` ```python Python theme={null} import requests chain_key = 'ethereum' response = requests.get( f'https://api.octav.fi/v1/chains/{chain_key}/protocols', params={'page': 1, 'limit': 20}, headers={'Authorization': f'Bearer {api_key}'} ) result = response.json() protocols = result['data'] pagination = result['pagination'] print(f'Found {len(protocols)} protocols (hasMore: {pagination["hasMore"]})') ``` ```typescript TypeScript theme={null} interface Protocol { uuid: string; key: string; name: string; imgSmall: string; imgLarge: string; isUserProtocol: boolean; } interface ProtocolResponse { data: Protocol[]; pagination: { page: number; limit: number; hasMore: boolean; }; } const chainKey = 'ethereum'; const response = await fetch( `https://api.octav.fi/v1/chains/${chainKey}/protocols?page=1&limit=20`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data: protocols, pagination }: ProtocolResponse = await response.json(); console.log(`Found ${protocols.length} protocols (hasMore: ${pagination.hasMore})`); ``` *** ## Example Response ```json theme={null} { "data": [ { "uuid": "abc12345-1234-5678-9abc-def012345678", "key": "aave", "name": "Aave", "imgSmall": "https://images.octav.fi/tokens/small/aave_small_icon.png", "imgLarge": "https://images.octav.fi/tokens/large/aave_large_icon.png", "isUserProtocol": false }, { "uuid": "def67890-1234-5678-9abc-def012345678", "key": "uniswap v3", "name": "Uniswap V3", "imgSmall": "https://images.octav.fi/tokens/small/uniswap_small_icon.png", "imgLarge": "https://images.octav.fi/tokens/large/uniswap_large_icon.png", "isUserProtocol": false } ], "pagination": { "page": 1, "limit": 20, "hasMore": true } } ``` *** ## Pagination The endpoint uses page-based pagination. Use `page` and `limit` to navigate through results. * **Default behavior:** Returns 20 protocols per page starting from page 1 * **`hasMore` flag:** Indicates whether additional pages exist * **User protocols:** Custom user-created protocols are prepended on page 1 only * **Ordering:** System protocols are returned in a consistent order Fetch all protocols for a chain by iterating until `hasMore` is `false`: ```javascript theme={null} async function getAllProtocols(chainKey) { const allProtocols = []; let page = 1; let hasMore = true; while (hasMore) { const response = await fetch( `https://api.octav.fi/v1/chains/${chainKey}/protocols?page=${page}&limit=100`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const result = await response.json(); allProtocols.push(...result.data); hasMore = result.pagination.hasMore; page++; } return allProtocols; } const protocols = await getAllProtocols('ethereum'); console.log(`Total protocols on Ethereum: ${protocols.length}`); ``` *** ## Use Cases Build a protocol selection dropdown for your UI: ```javascript theme={null} async function loadProtocolOptions(chainKey) { const response = await fetch( `https://api.octav.fi/v1/chains/${chainKey}/protocols?limit=100`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data: protocols } = await response.json(); return protocols.map(protocol => ({ value: protocol.key, label: protocol.name, icon: protocol.imgSmall, isCustom: protocol.isUserProtocol })); } // Populate dropdown when user selects a chain const options = await loadProtocolOptions('ethereum'); ``` Find which protocols are available across chains: ```javascript theme={null} async function getProtocolsByChains(chainKeys) { const results = {}; for (const chainKey of chainKeys) { const response = await fetch( `https://api.octav.fi/v1/chains/${chainKey}/protocols?limit=100`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); results[chainKey] = data.map(p => p.key); } return results; } const protocols = await getProtocolsByChains(['ethereum', 'arbitrum', 'base']); console.log('Ethereum protocols:', protocols.ethereum.length); console.log('Arbitrum protocols:', protocols.arbitrum.length); ``` Client-side filtering of protocol results: ```javascript theme={null} async function searchProtocols(chainKey, query) { // Fetch all protocols (the API does not expose server-side search) const allProtocols = await getAllProtocols(chainKey); const lowerQuery = query.toLowerCase(); return allProtocols.filter(p => p.name.toLowerCase().includes(lowerQuery) || p.key.toLowerCase().includes(lowerQuery) ); } const results = await searchProtocols('ethereum', 'uniswap'); // => [{ name: "Uniswap V2", ... }, { name: "Uniswap V3", ... }] ``` *** ## Error Responses Invalid query parameters. ```json theme={null} { "error": "Validation Failed", "details": { "message": "\"limit\" must be less than or equal to 100" } } ``` **Common causes:** * `page` is less than 1 * `limit` exceeds 100 Authentication failed. ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **Solution:** Check your API key in the Authorization header The specified chain key does not exist. ```json theme={null} { "message": "Chain with key 'xyz' not found" } ``` **Solution:** Use the [Chains](/api/endpoints/chains) endpoint to discover valid chain keys Rate limit exceeded. ```json theme={null} { "error": "Rate limit exceeded", "message": "You have exceeded your rate limit", "retry_after": 60 } ``` **Solution:** Wait for the specified time or implement retry logic *** ## Related Endpoints Get all supported chains and their keys Get portfolio holdings by protocol View all protocol type categories Query transactions filtered by protocol # Status Source: https://docs.octav.fi/api/endpoints/status Check synchronization status and data freshness for an address Retrieve synchronization status for an address to check when portfolio and transaction data were last updated. **Cost:** Free (0 credits) **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/status). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash theme={null} GET https://api.octav.fi/v1/status ``` ### Parameters Wallet address (EVM / SOL) ``` addresses=0x6426af179aabebe47666f345d69fd9079673f6cd ``` *** ## Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/status?addresses=0x6426af..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.octav.fi/v1/status?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [status] = await response.json(); console.log(`Last portfolio sync: ${status.portfolioLastSync}`); console.log(`Last transaction sync: ${status.transactionsLastSync}`); console.log(`Sync in progress: ${status.syncInProgress}`); ``` ```python Python theme={null} response = requests.get( 'https://api.octav.fi/v1/status', params={'addresses': address}, headers={'Authorization': f'Bearer {api_key}'} ) status = response.json()[0] print(f"Last portfolio sync: {status['portfolioLastSync']}") print(f"Last transaction sync: {status['transactionsLastSync']}") print(f"Sync in progress: {status['syncInProgress']}") ``` *** ## Response The wallet address ISO 8601 timestamp of last portfolio synchronization Example: `"2025-05-08T14:43:12.020Z"` ISO 8601 timestamp of last transaction synchronization Example: `"2025-05-08T14:38:15.920Z"` Whether transactions are currently syncing `true` = sync in progress, `false` = no active sync *** ## Example Response ```json theme={null} [ { "address": "0x6426af179aabebe47666f345d69fd9079673f6cd", "portfolioLastSync": "2025-05-08T14:43:12.020Z", "transactionsLastSync": "2025-05-08T14:38:15.920Z", "syncInProgress": false } ] ``` *** ## Use Cases Check if data is fresh enough: ```javascript theme={null} async function isDataFresh(address, maxAgeMinutes = 10) { const response = await fetch( `https://api.octav.fi/v1/status?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [status] = await response.json(); const lastSync = new Date(status.portfolioLastSync); const now = new Date(); const ageMinutes = (now - lastSync) / 1000 / 60; return ageMinutes <= maxAgeMinutes; } const isFresh = await isDataFresh(address); if (!isFresh) { console.log('Data is stale, consider syncing'); } ``` Wait for sync to complete: ```javascript theme={null} async function waitForSync(address, timeoutSeconds = 60) { const start = Date.now(); while (true) { const response = await fetch( `https://api.octav.fi/v1/status?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [status] = await response.json(); if (!status.syncInProgress) { console.log('Sync completed!'); return status; } if ((Date.now() - start) / 1000 > timeoutSeconds) { throw new Error('Sync timeout'); } await new Promise(resolve => setTimeout(resolve, 2000)); } } // Trigger sync then wait await fetch('https://api.octav.fi/v1/sync-transactions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: [address] }) }); await waitForSync(address); ``` *** ## Best Practices This endpoint costs 0 credits, so call it freely: * Check before expensive operations * Monitor sync status * Verify data freshness Understanding sync times: * **Portfolio:** 1-minute cache * **Transactions:** 10-minute cache * Check `syncInProgress` before triggering new sync *** ## Related Endpoints Trigger manual sync Check credit balance (also free) # Subscribe Snapshot Source: https://docs.octav.fi/api/endpoints/subscribe-snapshot Subscribe addresses to daily portfolio snapshots for historical tracking Subscribe addresses to receive daily portfolio snapshots, enabling you to retrieve historical portfolio data starting from the subscription date. **Cost:** 1200 credits per address per year (\$30) *** ## Endpoint ```bash theme={null} POST https://api.octav.fi/v1/subscribe-snapshot ``` ### Request Body Array of address objects to subscribe for snapshots Each object contains: * `address` (string, required): EVM or SOL wallet address * `description` (string, optional): Label or description for the address ```json theme={null} { "addresses": [ { "address": "0x6426af179aabebe47666f345d69fd9079673f6cd", "description": "Main Treasury" }, { "address": "0x1234567890abcdef1234567890abcdef12345678", "description": "" } ] } ``` Addresses must be valid and unique within the request. Duplicate addresses will be rejected. *** ## Example ```bash cURL theme={null} curl -X POST "https://api.octav.fi/v1/subscribe-snapshot" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "addresses": [ { "address": "0x6426af179aabebe47666f345d69fd9079673f6cd", "description": "Main Treasury" } ] }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/subscribe-snapshot', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: [ { address: '0x6426af179aabebe47666f345d69fd9079673f6cd', description: 'Main Treasury' } ] }) }); const result = await response.json(); console.log('Subscription result:', result); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.octav.fi/v1/subscribe-snapshot', headers={'Authorization': f'Bearer {api_key}'}, json={ 'addresses': [ { 'address': '0x6426af179aabebe47666f345d69fd9079673f6cd', 'description': 'Main Treasury' } ] } ) result = response.json() print('Subscription result:', result) ``` *** ## How It Works Call this endpoint with the addresses you want to track historically. Starting from the subscription date, daily portfolio snapshots are automatically captured for each subscribed address. Use the [Historical Portfolio](/api/endpoints/historical-portfolio) endpoint to retrieve portfolio data from any date after subscription. *** ## Use Cases Subscribe addresses to track portfolio performance over time: ```javascript theme={null} async function setupPortfolioTracking(addresses) { // Subscribe addresses for snapshots await fetch('https://api.octav.fi/v1/subscribe-snapshot', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: addresses.map(addr => ({ address: addr, description: `Tracked wallet ${addr.slice(0, 8)}...` })) }) }); console.log('Addresses subscribed! Historical data will be available starting tomorrow.'); } ``` Track multiple treasury addresses with descriptions: ```javascript theme={null} const treasuryAddresses = [ { address: '0xabc...', description: 'Operations Treasury' }, { address: '0xdef...', description: 'Development Fund' }, { address: '0x123...', description: 'Marketing Budget' } ]; await fetch('https://api.octav.fi/v1/subscribe-snapshot', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: treasuryAddresses }) }); ``` *** ## Best Practices Historical data is only available from the subscription date forward. Subscribe addresses as soon as you need historical tracking to maximize data availability. Add meaningful descriptions to addresses for easier identification when querying historical data or managing subscriptions. The endpoint validates that all addresses in a request are unique. Check your existing subscriptions before adding new addresses. *** ## Related Endpoints Query snapshots from subscribed addresses Current portfolio state # Sync Transactions Source: https://docs.octav.fi/api/endpoints/sync Manually trigger transaction synchronization for an address Trigger transaction synchronization for an address, bypassing the 10-minute cache to force an immediate update. **Cost:** 1 credit + 1 credit per 250 transactions indexed **Index Limit** — Addresses with more than 100,000 transactions are not indexed. Contact support for assistance. **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/sync). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash theme={null} POST https://api.octav.fi/v1/sync-transactions ``` ### Request Body Array of addresses to sync (currently supports 1 address) ```json theme={null} { "addresses": ["0x6426af179aabebe47666f345d69fd9079673f6cd"] } ``` *** ## Example ```bash cURL theme={null} curl -X POST "https://api.octav.fi/v1/sync-transactions" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"addresses": ["0x6426af179aabebe47666f345d69fd9079673f6cd"]}' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/sync-transactions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: ['0x6426af179aabebe47666f345d69fd9079673f6cd'] }) }); const result = await response.text(); console.log(result); // "Address is syncing" or "Address already syncing" ``` ```python Python theme={null} import requests response = requests.post( 'https://api.octav.fi/v1/sync-transactions', headers={'Authorization': f'Bearer {api_key}'}, json={'addresses': ['0x6426af179aabebe47666f345d69fd9079673f6cd']} ) print(response.text) # "Address is syncing" or "Address already syncing" ``` *** ## Response Returns a string indicating sync status: * `"Address is syncing"` - Synchronization started * `"Address already syncing"` - Address is currently syncing *** ## Cost Breakdown **1 credit** per sync request **1 credit per 250 transactions** indexed * Only new transactions since last sync are indexed * Transactions are indexed permanently (one-time cost) * Subsequent syncs only pay for new transactions ### Example Costs Address with 10,000 transactions: * API call: 1 credit * Indexing: 40 credits (10,000 ÷ 250) * **Total: 41 credits** (\$1.03) Address with 100 new transactions since last sync: * API call: 1 credit * Indexing: 1 credit (100 ÷ 250, rounded up) * **Total: 2 credits** (\$0.05) No transactions since last sync: * API call: 1 credit * Indexing: 0 credits * **Total: 1 credit** (\$0.025) *** ## Use Cases Force immediate transaction sync: ```javascript theme={null} async function forceSync(address) { const response = await fetch( 'https://api.octav.fi/v1/sync-transactions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: [address] }) } ); const status = await response.text(); console.log(`Sync status: ${status}`); // Wait for sync to complete await new Promise(resolve => setTimeout(resolve, 5000)); // Fetch updated transactions const txResponse = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&limit=10&offset=0&sort=DESC`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); return await txResponse.json(); } ``` Check status before syncing: ```javascript theme={null} async function smartSync(address) { // Check current status const statusResponse = await fetch( `https://api.octav.fi/v1/status?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [status] = await statusResponse.json(); // Check if sync needed const lastSync = new Date(status.transactionsLastSync); const now = new Date(); const minutesSinceSync = (now - lastSync) / 1000 / 60; if (minutesSinceSync > 10 && !status.syncInProgress) { console.log('Triggering sync...'); await fetch('https://api.octav.fi/v1/sync-transactions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ addresses: [address] }) }); } else if (status.syncInProgress) { console.log('Sync already in progress'); } else { console.log('Recent sync available, no need to sync'); } } ``` *** ## Best Practices Use the [Status endpoint](/api/endpoints/status) to check if a sync is needed before triggering one Avoid unnecessary sync costs by checking `transactionsLastSync` timestamp If you receive "Address already syncing", wait for the current sync to complete Typical sync time: 5-30 seconds depending on transaction count First-time syncs can be expensive for addresses with many transactions Regular syncs are cheap as only new transactions are indexed *** ## Related Endpoints Check when last synced Query synced transactions # Token Overview Source: https://docs.octav.fi/api/endpoints/token-overview Get detailed token breakdown across wallet and protocol positions Retrieve a comprehensive view of all tokens owned by an address, including distribution across wallet holdings and DeFi protocol positions. **PRO Only**  This endpoint requires an Octav PRO subscription **Cost:** 1 credit per call **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/tokens/token-overview). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/token-overview ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/token-overview?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&date=2024-11-01" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Parameters EVM or SOL blockchain address ``` addresses=0x6426af179aabebe47666f345d69fd9079673f6cd ``` Unlike other endpoints, this currently supports a single address at a time Portfolio snapshot date in YYYY-MM-DD format ``` date=2024-11-01 ``` Retrieves token holdings as they existed on this specific date *** ## Response ### Token Object Token logo URL Token ticker symbol (e.g., "ETH", "USDC") Full token name Token price at the specified date Total quantity held across all positions Total value in USD (balance � price) Percentage of total portfolio value Breakdown of token distribution across protocols Each entry contains: * `key`: Protocol identifier * `name`: Protocol display name * `value`: Value held in this protocol * `balance`: Quantity held in this protocol * `image`: Protocol logo URL *** ## Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/token-overview?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&date=2024-11-01" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const date = '2024-11-01'; const response = await fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const tokens = await response.json(); console.log(`Found ${tokens.length} tokens`); // Display top holdings tokens.slice(0, 5).forEach(token => { console.log(`${token.symbol}: ${token.balance} ($${token.value})`); }); ``` ```python Python theme={null} import requests address = '0x6426af179aabebe47666f345d69fd9079673f6cd' date = '2024-11-01' response = requests.get( 'https://api.octav.fi/v1/token-overview', params={ 'addresses': address, 'date': date }, headers={'Authorization': f'Bearer {api_key}'} ) tokens = response.json() print(f'Found {len(tokens)} tokens') # Display top holdings for token in tokens[:5]: print(f"{token['symbol']}: {token['balance']} (${token['value']})") ``` ```typescript TypeScript theme={null} interface ProtocolPosition { key: string; name: string; value: string; balance: string; image?: string; } interface Token { image?: string; symbol: string; name: string; price: string; balance: string; value: string; percentage: string; protocolsDetailed: ProtocolPosition[]; } const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const date = '2024-11-01'; const response = await fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const tokens: Token[] = await response.json(); console.log(`Found ${tokens.length} tokens`); // Display top holdings tokens.slice(0, 5).forEach(token => { console.log(`${token.symbol}: ${token.balance} ($${token.value})`); }); ``` *** ## Example Response ```json theme={null} [ { "image": "https://assets.coingecko.com/coins/images/279/small/ethereum.png", "symbol": "ETH", "name": "Ethereum", "price": "3245.67", "balance": "5.5", "value": "17851.19", "percentage": "42.5", "protocolsDetailed": [ { "key": "wallet", "name": "Wallet", "value": "10386.68", "balance": "3.2", "image": "https://..." }, { "key": "aave_v3", "name": "Aave V3", "value": "6491.01", "balance": "2.0", "image": "https://..." }, { "key": "uniswap_v3", "name": "Uniswap V3", "value": "973.50", "balance": "0.3", "image": "https://..." } ] }, { "image": "https://assets.coingecko.com/coins/images/6319/small/USD_Coin_icon.png", "symbol": "USDC", "name": "USD Coin", "price": "1.00", "balance": "15234.89", "value": "15234.89", "percentage": "36.3", "protocolsDetailed": [ { "key": "wallet", "name": "Wallet", "value": "5234.89", "balance": "5234.89", "image": "https://..." }, { "key": "aave_v3", "name": "Aave V3", "value": "10000.00", "balance": "10000.00", "image": "https://..." } ] }, { "image": "https://assets.coingecko.com/coins/images/877/small/chainlink-new-logo.png", "symbol": "LINK", "name": "Chainlink", "price": "18.45", "balance": "234.5", "value": "4326.53", "percentage": "10.3", "protocolsDetailed": [ { "key": "wallet", "name": "Wallet", "value": "4326.53", "balance": "234.5", "image": "https://..." } ] } ] ``` *** ## Use Cases Visualize token distribution across portfolio: ```javascript theme={null} async function getTokenDistribution(address, date) { const response = await fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const tokens = await response.json(); // Sort by value const sortedTokens = tokens.sort((a, b) => parseFloat(b.value) - parseFloat(a.value) ); // Display distribution sortedTokens.forEach(token => { console.log( `${token.symbol}: ${token.percentage}% ($${token.value})` ); }); return sortedTokens; } const distribution = await getTokenDistribution(address, '2024-11-01'); ``` Analyze where tokens are deployed: ```javascript theme={null} async function analyzeTokenDeployment(address, date) { const response = await fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const tokens = await response.json(); // Aggregate by protocol const protocolTotals = {}; tokens.forEach(token => { token.protocolsDetailed.forEach(protocol => { if (!protocolTotals[protocol.name]) { protocolTotals[protocol.name] = 0; } protocolTotals[protocol.name] += parseFloat(protocol.value); }); }); // Display protocol exposure Object.entries(protocolTotals) .sort((a, b) => b[1] - a[1]) .forEach(([protocol, value]) => { console.log(`${protocol}: $${value.toFixed(2)}`); }); return protocolTotals; } const deployment = await analyzeTokenDeployment(address, '2024-11-01'); ``` Compare token holdings across dates: ```javascript theme={null} async function compareTokenHoldings(address, date1, date2) { const [snapshot1, snapshot2] = await Promise.all([ fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date1}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()), fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date2}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()) ]); // Create maps for easy comparison const tokens1 = new Map( snapshot1.map(t => [t.symbol, parseFloat(t.balance)]) ); const tokens2 = new Map( snapshot2.map(t => [t.symbol, parseFloat(t.balance)]) ); // Find changes const allSymbols = new Set([...tokens1.keys(), ...tokens2.keys()]); const changes = []; allSymbols.forEach(symbol => { const balance1 = tokens1.get(symbol) || 0; const balance2 = tokens2.get(symbol) || 0; const change = balance2 - balance1; if (change !== 0) { changes.push({ symbol, change, percentChange: balance1 > 0 ? (change / balance1 * 100) : 0 }); } }); // Display changes changes .sort((a, b) => Math.abs(b.change) - Math.abs(a.change)) .forEach(({ symbol, change, percentChange }) => { const direction = change > 0 ? '=�' : '=�'; console.log( `${direction} ${symbol}: ${change > 0 ? '+' : ''}${change.toFixed(4)} ` + `(${percentChange > 0 ? '+' : ''}${percentChange.toFixed(2)}%)` ); }); return changes; } const changes = await compareTokenHoldings( address, '2024-10-01', '2024-11-01' ); ``` Identify portfolio concentration risk: ```javascript theme={null} async function analyzeConcentration(address, date) { const response = await fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const tokens = await response.json(); // Calculate concentration metrics const totalValue = tokens.reduce( (sum, t) => sum + parseFloat(t.value), 0 ); // Top token concentration const topToken = tokens.reduce((max, t) => parseFloat(t.value) > parseFloat(max.value) ? t : max ); // Top 3 concentration const top3Value = tokens .slice(0, 3) .reduce((sum, t) => sum + parseFloat(t.value), 0); // Top 5 concentration const top5Value = tokens .slice(0, 5) .reduce((sum, t) => sum + parseFloat(t.value), 0); console.log('Portfolio Concentration:'); console.log(`Top token: ${topToken.symbol} - ${topToken.percentage}%`); console.log(`Top 3 tokens: ${(top3Value / totalValue * 100).toFixed(2)}%`); console.log(`Top 5 tokens: ${(top5Value / totalValue * 100).toFixed(2)}%`); console.log(`Total tokens: ${tokens.length}`); return { topToken: topToken.percentage, top3: (top3Value / totalValue * 100).toFixed(2), top5: (top5Value / totalValue * 100).toFixed(2), totalTokens: tokens.length }; } const concentration = await analyzeConcentration(address, '2024-11-01'); ``` *** ## Historical Analysis The Token Overview endpoint is particularly powerful when combined with historical portfolio subscriptions: First, subscribe to the address for historical snapshots See [Historical Portfolio](/api/endpoints/historical-portfolio) for details Retrieve token overviews for different dates to track changes ```javascript theme={null} const dates = ['2024-09-01', '2024-10-01', '2024-11-01']; const snapshots = await Promise.all( dates.map(date => fetch( `https://api.octav.fi/v1/token-overview?addresses=${address}&date=${date}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()) ) ); ``` Track how token allocation changes over time * New tokens added * Tokens sold or removed * Balance changes * Protocol migration patterns *** ## Error Responses Invalid parameters provided. ```json theme={null} { "error": "Bad Request", "message": "Invalid date format. Use YYYY-MM-DD" } ``` **Common causes:** * Missing required parameters * Invalid date format * Invalid address format Authentication failed or missing PRO subscription. ```json theme={null} { "error": "Unauthorized", "message": "This endpoint requires Octav PRO subscription" } ``` **Solution:** Upgrade to Octav PRO at [data.octav.fi](https://data.octav.fi/) No historical data available for the specified date. ```json theme={null} { "error": "Not Found", "message": "No snapshot available for this date" } ``` **Solution:** Subscribe to the address for historical snapshots Insufficient credits. ```json theme={null} { "error": "Insufficient credits", "message": "Please purchase more credits to continue" } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) *** ## Related Endpoints Get current portfolio overview View full portfolio at specific dates Track token movements over time Learn about PRO subscription # Crypto Transaction History API Source: https://docs.octav.fi/api/endpoints/transactions Retrieve paginated, labelled blockchain transaction history for any address, with filtering by chain, type, protocol and date, plus full-text search. Retrieve paginated transaction history for blockchain addresses with comprehensive filtering, full-text search, and detailed transaction data. **Cost:** 1 credit per call **Index Limit** — Addresses with more than 100,000 transactions are not automatically indexed. Contact support for assistance with large addresses. **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/transactions). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/transactions ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&limit=10&offset=0&sort=DESC" \ -H "Authorization: Bearer YOUR_API_KEY" ``` *** ## Required Parameters Wallet address (EVM / SOL) ``` addresses=0x6426af179aabebe47666f345d69fd9079673f6cd ``` Number of transactions per page * **Minimum:** 1 * **Maximum:** 250 * **Recommended:** 10-50 for optimal performance Pagination offset (0-based) ``` offset=0 # First page offset=10 # Second page (if limit=10) offset=20 # Third page (if limit=10) ``` *** ## Optional Parameters ### Search & Filter Full-text search in transaction assets Searches across: * Token symbols * Token names * Contract addresses * Transaction descriptions ``` initialSearchText=USDC ``` Filter by interacting addresses (comma-separated) Shows only transactions where these addresses are involved: ``` interactingAddresses=0x123...,0x456... ``` Filter by blockchain networks (comma-separated chain keys) ``` networks=ethereum,arbitrum,base ``` See [Supported Blockchains](/docs/supported-blockchains) for chain keys Filter by transaction types (comma-separated type keys) See [Transaction Types](https://octav-0131e508.mintlify.app/api/reference/transaction-types) Common types: * `SEND` - Token transfers out * `RECEIVE` - Token transfers in * `SWAP` - Token swaps * `DEPOSIT` - DeFi deposits * `WITHDRAW` - DeFi withdrawals * `STAKE` - Staking operations * `CLAIM` - Reward claims * `APPROVE` - Token approvals ``` txTypes=SWAP,DEPOSIT ``` Filter by protocol keys (comma-separated) See [Protocol Keys](https://protocols.octav.fi/) ``` protocols=uniswap_v3,aave_v3 ``` Exclude spam transactions ``` hideSpam=true ``` Exclude dust transactions ``` hideDust=true ``` ### Sorting & Date Range Sort order by timestamp * `DESC` - Newest first (recommended) * `ASC` - Oldest first ``` sort=DESC ``` Start of the date range, ISO 8601. Inclusive — filters `timestamp >= startDate` at the exact instant you pass, compared in UTC. Use camelCase; `start_date` is rejected with a 400 validation error. Omit to start from the wallet's first indexed transaction. ``` startDate=2024-01-01T00:00:00Z ``` End of the date range, ISO 8601. Inclusive, but **rounded up to the end of that UTC calendar day** (`23:59:59Z`) — the time-of-day you send is ignored. Filters `timestamp <= endOfDay(endDate)`. Use camelCase; `end_date` is rejected with a 400 validation error. Omit to include transactions up to the present. ``` endDate=2024-12-31T23:59:59Z ``` **Building daily windows without duplicates** — because `endDate` rounds up to the end of its UTC calendar day: * **Single day:** set `startDate` and `endDate` to the same date (e.g. `startDate=2026-05-27&endDate=2026-05-27` returns `2026-05-27 00:00:00Z`–`23:59:59Z`). * **Range:** set `startDate` to the first day and `endDate` to the last day you want included. * **Do not** set `endDate` to the next day's midnight (e.g. `2026-05-28T00:00:00Z`) — it rounds up to the end of the 28th and pulls in an extra day, so the same transaction appears in two consecutive windows. Since `timestamp` is integer epoch seconds, contiguous same-day windows have no overlap and no gap. ### NFT Filters Filter by NFT token ID ``` tokenId=1234 ``` *** ## Response The response is an object with a single `transactions` key holding the array: ```json theme={null} { "transactions": [ { /* ...tx... */ } ] } ``` ### Transaction Object Transaction hash Transaction timestamp in seconds since epoch Blockchain information * `key`: Chain identifier * `name`: Chain display name Address initiating the transaction Recipient address Transaction type (DEPOSIT, WITHDRAWAL, SWAP, etc.) Protocol information (if applicable) * `key`: Protocol identifier * `name`: Protocol display name Sub-protocol information (if applicable) * `key`: Sub-protocol identifier * `name`: Sub-protocol name Transaction value in native asset Transaction value in USD Fees paid in native asset Fees paid in USD Assets received in the transaction. Each asset contains `symbol`, `name`, `balance`, `value`, `decimal`, `contract`, `chainKey`, and `chainContract`. The token quantity is `balance` (not `amount`), and the on-chain address is `contract` (not `contractAddress`); native assets use the zero address. There is no nested `chain` object at the asset level — use `chainKey` / `chainContract`. Assets sent out in the transaction. Same shape as `assetsIn`. Smart contract function called Realized profit/loss for the transaction (if available, otherwise "N/A") Detailed fee breakdown *** ## Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&limit=10&offset=0&sort=DESC&hideSpam=true" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const params = new URLSearchParams({ addresses: '0x6426af179aabebe47666f345d69fd9079673f6cd', limit: '10', offset: '0', sort: 'DESC', hideSpam: 'true' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); console.log(`Retrieved ${transactions.length} transactions`); ``` ```python Python theme={null} import requests params = { 'addresses': '0x6426af179aabebe47666f345d69fd9079673f6cd', 'limit': 10, 'offset': 0, 'sort': 'DESC', 'hideSpam': True } response = requests.get( 'https://api.octav.fi/v1/transactions', params=params, headers={'Authorization': f'Bearer {api_key}'} ) transactions = response.json()['transactions'] print(f'Retrieved {len(transactions)} transactions') ``` ```typescript TypeScript theme={null} interface Asset { symbol: string; name: string; balance: string; value: string; decimal: string; contract: string; chainKey: string; chainContract: string; } interface Transaction { hash: string; timestamp: string; chain: { key: string; name: string; }; from: string; to: string; type: string; value: string; valueFiat: string; fees: string; feesFiat: string; assetsIn: Asset[]; assetsOut: Asset[]; protocol?: { key: string; name: string; }; } interface TransactionsResponse { transactions: Transaction[]; } const params = new URLSearchParams({ addresses: '0x6426af179aabebe47666f345d69fd9079673f6cd', limit: '10', offset: '0', sort: 'DESC', hideSpam: 'true' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions }: TransactionsResponse = await response.json(); console.log(`Retrieved ${transactions.length} transactions`); ``` *** ## Example Response ```json theme={null} { "transactions": [ { "hash": "0xa1b2c3d4e5f6...", "timestamp": "1699012800", "chain": { "key": "ethereum", "name": "Ethereum" }, "from": "0x6426af179aabebe47666f345d69fd9079673f6cd", "to": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", "type": "SWAP", "protocol": { "key": "uniswap_v3", "name": "Uniswap V3" }, "value": "0", "valueFiat": "0", "fees": "0.002134", "feesFiat": "7.12", "assetsIn": [ { "symbol": "weth", "name": "wrapped ether", "balance": "1.5", "value": "4800.00", "decimal": "18", "contract": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "chainKey": "ethereum", "chainContract": "ethereum:0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } ], "assetsOut": [ { "symbol": "usdc", "name": "usd coin", "balance": "4795.23", "value": "4795.23", "decimal": "6", "contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "chainKey": "ethereum", "chainContract": "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } ], "functionName": "swapExactTokensForTokens", "nativeAssetFees": { "symbol": "eth", "balance": "0.002134", "value": "7.12", "contract": "0x0000000000000000000000000000000000000000", "chainKey": "ethereum" } }, { "hash": "0xf9e8d7c6b5a4...", "timestamp": "1698926400", "chain": { "key": "arbitrum", "name": "Arbitrum" }, "from": "0x6426af179aabebe47666f345d69fd9079673f6cd", "to": "0x794a61358D6845594F94dc1DB02A252b5b4814aD", "type": "DEPOSIT", "protocol": { "key": "aave_v3", "name": "Aave V3" }, "value": "0", "valueFiat": "0", "fees": "0.000421", "feesFiat": "1.35", "assetsIn": [], "assetsOut": [ { "symbol": "usdc", "name": "usd coin", "balance": "10000", "value": "10000.00", "decimal": "6", "contract": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "chainKey": "arbitrum", "chainContract": "arbitrum:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" } ], "functionName": "supply", "nativeAssetFees": { "symbol": "eth", "balance": "0.000421", "value": "1.35", "contract": "0x0000000000000000000000000000000000000000", "chainKey": "arbitrum" } } ] } ``` *** ## Use Cases Display transaction history with pagination: ```javascript theme={null} async function getTransactionHistory(address, page = 1, pageSize = 20) { const offset = (page - 1) * pageSize; const params = new URLSearchParams({ addresses: address, limit: pageSize.toString(), offset: offset.toString(), sort: 'DESC', hideSpam: 'true' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); return transactions; } // Get first page const page1 = await getTransactionHistory(address, 1); // Get next page const page2 = await getTransactionHistory(address, 2); ``` Filter transactions by type: ```javascript theme={null} async function getSwapTransactions(address) { const params = new URLSearchParams({ addresses: address, txTypes: 'SWAP', limit: '50', offset: '0', sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); // Calculate total swap volume const totalVolume = transactions.reduce((sum, tx) => { return sum + parseFloat(tx.valueFiat || 0); }, 0); console.log(`Total swap volume: $${totalVolume}`); return transactions; } ``` Search for specific tokens or protocols: ```javascript theme={null} async function searchTransactions(address, searchTerm) { const params = new URLSearchParams({ addresses: address, initialSearchText: searchTerm, limit: '50', offset: '0', sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); return transactions; } // Search for USDC transactions const usdcTxs = await searchTransactions(address, 'USDC'); // Search for Uniswap interactions const uniswapTxs = await searchTransactions(address, 'Uniswap'); ``` Query transactions within a date range: ```javascript theme={null} async function getTransactionsInRange(address, startDate, endDate) { const params = new URLSearchParams({ addresses: address, startDate: startDate.toISOString(), endDate: endDate.toISOString(), limit: '100', offset: '0', sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); return transactions; } // Get last 30 days const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 30); const recentTxs = await getTransactionsInRange( address, startDate, endDate ); ``` Calculate total fees paid: ```javascript theme={null} async function calculateTotalFees(address, startDate, endDate) { let offset = 0; const limit = 250; let totalFees = 0; let hasMore = true; while (hasMore) { const params = new URLSearchParams({ addresses: address, startDate: startDate.toISOString(), endDate: endDate.toISOString(), limit: limit.toString(), offset: offset.toString(), sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); if (transactions.length === 0) { hasMore = false; } else { transactions.forEach(tx => { totalFees += parseFloat(tx.feesFiat || 0); }); offset += limit; hasMore = transactions.length === limit; } } return totalFees; } const fees = await calculateTotalFees(address, startDate, endDate); console.log(`Total fees paid: $${fees.toFixed(2)}`); ``` *** ## Pagination Best Practices **Use appropriate page sizes:** * Small pages (10-20): Fast response, more requests * Medium pages (50-100): Balanced approach (recommended) * Large pages (200-250): Fewer requests, slower response **Track pagination state:** ```javascript theme={null} class TransactionPaginator { constructor(address, pageSize = 50) { this.address = address; this.pageSize = pageSize; this.currentOffset = 0; } async nextPage() { const params = new URLSearchParams({ addresses: this.address, limit: this.pageSize.toString(), offset: this.currentOffset.toString(), sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); if (transactions.length > 0) { this.currentOffset += transactions.length; } return { transactions, hasMore: transactions.length === this.pageSize }; } } ``` For addresses with many transactions: 1. **Use filters to reduce results:** * Filter by date range * Filter by transaction type * Filter by specific networks 2. **Process in batches:** ```javascript theme={null} async function processAllTransactions(address, processor) { let offset = 0; const limit = 250; while (true) { const params = new URLSearchParams({ addresses: address, limit: limit.toString(), offset: offset.toString(), sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { transactions } = await response.json(); if (transactions.length === 0) break; await processor(transactions); offset += transactions.length; if (transactions.length < limit) break; } } ``` 3. **Implement caching:** * Cache transaction pages locally * Only fetch new transactions since last sync * Use `startDate` to fetch only recent data *** ## Error Responses Invalid parameters provided. ```json theme={null} { "error": "Bad Request", "message": "limit must be between 1 and 250" } ``` **Common causes:** * Missing required parameters (`addresses`, `limit`, `offset`) * Invalid parameter values * Invalid date format Address has more than 100,000 transactions. ```json theme={null} { "error": "Address not indexed", "message": "This address has over 100,000 transactions and is not indexed" } ``` **Solution:** Contact support for assistance with high-volume addresses Authentication failed. ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **Solution:** Check your API key in the Authorization header Insufficient credits. ```json theme={null} { "error": "Insufficient credits", "message": "Please purchase more credits to continue" } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) *** ## Related Endpoints View current portfolio holdings Manually trigger transaction sync Check when transactions were last synced View portfolio at specific dates # Virtual Users Portfolio API Source: https://docs.octav.fi/api/endpoints/virtual-users Query portfolios for virtual users — abstracted identities representing balance-tracking or CEX-linked accounts. Manage and query portfolios for virtual users — abstracted identities that represent balance-tracking or CEX-linked accounts. Virtual user endpoints follow the same patterns as [Portfolio](/api/endpoints/portfolio) but use `virtual:` addresses. **Pro subscription required.** Virtual users are a Pro feature. Create and manage them in the [Octav Pro](https://pro.octav.fi) app — the API provides read-only access. **Interactive Playground:** Test these endpoints in the [API Playground](/api-reference/virtual-users). Get your API key at [data.octav.fi](https://data.octav.fi/) **Cost:** 1 credit per call (list) · 1 credit per virtual user address (portfolio) *** ## List Virtual Users Returns all virtual users belonging to the authenticated API user. ### Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/virtual-users ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/virtual-users" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Parameters No query parameters required — returns all virtual users for the authenticated user. ### Response Returns an array of virtual user objects. Virtual user identifier in `virtual:` format. Use this value in the portfolio endpoint's `addresses` parameter. The virtual user type (e.g. `BALANCE`, `CEX`) User-defined label for the virtual user ### Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/virtual-users" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://api.octav.fi/v1/virtual-users', { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const virtualUsers = await response.json(); virtualUsers.forEach(user => { console.log(`${user.label}: ${user.address} (${user.type})`); }); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.octav.fi/v1/virtual-users', headers={'Authorization': f'Bearer {api_key}'} ) virtual_users = response.json() for user in virtual_users: print(f"{user['label']}: {user['address']} ({user['type']})") ``` ```typescript TypeScript theme={null} interface VirtualUser { address: string; type: string; label: string; } const response = await fetch( 'https://api.octav.fi/v1/virtual-users', { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const virtualUsers: VirtualUser[] = await response.json(); virtualUsers.forEach(user => { console.log(`${user.label}: ${user.address} (${user.type})`); }); ``` ### Example Response ```json theme={null} [ { "address": "virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890", "type": "BALANCE", "label": "My Virtual Portfolio" }, { "address": "virtual:f9e8d7c6-b5a4-3210-fedc-ba0987654321", "type": "CEX", "label": "Exchange Account" } ] ``` *** ## Virtual Users Portfolio Fetch portfolios for one or more virtual users. Works identically to [GET /v1/portfolio](/api/endpoints/portfolio) but uses virtual user addresses instead of wallet addresses. ### Endpoint ```bash Request theme={null} GET https://api.octav.fi/v1/virtual-users/portfolio ``` ```bash Example theme={null} curl -X GET "https://api.octav.fi/v1/virtual-users/portfolio?addresses=virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Parameters Comma-separated virtual user addresses (from the list endpoint). Format: `virtual:`. Max 10. ``` addresses=virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890,virtual:f9e8d7c6-b5a4-3210-fedc-ba0987654321 ``` Return a single reduced portfolio across all virtual users When `true`, returns a single-element array with merged holdings from all specified virtual users Wait for fresh data if cache is stale * `false`: Return cached data immediately (recommended) * `true`: Wait for sync if data is older than 1 minute Include image URLs for chains, assets, and protocols Useful for displaying logos in your application UI Include blockchain explorer URLs for assets and transactions Links to Etherscan, Arbiscan, etc. for easy navigation ### Response Same portfolio schema as [GET /v1/portfolio](/api/endpoints/portfolio). Returns an array of portfolio objects (one per virtual user), or a single-element array if `aggregated=true`. ### Portfolio Object The virtual user address (`virtual:`) Total portfolio net worth in USD Available cash balance Income generated today Expenses incurred today Total fees in native asset Total fees in USD Last sync timestamp (milliseconds since epoch) Unrealized profit/loss (if available, otherwise "N/A") Realized profit/loss (if available, otherwise "N/A") Total cost basis of holdings (if available, otherwise "N/A") Assets organized by protocol (wallet, lending, staking, etc.) Each protocol contains: * `key`: Protocol identifier * `name`: Protocol display name * `value`: Total value in USD * `assets[]`: Array of asset holdings Assets organized by blockchain Each chain contains: * `key`: Chain identifier (e.g., "ethereum", "arbitrum") * `name`: Chain display name * `value`: Total value on this chain * `protocols[]`: Protocols with positions on this chain ### Example Request ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/virtual-users/portfolio?addresses=virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890&aggregated=true&includeImages=true" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const addresses = [ 'virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'virtual:f9e8d7c6-b5a4-3210-fedc-ba0987654321' ].join(','); const response = await fetch( `https://api.octav.fi/v1/virtual-users/portfolio?addresses=${addresses}&aggregated=true`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolios = await response.json(); console.log(`Net Worth: $${portfolios[0].networth}`); ``` ```python Python theme={null} import requests addresses = ','.join([ 'virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'virtual:f9e8d7c6-b5a4-3210-fedc-ba0987654321' ]) response = requests.get( 'https://api.octav.fi/v1/virtual-users/portfolio', params={ 'addresses': addresses, 'aggregated': True, 'includeImages': True }, headers={'Authorization': f'Bearer {api_key}'} ) portfolios = response.json() print(f"Net Worth: ${portfolios[0]['networth']}") ``` ```typescript TypeScript theme={null} interface Asset { balance: string; symbol: string; price: string; value: string; contractAddress?: string; } interface Protocol { key: string; name: string; value: string; assets: Asset[]; } interface Portfolio { address: string; networth: string; cashBalance: string; lastUpdated: string; assetByProtocols: Record; chains: Record; } const addresses = [ 'virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'virtual:f9e8d7c6-b5a4-3210-fedc-ba0987654321' ].join(','); const response = await fetch( `https://api.octav.fi/v1/virtual-users/portfolio?addresses=${addresses}&aggregated=true`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolios: Portfolio[] = await response.json(); console.log(`Net Worth: $${portfolios[0].networth}`); ``` ### Example Response ```json theme={null} [ { "address": "virtual:a1b2c3d4-e5f6-7890-abcd-ef1234567890", "cashBalance": "0", "dailyIncome": "0", "dailyExpense": "0", "fees": "0.05", "feesFiat": "160.25", "lastUpdated": "1715173392020", "networth": "28450.30", "assetByProtocols": { "wallet": { "key": "wallet", "name": "Wallet", "value": "18200.00", "assets": [ { "balance": "3.2", "symbol": "ETH", "price": "3200.50", "value": "10241.60", "contractAddress": "0x0000000000000000000000000000000000000000", "chain": "ethereum" }, { "balance": "7958.40", "symbol": "USDC", "price": "1.00", "value": "7958.40", "contractAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "chain": "ethereum" } ] }, "aave_v3": { "key": "aave_v3", "name": "Aave V3", "value": "10250.30", "assets": [ { "balance": "10250.30", "symbol": "USDT", "price": "1.00", "value": "10250.30", "contractAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7", "chain": "ethereum" } ] } }, "chains": { "ethereum": { "key": "ethereum", "name": "Ethereum", "value": "28450.30", "protocols": ["wallet", "aave_v3"] } } } ] ``` *** ## Data Freshness The Virtual Users Portfolio endpoint uses the same caching strategy as [Portfolio](/api/endpoints/portfolio): **Cache Duration:** 1 minute **When data is less than 1 minute old:** * Cached data returned immediately * Response time: under 100ms **When data is more than 1 minute old:** * Cached data returned immediately * Background sync initiated for next request * Next request gets fresh data **With waitForSync=true:** * Waits for sync if data is stale * Returns data less than 1 minute old * Response time: Variable (1-10 seconds) **For most use cases:** * Use default `waitForSync=false` * Data fresher than 1 minute is sufficient * Fast response times **For real-time tracking:** * Set `waitForSync=true` when you need the absolute latest data * Accept longer response times * Consider rate limits *** ## Use Cases Discover virtual users and fetch their portfolios in a single flow: ```javascript theme={null} // Step 1: List all virtual users const usersResponse = await fetch( 'https://api.octav.fi/v1/virtual-users', { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const virtualUsers = await usersResponse.json(); // Step 2: Fetch aggregated portfolio for all virtual users const addresses = virtualUsers.map(u => u.address).join(','); const portfolioResponse = await fetch( `https://api.octav.fi/v1/virtual-users/portfolio?addresses=${addresses}&aggregated=true`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolio = await portfolioResponse.json(); console.log(`Total Net Worth: $${portfolio[0].networth}`); ``` Fetch individual portfolios for each virtual user: ```javascript theme={null} const addresses = virtualUsers.map(u => u.address).join(','); const response = await fetch( `https://api.octav.fi/v1/virtual-users/portfolio?addresses=${addresses}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolios = await response.json(); portfolios.forEach(portfolio => { const user = virtualUsers.find(u => u.address === portfolio.address); console.log(`${user.label}: $${portfolio.networth}`); }); ``` Combine virtual user and wallet portfolios for a full picture: ```javascript theme={null} // Fetch wallet portfolio const walletPortfolio = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${walletAddress}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()); // Fetch virtual user portfolio const virtualPortfolio = await fetch( `https://api.octav.fi/v1/virtual-users/portfolio?addresses=${virtualAddress}&aggregated=true`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()); const totalNetWorth = parseFloat(walletPortfolio[0]?.networth ?? '0') + parseFloat(virtualPortfolio[0]?.networth ?? '0'); console.log(`Combined Net Worth: $${totalNetWorth.toFixed(2)}`); ``` *** ## Error Responses Invalid parameters provided. ```json theme={null} { "error": "Bad Request", "message": "addresses parameter is required" } ``` **Common causes:** * Missing `addresses` parameter (portfolio endpoint) * Invalid address format (must be `virtual:`) * More than 10 addresses in single request Authentication failed. ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **Solution:** Check your API key in the Authorization header Attempted to access virtual users that don't belong to the authenticated API user. ```json theme={null} { "error": "Forbidden", "message": "One or more virtual user addresses do not belong to this account" } ``` **Solution:** Only use virtual user addresses returned by `GET /v1/virtual-users` for your API key Rate limit exceeded. ```json theme={null} { "error": "Rate limit exceeded", "message": "You have exceeded your rate limit", "retry_after": 60 } ``` **Solution:** Wait for the specified time or implement retry logic Insufficient credits. ```json theme={null} { "error": "Insufficient credits", "message": "Please purchase more credits to continue" } ``` **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/) *** ## Related Endpoints Retrieve portfolio holdings for wallet addresses View portfolio value at specific dates Check when portfolio was last synced Check your remaining API credits # Wallet Source: https://docs.octav.fi/api/endpoints/wallet Get wallet balances and token holdings across chains Retrieve token balances held directly in a wallet address, excluding DeFi protocol positions. This endpoint shows only assets in the wallet's custody across supported blockchains. **Cost:** 1 credit per call **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/wallet). Get your API key at [data.octav.fi](https://data.octav.fi/) *** ## Endpoint ```bash theme={null} GET https://api.octav.fi/v1/wallet ``` ### Parameters EVM or Solana wallet address ``` addresses=0xddda947f31da53d8f9b05ab5a0bb07713c256e35 ``` *** ## Example ```bash cURL theme={null} curl "https://api.octav.fi/v1/wallet?addresses=0xddda947f31da53d8f9b05ab5a0bb07713c256e35" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const address = '0xddda947f31da53d8f9b05ab5a0bb07713c256e35'; const response = await fetch( `https://api.octav.fi/v1/wallet?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [wallet] = await response.json(); console.log(`Total wallet value: $${wallet.networth}`); ``` ```python Python theme={null} address = '0xddda947f31da53d8f9b05ab5a0bb07713c256e35' response = requests.get( 'https://api.octav.fi/v1/wallet', params={'addresses': address}, headers={'Authorization': f'Bearer {api_key}'} ) wallet = response.json()[0] print(f"Total wallet value: ${wallet['networth']}") ``` ```typescript TypeScript theme={null} const address = '0xddda947f31da53d8f9b05ab5a0bb07713c256e35'; const response = await fetch( `https://api.octav.fi/v1/wallet?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [wallet]: Wallet[] = await response.json(); console.log(`Total wallet value: $${wallet.networth}`); ``` *** ## Response Returns an array of portfolio objects containing only wallet holdings. The structure follows the same format as the [Portfolio endpoint](/api/endpoints/portfolio), but filtered to show only the `wallet` protocol. ### Top-Level Fields The wallet address Total value of all wallet holdings in USD Cash balance in USD (typically "0" for wallet-only view) Realized profit/loss (if available) Unrealized profit/loss (if available) Total transaction fees in wei Total transaction fees in USD Last sync timestamp (milliseconds since epoch) Assets organized by protocol - contains only the `wallet` key The `wallet` protocol contains: * `name`: "Wallet" * `key`: "wallet" * `value`: Total USD value * `chains`: Holdings organized by blockchain Chain-level summary of wallet holdings Each chain contains: * `key`: Chain identifier * `name`: Chain display name * `value`: Total value on this chain * `chainId`: Numeric chain ID * `valuePercentile`: Percentage of total portfolio ### Asset Fields Each token asset includes: Token balance (in token units) Token symbol (e.g., "eth", "uni") Token full name USD value of holding Current token price in USD Token contract address (0x000...000 for native tokens) Blockchain identifier (e.g., "ethereum", "arbitrum") Token decimals *** ## Example Response ```json theme={null} [ { "address": "0xddda947f31da53d8f9b05ab5a0bb07713c256e35", "networth": "1504.0972690884063639880894878216512", "cashBalance": "0", "closedPnl": "13.5466715367602206822681107794098304", "fees": "48918091440000", "feesFiat": "0.125327383206473102336577", "lastUpdated": "1736976333061", "assetByProtocols": { "wallet": { "name": "Wallet", "key": "wallet", "value": "1055.20576207293341601424", "chains": { "arbitrum": { "name": "Arbitrum", "key": "arbitrum", "value": "1055.20576207293341601424", "protocolPositions": { "WALLET": { "name": "wallet", "totalValue": "1055.20576207293341601424", "assets": [ { "symbol": "eth", "name": "ethereum", "balance": "0.012695517744456324", "value": "43.70762066990445401424", "price": "3442.76", "contract": "0x0000000000000000000000000000000000000000", "chainKey": "arbitrum", "decimal": "18" }, { "symbol": "uni", "name": "uniswap", "balance": "69.9031085959246", "value": "1011.497981383028962", "price": "14.47", "contract": "0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0", "chainKey": "arbitrum", "decimal": "18" }, { "symbol": "ftw", "name": "black agnus", "balance": "15000", "value": "0.00016002", "price": "1.0668e-8", "contract": "0x306fd3e7b169aa4ee19412323e1a5995b8c1a1f4", "chainKey": "arbitrum", "decimal": "18" } ] } } } } } }, "chains": { "arbitrum": { "name": "Arbitrum", "key": "arbitrum", "chainId": "42161", "value": "1504.0972690884063639880894878216512", "valuePercentile": "100" } } } ] ``` *** ## Use Cases Get all tokens in a wallet: ```javascript theme={null} async function listWalletTokens(address) { const response = await fetch( `https://api.octav.fi/v1/wallet?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [wallet] = await response.json(); const walletProtocol = wallet.assetByProtocols.wallet; console.log(`Total Value: $${parseFloat(wallet.networth).toFixed(2)}\n`); // Iterate through each chain Object.values(walletProtocol.chains).forEach(chain => { console.log(`\n${chain.name}:`); const assets = chain.protocolPositions.WALLET.assets; assets.forEach(asset => { console.log(` ${asset.symbol.toUpperCase()}: ${parseFloat(asset.balance).toFixed(4)} ($${parseFloat(asset.value).toFixed(2)})`); }); }); } ``` Calculate holdings by chain: ```javascript theme={null} async function calculateHoldingsByChain(address) { const response = await fetch( `https://api.octav.fi/v1/wallet?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [wallet] = await response.json(); const holdingsByChain = {}; Object.entries(wallet.chains).forEach(([chainKey, chain]) => { holdingsByChain[chain.name] = { value: parseFloat(chain.value), percentage: parseFloat(chain.valuePercentile), chainId: chain.chainId }; }); return holdingsByChain; } const holdings = await calculateHoldingsByChain(address); console.log('Holdings by Chain:', holdings); ``` Show only tokens above a value threshold: ```javascript theme={null} async function getHighValueTokens(address, minValue = 10) { const response = await fetch( `https://api.octav.fi/v1/wallet?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [wallet] = await response.json(); const highValueTokens = []; const walletProtocol = wallet.assetByProtocols.wallet; Object.values(walletProtocol.chains).forEach(chain => { const assets = chain.protocolPositions.WALLET.assets; assets.forEach(asset => { const value = parseFloat(asset.value); if (value >= minValue) { highValueTokens.push({ symbol: asset.symbol, name: asset.name, balance: asset.balance, value: value, chain: chain.name }); } }); }); return highValueTokens.sort((a, b) => b.value - a.value); } const tokens = await getHighValueTokens(address, 50); console.log(`Tokens worth $50+: ${tokens.length}`); ``` Monitor transaction fees: ```javascript theme={null} async function getWalletFees(address) { const response = await fetch( `https://api.octav.fi/v1/wallet?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [wallet] = await response.json(); return { feesWei: wallet.fees, feesUSD: parseFloat(wallet.feesFiat), lastUpdated: new Date(parseInt(wallet.lastUpdated)) }; } const fees = await getWalletFees(address); console.log(`Total fees paid: $${fees.feesUSD.toFixed(2)}`); console.log(`Last updated: ${fees.lastUpdated.toLocaleString()}`); ``` *** ## Comparison: Wallet vs Portfolio **Wallet Endpoint** (`/v1/wallet`) * Returns only tokens in direct wallet custody * Shows the `wallet` protocol only * Excludes DeFi positions (lending, staking, liquidity pools) * Best for simple balance checks **Portfolio Endpoint** (`/v1/portfolio`) * Returns complete portfolio including DeFi positions * Shows all protocols (wallet, lending, staking, etc.) * Includes positions on Aave, Uniswap, etc. * Best for comprehensive portfolio view **Example:** * Wallet: Shows 1 ETH in your address * Portfolio: Shows 1 ETH + 2 ETH deposited in Aave + LP tokens in Uniswap **Use /v1/wallet when:** * You only need basic token balances * Building a simple wallet balance viewer * Checking liquid/available assets * Monitoring tokens ready to transfer **Use /v1/portfolio when:** * You need complete financial picture * Tracking DeFi positions * Calculating total net worth * Building portfolio analytics dashboard Both endpoints return the same data structure: * Same top-level fields (address, networth, chains) * Same asset format (balance, value, price) * Same nested structure The only difference: * Wallet: `assetByProtocols` contains only `wallet` key * Portfolio: `assetByProtocols` contains all protocols *** ## Best Practices Wallets can hold assets on multiple chains: ```javascript theme={null} const walletProtocol = wallet.assetByProtocols.wallet; const chainCount = Object.keys(walletProtocol.chains).length; console.log(`Assets on ${chainCount} chains`); // Process each chain Object.entries(walletProtocol.chains).forEach(([key, chain]) => { console.log(`${chain.name}: $${parseFloat(chain.value).toFixed(2)}`); }); ``` Native tokens (ETH, MATIC, etc.) have special contract address: ```javascript theme={null} function isNativeToken(asset) { return asset.contract === '0x0000000000000000000000000000000000000000'; } const assets = chain.protocolPositions.WALLET.assets; const nativeToken = assets.find(isNativeToken); if (nativeToken) { console.log(`Native balance: ${nativeToken.balance} ${nativeToken.symbol}`); } ``` Filter out very small token amounts (dust): ```javascript theme={null} const MIN_DUST_VALUE = 0.01; // $0.01 const significantTokens = assets.filter(asset => { return parseFloat(asset.value) > MIN_DUST_VALUE; }); console.log(`${significantTokens.length} tokens above dust threshold`); ``` Handle scientific notation in prices: ```javascript theme={null} function formatPrice(priceString) { const price = parseFloat(priceString); if (price < 0.01) { // Use scientific notation for very small prices return price.toExponential(4); } else if (price < 1) { return price.toFixed(4); } else { return price.toFixed(2); } } console.log(formatPrice("1.0668e-8")); // "1.0668e-08" console.log(formatPrice("3442.76")); // "3442.76" ``` *** ## Common Patterns Sum up token holdings across chains: ```javascript theme={null} function getTotalBySymbol(wallet, symbol) { let total = 0; const walletProtocol = wallet.assetByProtocols.wallet; Object.values(walletProtocol.chains).forEach(chain => { const assets = chain.protocolPositions.WALLET.assets; assets.forEach(asset => { if (asset.symbol.toLowerCase() === symbol.toLowerCase()) { total += parseFloat(asset.balance); } }); }); return total; } const totalETH = getTotalBySymbol(wallet, 'eth'); console.log(`Total ETH across all chains: ${totalETH}`); ``` Get tokens sorted by USD value: ```javascript theme={null} function getSortedAssets(wallet) { const walletProtocol = wallet.assetByProtocols.wallet; const allAssets = []; Object.values(walletProtocol.chains).forEach(chain => { const assets = chain.protocolPositions.WALLET.assets; assets.forEach(asset => { allAssets.push({ ...asset, chainName: chain.name }); }); }); return allAssets.sort((a, b) => { return parseFloat(b.value) - parseFloat(a.value); }); } const sorted = getSortedAssets(wallet); console.log('Top 3 holdings:', sorted.slice(0, 3)); ``` Find if wallet holds a specific token: ```javascript theme={null} function hasToken(wallet, contractAddress) { const walletProtocol = wallet.assetByProtocols.wallet; for (const chain of Object.values(walletProtocol.chains)) { const assets = chain.protocolPositions.WALLET.assets; const found = assets.find(asset => asset.contract.toLowerCase() === contractAddress.toLowerCase() ); if (found) { return { has: true, balance: found.balance, value: found.value, chain: chain.name }; } } return { has: false }; } const uniToken = hasToken(wallet, '0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0'); if (uniToken.has) { console.log(`Found ${uniToken.balance} UNI on ${uniToken.chain}`); } ``` *** ## Related Endpoints Complete portfolio including DeFi positions Transaction history for wallet Detailed token breakdown (PRO) Check claimable airdrops # Crypto Portfolio API Source: https://docs.octav.fi/api/introduction Integrate multi-chain crypto portfolio tracking, decoded DeFi positions and transaction history into your application with the Octav REST API. Welcome to the Octav API documentation. Here you'll find everything you need to integrate cryptocurrency portfolio tracking, transaction history, and analytics into your applications. Get started with your first API call in minutes Learn how to authenticate your API requests Explore all available endpoints Understand our credit-based pricing model *** ## What You Can Build The Octav API enables you to: * Retrieve real-time portfolio balances across multiple chains * Track assets in wallets and DeFi protocols * Monitor net worth and PnL metrics * Include protocol positions * Access complete transaction history * Filter by chain, protocol, transaction type * Search transactions with full-text search * Track fees and profit/loss per transaction * Get detailed token breakdowns by protocol * View token distribution across positions * Track token balances and valuations * Monitor historical token holdings * Retrieve historical portfolio snapshots * Subscribe to addresses for daily snapshots * Analyze portfolio performance over time * Track net worth changes *** ## Key Features Support for 20+ blockchain networks including Ethereum, Arbitrum, Base, Solana, and more Fresh data with 1-minute caching and on-demand sync capabilities Track positions across hundreds of DeFi protocols automatically Pay only for what you use with our flexible credit system Enterprise-grade security and compliance standards Rate limiting, error handling, and status monitoring built-in *** ## API Overview ### Base URL ``` https://api.octav.fi ``` ### Authentication All API requests require an API key passed in the Authorization header: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` **Get Your API Key** — Create and manage API keys at [data.octav.fi](https://data.octav.fi/) ### Rate Limits **360 requests / minute / API key** Higher limits available upon request ### Response Format All endpoints return JSON responses with consistent structure: ```json theme={null} { "data": { ... }, "status": "success" } ``` *** ## All Endpoints The complete API surface. Every endpoint uses `Authorization: Bearer YOUR_API_KEY` against the base URL `https://api.octav.fi`. | Endpoint | Method | Description | Cost | | ------------------------------------------------------------- | ------ | ---------------------------------------------------------- | --------------------------- | | [`/v1/portfolio`](/api/endpoints/portfolio) | GET | Holdings across wallets and DeFi protocols | 1 credit | | [`/v1/portfolio/at-block`](/api/endpoints/portfolio-at-block) | GET | Portfolio valued at a historical block (Ethereum) | Add-on + 1 credit | | [`/v1/virtual-users`](/api/endpoints/virtual-users) | GET | List virtual users (Pro) | 1 credit | | [`/v1/virtual-users/portfolio`](/api/endpoints/virtual-users) | GET | Virtual user holdings (Pro) | 1 credit / address | | [`/v1/nav`](/api/endpoints/nav) | GET | Net Asset Value — `{nav, currency, conversionPrice}` | 1 credit | | [`/v1/wallet`](/api/endpoints/wallet) | GET | Wallet token balances, excluding DeFi positions | 1 credit | | [`/v1/transactions`](/api/endpoints/transactions) | GET | Transaction history with filtering and search | 1 credit | | [`/v1/approvals/{chain}`](/api/endpoints/approvals) | GET | ERC-20 token approval records | 1 credit | | [`/v1/token-overview`](/api/endpoints/token-overview) | GET | Token breakdown by protocol (Pro) | 1 credit | | [`/v1/airdrop`](/api/endpoints/airdrop) | GET | Claimable airdrops (Solana) | 1 credit | | [`/v1/historical`](/api/endpoints/historical-portfolio) | GET | Portfolio snapshot for a past date | 1 credit | | [Subscribe Snapshot](/api/endpoints/subscribe-snapshot) | POST | Enable daily portfolio snapshots | 1200 credits | | [`/v1/sync-transactions`](/api/endpoints/sync) | POST | Trigger transaction indexing | 1 credit + 1 / 250 txns | | [`/v1/contract-protocol`](/api/endpoints/contract-protocol) | GET | Resolve a contract address to its DeFi protocol | 5 credits (refunded on 404) | | [`/v1/beacon/validators/*`](/api/endpoints/beacon-validators) | GET | Ethereum validator details, rewards, withdrawals, deposits | Add-on | | [`/v1/chains`](/api/endpoints/chains) | GET | Supported blockchain networks | Free | | [`/v1/chains/{chainKey}/protocols`](/api/endpoints/protocols) | GET | Protocols available on a chain | Free | | [`/v1/status`](/api/endpoints/status) | GET | Sync status and data freshness | Free | | [`/v1/credits`](/api/endpoints/credits) | GET | Remaining credit balance | Free | **This API-key REST API is the default way to use Octav.** A subset of five endpoints is also available without an API key via the x402 payment protocol at [`/v1/agent/*`](/api/endpoints/agent-x402) — use that only if you specifically want pay-per-call, or your agent holds a wallet and cannot store an API key. Transaction history is **not** available over x402. ### Most-used endpoints **GET** `/v1/portfolio` Retrieve portfolio holdings with asset breakdown by protocol and chain **Cost:** 1 credit per call **GET** `/v1/virtual-users` · `/v1/virtual-users/portfolio` List virtual users and retrieve their portfolio holdings (Pro) **Cost:** 1 credit per call / per address **GET** `/v1/nav` Retrieve Net Asset Value of an address **Cost:** 1 credit per call **GET** `/v1/transactions` Get paginated transaction history with filtering and search **Cost:** 1 credit per call **GET** `/v1/token-overview` Detailed token breakdown across wallet and protocol positions **Cost:** 1 credit per call **GET** `/v1/historical` Retrieve historical portfolio snapshots for any date **Cost:** 1 credit per call **POST** `/v1/sync-transactions` Trigger transaction synchronization for an address **Cost:** 1 credit + 1 per 250 txns **GET** `/v1/status` Check sync status and data freshness **Cost:** Free (0 credits) **GET** `/v1/credits` Check your remaining credit balance **Cost:** Free (0 credits) *** ## Next Steps Visit [data.octav.fi](https://data.octav.fi/) to create your API key Follow our [Quickstart Guide](/api/quickstart) to make your first API call Browse the [API Reference](/api/endpoints/portfolio) to see all available endpoints Track your credit usage and manage your account at the [Dev Portal](https://data.octav.fi/) *** ## Need Help? Get help from the team and community Reach out for enterprise support # Migrate from DeBank Source: https://docs.octav.fi/api/migrate/from-debank A 1:1 mapping from the DeBank Cloud API to the Octav API A drop-in mapping from the **DeBank Cloud (Pro OpenAPI)** to Octav. The biggest wins: * **One call instead of three.** DeBank splits a portfolio across `all_token_list`, `all_complex_protocol_list`, and `total_balance`. Octav returns wallet tokens, DeFi positions, and net worth from a single [`GET /v1/portfolio`](/api/endpoints/portfolio). * **Solana included.** DeBank is EVM-only. The same Octav endpoint accepts base58 Solana addresses. * **USD values precomputed.** DeBank gives you `amount` and `price`; you multiply. Octav returns `value` (and `price`) on every asset. * **P\&L and cost basis** come back in the same response. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. ## Endpoint mapping | Use case | DeBank | Octav | | ---------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens (all chains) | `GET /v1/user/all_token_list` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | `GET /v1/user/all_complex_protocol_list` | same `GET /v1/portfolio` → `assetByProtocols.` | | Off-chain apps (perps, etc.) | `GET /v1/user/complex_app_list` | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | `GET /v1/user/total_balance` | same response → `networth` + `chains` | | Transaction history | `GET /v1/user/history_list` | [`GET /v1/transactions`](/api/endpoints/transactions) | | Token metadata / price | `GET /v1/token` | inline on every asset (`price`, `value`, `decimal`) | Everything except history comes from a **single** `GET /v1/portfolio` call. There is no separate token / protocol / balance request to merge. ## Authentication DeBank uses an `AccessKey` header. Octav uses a standard `Authorization: Bearer` token. ```bash DeBank theme={null} curl "https://pro-openapi.debank.com/v1/user/all_token_list?id=0x6426af179aabebe47666f345d69fd9079673f6cd&is_all=false" \ -H "AccessKey: YOUR_DEBANK_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances DeBank's `all_token_list` returns a flat array of tokens. In Octav, wallet tokens live under the `wallet` protocol, grouped by chain: ``` assetByProtocols.wallet.chains..protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "0x6426af179aabebe47666f345d69fd9079673f6cd"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "0x6426af179aabebe47666f345d69fd9079673f6cd" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | DeBank (`all_token_list[]`) | Octav (`…assets[]`) | Notes | | ----------------------------- | -------------------------------------- | --------------------------------------------------------------------------------- | | `id` | `contract` | Token address. Native tokens use the zero address in Octav. | | `chain` | `chainKey` | e.g. `eth` → `ethereum`. See [supported chains](/api/reference/supported-chains). | | `symbol` / `optimized_symbol` | `symbol` | | | `name` | `name` | | | `decimals` | `decimal` | | | `price` | `price` | USD, precomputed. | | `amount` | `balance` | Human-readable in both. | | `amount × price` | `value` | Octav precomputes `value`; you no longer multiply. | | `logo_url` | asset logo (with `includeImages=true`) | | | `is_core` | — | Octav applies its own spam filtering; there is no `is_core` flag to check. | ## Net worth and per-chain breakdown DeBank's `total_balance` maps directly onto the top-level `networth` and `chains` fields returned by the same portfolio call. | DeBank (`total_balance`) | Octav | Notes | | ------------------------ | ------------------------------------------ | ----------------------------------------------- | | `total_usd_value` | `networth` | Total net worth in USD. | | `chain_list[].usd_value` | `chains..value` | Per-chain total. | | `chain_list[].id` | `chains..key` / `chainId` | Chain identifier + numeric ID. | | — | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | — | `openPnl` / `closedPnl` / `totalCostBasis` | P\&L and cost basis (Octav adds these). | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions DeBank returns decoded positions under `all_complex_protocol_list[].portfolio_item_list[]`. Octav returns the same money under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …). ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` **Field mapping** | DeBank | Octav | Notes | | ------------------------------------------- | --------------------------------------- | --------------------------------- | | `protocol.id` / `protocol.name` | `assetByProtocols..name` | Keyed by protocol. | | `portfolio_item_list[].stats.net_usd_value` | `protocolPositions..totalValue` | Net position value. | | `portfolio_item_list[].name` | position `TYPE` key | e.g. `Lending`, `Liquidity Pool`. | | `detail.supply_token_list[]` | `assets[]` (supplied) | | | `detail.borrow_token_list[]` | `assets[]` (borrowed) | Debt is decoded, not netted away. | | `detail.reward_token_list[]` | `assets[]` (rewards) | | | `detail.health_rate` | position health rate | | | `chain` | `assetByProtocols..chains.` | | DeBank's off-chain "apps" (`complex_app_list`) — Hyperliquid, Lighter, prediction markets — are folded into the same `assetByProtocols` map in Octav, so you don't call a second endpoint for them. ## Transaction history Swap `history_list` for [`GET /v1/transactions`](/api/endpoints/transactions). ```bash DeBank theme={null} curl "https://pro-openapi.debank.com/v1/user/history_list?id=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "AccessKey: YOUR_DEBANK_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Calls per portfolio.** DeBank: `all_token_list` + `all_complex_protocol_list` + `complex_app_list` + `total_balance`. Octav: one `GET /v1/portfolio`. * **Chains.** DeBank is EVM-only. Octav covers EVM **and** Solana from the same endpoint and shape. * **Values.** DeBank returns `amount` + `price`; Octav also returns `value`. No client-side multiplication. * **Grouping.** DeBank groups by protocol → `portfolio_item_list`. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **Billing.** DeBank uses prepaid units. Octav charges **1 credit per call** (see [pricing](/api/pricing)). * **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. * **NFTs.** Octav's `/portfolio` focuses on fungible assets and DeFi positions; it does not enumerate NFTs the way DeBank's `all_nft_list` does. ## Need help migrating? Share your DeBank response shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate from GoldRush (Covalent) Source: https://docs.octav.fi/api/migrate/from-goldrush A 1:1 mapping from the GoldRush (Covalent) API to the Octav API A drop-in mapping from the **GoldRush (Covalent)** API to Octav. The biggest wins: * **Every chain in one call.** GoldRush balances are fetched per chain (`/{chain}/…/balances_v2`) or in batches of at most 10 chains (`/allchains/…/balances`), so full EVM coverage takes several requests. Octav returns every chain from a single [`GET /v1/portfolio`](/api/endpoints/portfolio). * **DeFi positions come back.** GoldRush is token balances only; its per-protocol DeFi endpoints are deprecated. Octav decodes lending, staking, liquidity, and farming positions in the same response. * **Solana in the same shape.** Both support Solana tokens. Octav also decodes Solana DeFi and returns it in the identical structure. * **Values ready to use.** GoldRush returns `balance` as a raw integer string you divide by `10^contract_decimals`. Octav returns a human-readable `balance` and a precomputed `value`. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. GoldRush already uses `Authorization: Bearer`, so only the base URL and response shape change. ## Endpoint mapping | Use case | GoldRush (Covalent) | Octav | | -------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens (one chain) | `GET /{chain}/address/{address}/balances_v2/` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | Wallet tokens (multichain) | `GET /allchains/address/{address}/balances/?chains=…` (max 10 chains) | same `GET /v1/portfolio` (all chains, one call) | | Solana tokens | `GET /solana-mainnet/address/{address}/balances_v2/` | same `GET /v1/portfolio` → `assetByProtocols.wallet` | | DeFi positions | deprecated (per-protocol `stacks/*`) | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | none (sum `quote` across calls) | same response → `networth` + `chains` | | Transaction history | `GET /{chain}/address/{address}/transactions_v3/` | [`GET /v1/transactions`](/api/endpoints/transactions) | Tokens, DeFi positions, and net worth all come from a **single** `GET /v1/portfolio` call. There is no per-chain loop and no 10-chain batching to merge. ## Authentication Both APIs use a standard `Authorization: Bearer` token. Only the base URL and the endpoint shape change: GoldRush scopes each request to a chain slug, while Octav takes the address once and returns everything. ```bash GoldRush theme={null} curl "https://api.covalenthq.com/v1/eth-mainnet/address/0x6426af179aabebe47666f345d69fd9079673f6cd/balances_v2/?quote-currency=USD&no-spam=true" \ -H "Authorization: Bearer YOUR_GOLDRUSH_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances GoldRush's `balances_v2` returns `data.items[]`, a flat array of tokens for one chain. In Octav, wallet tokens live under the `wallet` protocol, grouped by chain, for every chain at once: ``` assetByProtocols.wallet.chains..protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "0x6426af179aabebe47666f345d69fd9079673f6cd"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "0x6426af179aabebe47666f345d69fd9079673f6cd" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | GoldRush (`data.items[]`) | Octav (`…assets[]`) | Notes | | ---------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `contract_address` | `contract` | Token address. Native tokens use the zero address in Octav; GoldRush flags them with `native_token: true`. | | `{chain}` slug (request path) | `chainKey` | e.g. `eth-mainnet` → `ethereum`. See [supported chains](/api/reference/supported-chains). | | `contract_ticker_symbol` | `symbol` | | | `contract_name` | `name` | | | `contract_decimals` | `decimal` | | | `quote_rate` | `price` | USD, precomputed. | | `balance` ÷ 10^`contract_decimals` | `balance` | GoldRush returns a raw integer string; Octav returns it human-readable. | | `quote` | `value` | Octav returns `value` directly. | | `logo_urls.token_logo_url` | asset logo (with `includeImages=true`) | | | `is_spam` | — | Octav applies its own spam filtering; there is no flag to check. | ## Net worth and per-chain breakdown GoldRush has no single net-worth endpoint. You sum `quote` across every token and every chain call yourself. Octav returns the total directly on the same portfolio response, plus a per-chain breakdown. | GoldRush | Octav | Notes | | ------------------------------------------ | ------------------------------------------ | ----------------------------------------------- | | sum of `quote` across all items and chains | `networth` | Total net worth in USD, precomputed. | | sum of `quote` per chain call | `chains..value` | Per-chain total. | | `{chain}` slug | `chains..key` / `chainId` | Chain identifier + numeric ID. | | — | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | — | `openPnl` / `closedPnl` / `totalCostBasis` | P\&L and cost basis (Octav adds these). | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions GoldRush's per-protocol DeFi endpoints (the `stacks/*` routes for Aave, Curve, and others) are **deprecated and sunset**, so there is nothing left to map. This is net-new from Octav: the same portfolio call decodes DeFi positions under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …). ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` Solana DeFi is decoded too, in the same `assetByProtocols` structure. You do not switch endpoints or response shapes between EVM and Solana. ## Transaction history Swap `transactions_v3` for [`GET /v1/transactions`](/api/endpoints/transactions). GoldRush scopes history to one chain per call; Octav returns it for the address across chains. ```bash GoldRush theme={null} curl "https://api.covalenthq.com/v1/eth-mainnet/address/0x6426af179aabebe47666f345d69fd9079673f6cd/transactions_v3/" \ -H "Authorization: Bearer YOUR_GOLDRUSH_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Calls per portfolio.** GoldRush: one `balances_v2` call per chain, or `allchains/balances` batched at 10 chains max, plus a separate Solana call. Octav: one `GET /v1/portfolio` for every chain, EVM and Solana. * **DeFi.** GoldRush is token balances only; its DeFi position endpoints are deprecated. Octav decodes DeFi positions in the same response. * **Values.** GoldRush returns `balance` as a raw integer string you divide by `10^contract_decimals`. Octav returns a human-readable `balance` and a precomputed `value`. * **Net worth.** GoldRush has no net-worth endpoint; you sum `quote` yourself. Octav returns `networth` and per-chain `value` directly. * **Grouping.** GoldRush returns a flat `data.items[]` per chain. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **Billing.** Octav charges **1 credit per call** (see [pricing](/api/pricing)). * **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. ## Need help migrating? Share your GoldRush response shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate from Mobula Source: https://docs.octav.fi/api/migrate/from-mobula A 1:1 mapping from the Mobula API to the Octav API A drop-in mapping from the **Mobula API** to Octav. The biggest wins: * **One call instead of two.** Mobula splits a portfolio across `wallet/portfolio` (tokens) and `wallet/defi-positions` (DeFi). Octav returns wallet tokens, DeFi positions, and net worth from a single [`GET /v1/portfolio`](/api/endpoints/portfolio). * **Reliable DeFi coverage.** Mobula's DeFi decoding is inconsistent run-to-run. Octav decodes positions consistently across chains and protocols. * **Sanity-checked pricing.** Mobula can emit long-tail price outliers — a single mispriced defunct token can blow up your total. Octav validates pricing so `networth` stays trustworthy. * **P\&L and cost basis** come back in the same response. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. ## Endpoint mapping | Use case | Mobula | Octav | | -------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens (all chains) | `GET /wallet/portfolio` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | `GET /wallet/defi-positions` | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | `GET /wallet/portfolio` → `total_wallet_balance` (+ DeFi total) | same response → `networth` + `chains` | | Transaction history | `GET /wallet/transactions` | [`GET /v1/transactions`](/api/endpoints/transactions) | Tokens and DeFi both come from a **single** `GET /v1/portfolio` call. There is no separate positions request to merge. ## Authentication Mobula uses a raw `Authorization` header (no scheme prefix). Octav uses a standard `Authorization: Bearer` token. ```bash Mobula theme={null} curl "https://api.mobula.io/api/1/wallet/portfolio?wallet=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: YOUR_MOBULA_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances Mobula's `wallet/portfolio` returns `data.assets[]`, a flat array of tokens with per-chain splits in `cross_chain_balances`. In Octav, wallet tokens live under the `wallet` protocol, already grouped by chain: ``` assetByProtocols.wallet.chains..protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "0x6426af179aabebe47666f345d69fd9079673f6cd"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "0x6426af179aabebe47666f345d69fd9079673f6cd" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | Mobula (`data.assets[]`) | Octav (`…assets[]`) | Notes | | ------------------------------ | -------------------------------------- | -------------------------------------------------------------------------------- | | `asset.symbol` | `symbol` | | | `asset.name` | `name` | | | `asset.contracts[]` | `contract` | Per-chain token address. Native tokens use the zero address in Octav. | | `cross_chain_balances.` | per-chain split | Octav splits by `chainKey` natively; no `cross_chain_balances` object to unpack. | | `price` | `price` | USD, precomputed. | | `token_balance` | `balance` | Human-readable in both. | | `estimated_balance` | `value` | USD value of the holding. | | `asset.logo` | asset logo (with `includeImages=true`) | | ## Net worth and per-chain breakdown In Mobula you add `data.total_wallet_balance` (from `wallet/portfolio`) to the DeFi positions total to get a full net worth. Octav returns the already-summed `networth`, plus a per-chain breakdown in `chains`. | Mobula | Octav | Notes | | ---------------------------------------- | ------------------------------------------ | ----------------------------------------------- | | `data.total_wallet_balance` + DeFi total | `networth` | Single field, tokens and DeFi already summed. | | — | `chains..value` | Per-chain total (Octav adds this). | | — | `chains..key` / `chainId` | Chain identifier + numeric ID. | | — | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | — | `openPnl` / `closedPnl` / `totalCostBasis` | P\&L and cost basis (Octav adds these). | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions Mobula returns DeFi from a **second** call, `wallet/defi-positions`, as an array of `{ protocol, positions[] }`. Octav returns the same money in the first response under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …). ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` **Field mapping** | Mobula (`defi-positions[]`) | Octav | Notes | | --------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------- | | `protocol.name` | `assetByProtocols..name` | Keyed by protocol. | | `positions[].type` | position `TYPE` key | e.g. `LENDING`, `LIQUIDITYPOOL`, `STAKED`. | | `positions[].chain_id` | `assetByProtocols..chains.` | Strip the `evm:` prefix to get the numeric chain id (e.g. `evm:1` → `1`). | | `positions[].tokens[].symbol` | asset `symbol` | | | `positions[].tokens[].amount` | asset `balance` | Human-readable in both. | | `positions[].tokens[].amount_usd` | asset `value` | USD value, precomputed. | | `protocol.logo` | protocol logo (with `includeImages=true`) | | Mobula's DeFi coverage varies between runs and can miss protocols. Octav decodes positions consistently and folds every protocol into the same `assetByProtocols` map, so you never call a second endpoint. ## Transaction history Swap Mobula's `wallet/transactions` for [`GET /v1/transactions`](/api/endpoints/transactions). ```bash Mobula theme={null} curl "https://api.mobula.io/api/1/wallet/transactions?wallet=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: YOUR_MOBULA_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Calls per portfolio.** Mobula: `wallet/portfolio` + `wallet/defi-positions`. Octav: one `GET /v1/portfolio`. * **DeFi reliability.** Mobula's DeFi decoding is inconsistent run-to-run. Octav decodes positions consistently. * **Pricing.** Mobula can emit long-tail price outliers that distort totals. Octav sanity-checks pricing so `networth` stays trustworthy. * **Auth header.** Mobula sends the raw key in `Authorization`. Octav uses `Authorization: Bearer `. * **Chains.** Both cover EVM **and** Solana. Octav returns them in the same shape from one endpoint. * **Chain ids.** Mobula formats DeFi chain ids like `evm:1`; strip the `evm:` prefix for the numeric id. Octav keys chains by name (`ethereum`) with `chainId` alongside. * **Grouping.** Mobula returns `data.assets[]` (tokens) and a separate `defi-positions[]` array. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **Billing.** Mobula charges roughly 1 credit per chain. Octav charges **1 credit per call** (see [pricing](/api/pricing)). * **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. ## Need help migrating? Share your Mobula response shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate from Nansen Source: https://docs.octav.fi/api/migrate/from-nansen A 1:1 mapping from the Nansen API to the Octav API A drop-in mapping from the **Nansen API** to Octav. The biggest wins: * **One call instead of two.** Nansen splits a portfolio across `POST /profiler/address/current-balance` (tokens) and `POST /portfolio/defi-holdings` (DeFi). Octav returns wallet tokens, DeFi positions, and net worth from a single [`GET /v1/portfolio`](/api/endpoints/portfolio). * **No double-counting.** Merge Nansen's two endpoints naively and a protocol receipt token (an aToken, say) shows up in both `current-balance` and inside `defi-holdings`, inflating the total. Octav decodes each asset once, so the numbers add up. * **One GET, not two POSTs.** Nansen wants JSON bodies on POST requests and paginates balances. Octav returns everything in one GET with no page loop. * **Logos and P\&L included.** Nansen returns no token logos. Octav returns asset logos with `includeImages=true`, plus `openPnl`, `closedPnl`, and `totalCostBasis` in the same response. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. ## Endpoint mapping | Use case | Nansen | Octav | | -------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens (all chains) | `POST /profiler/address/current-balance` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | `POST /portfolio/defi-holdings` | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | sum of both responses | same response → `networth` + `chains` | | Transaction history | — | [`GET /v1/transactions`](/api/endpoints/transactions) | | Token metadata / price | inline on `current-balance` rows | inline on every asset (`price`, `value`, `decimal`) | Everything comes from a **single** `GET /v1/portfolio` call. There is no separate DeFi request to merge, and no pagination loop to unwind. ## Authentication Nansen uses an `apikey` header and POSTs a JSON body. Octav uses a standard `Authorization: Bearer` token on a GET. ```bash Nansen theme={null} curl -X POST "https://api.nansen.ai/api/v1/profiler/address/current-balance" \ -H "apikey: YOUR_NANSEN_KEY" \ -H "content-type: application/json" \ -d '{"chain":"all","address":"0x6426af179aabebe47666f345d69fd9079673f6cd","hide_spam_token":true,"pagination":{"page":1,"per_page":100}}' ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances Nansen's `current-balance` returns a paginated `data` array, one row per token. In Octav, wallet tokens live under the `wallet` protocol, grouped by chain, and arrive in full with no page loop: ``` assetByProtocols.wallet.chains..protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "0x6426af179aabebe47666f345d69fd9079673f6cd"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain — no pagination to unwind const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "0x6426af179aabebe47666f345d69fd9079673f6cd" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | Nansen (`current-balance` `data[]`) | Octav (`…assets[]`) | Notes | | ----------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------- | | `token_address` | `contract` | Token address. Native tokens use the zero address in Octav. | | `chain` | `chainKey` | e.g. `eth` → `ethereum`. See [supported chains](/api/reference/supported-chains). | | `token_symbol` | `symbol` | | | `token_name` | `name` | | | `token_amount` | `balance` | Human-readable in both. | | `price_usd` | `price` | USD, precomputed. | | `value_usd` | `value` | Precomputed in both. | | — | asset logo (with `includeImages=true`) | Nansen returns no logos; Octav adds them. | | `hide_spam_token` flag | — | Octav applies its own spam filtering; there is no flag to pass. | Nansen paginates `current-balance` and you loop pages until `pagination.is_last_page` is `true`. Octav returns every token in one response, so there is no page loop. ## Net worth and per-chain breakdown To get net worth from Nansen you sum the `value_usd` across `current-balance` plus the `total_value_usd` across `defi-holdings` — which is exactly where the double-count creeps in. Octav returns net worth directly on the top-level `networth` field, with each asset counted once, plus a per-chain breakdown under `chains`. | Nansen | Octav | Notes | | ------------------------------------------------- | ------------------------------------------ | ----------------------------------------------- | | sum of `current-balance` + `defi-holdings` values | `networth` | Octav counts each asset once — no double-count. | | per-chain sum (compute yourself) | `chains..value` | Per-chain total. | | `chain` string | `chains..key` / `chainId` | Chain identifier + numeric ID. | | — | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | — | `openPnl` / `closedPnl` / `totalCostBasis` | P\&L and cost basis (Octav adds these). | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions Nansen returns decoded positions under `defi-holdings.protocols[]`, each with a `tokens[]` array tagged by `position_type`. Octav returns the same money under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …). ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` **Field mapping** | Nansen (`defi-holdings.protocols[]`) | Octav | Notes | | ------------------------------------ | --------------------------------------------- | ------------------------------- | | `protocol_name` | `assetByProtocols..name` | Keyed by protocol. | | `total_value_usd` | `protocolPositions..totalValue` | Net position value. | | `tokens[].position_type` | asset role (supply / borrow / stake / reward) | Drives the position `TYPE` key. | | `tokens[].symbol` | asset `symbol` | | | `tokens[].amount` | asset `balance` | | | `tokens[].value_usd` | asset `value` | | | `chain` | `assetByProtocols..chains.` | | Because Octav decodes each asset once, a protocol receipt token never appears in both the wallet bucket and a DeFi position. That is the double-count you had to reconcile when merging Nansen's two endpoints. ## Transaction history Nansen has no dedicated portfolio transaction feed to migrate from. Octav provides one at [`GET /v1/transactions`](/api/endpoints/transactions). ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Calls per portfolio.** Nansen: `current-balance` (paginated) + `defi-holdings`. Octav: one `GET /v1/portfolio`. * **Double-counting.** Merging Nansen's two endpoints double-counts protocol receipt tokens. Octav decodes each asset once, so `networth` is correct out of the box. * **HTTP shape.** Nansen uses POST with JSON bodies and page loops. Octav uses a single GET with query params. * **Chains.** Both cover EVM and Solana. Octav serves them from the same endpoint and response shape. * **Grouping.** Nansen groups DeFi by `protocols[]` → `tokens[]`. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **Logos.** Nansen returns no token logos. Octav returns them with `includeImages=true`. * **Billing.** Nansen bills on credits with a subscription (one of the priciest). Octav charges **1 credit per call** (see [pricing](/api/pricing)). * **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. ## Need help migrating? Share your Nansen response shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate from Sim (Dune) Source: https://docs.octav.fi/api/migrate/from-sim A 1:1 mapping from the Sim (Dune) API to the Octav API A drop-in mapping from the **Sim (Dune) API** to Octav. Sim is **token balances only** today, so migrating is mostly additive: * **Keep your token balances, gain DeFi.** Sim's DeFi Positions API was sunset (it returns HTTP 410), so a DeFi wallet reads as just its loose tokens. Octav returns the same wallet tokens **and** the decoded DeFi positions Sim can no longer provide (lending, LP, staking, perps) from a single [`GET /v1/portfolio`](/api/endpoints/portfolio). * **Solana included.** Sim's balances are EVM-only. The same Octav endpoint accepts base58 Solana addresses, including Solana DeFi. * **Human-readable balances.** Sim returns a raw on-chain `amount` you divide by `10^decimals`. Octav returns `balance` already scaled. * **USD values precomputed.** Octav returns `value` on every asset, plus `networth` for the whole portfolio, so you never sum `value_usd` yourself. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. ## Endpoint mapping | Use case | Sim (Dune) | Octav | | -------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens (all chains) | `GET /v1/evm/balances/{address}` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | *sunset — returns HTTP 410* | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | *no endpoint (sum `value_usd` yourself)* | same response → `networth` + `chains` | | Transaction history | `GET /v1/evm/transactions/{address}` / `GET /v1/evm/activity/{address}` | [`GET /v1/transactions`](/api/endpoints/transactions) | | Token metadata / price | inline on each balance | inline on every asset (`price`, `value`, `decimal`) | Everything except history comes from a **single** `GET /v1/portfolio` call. Tokens, DeFi, and net worth arrive together — there is no second request to merge, and no DeFi endpoint to miss. ## Authentication Sim uses an `X-Sim-Api-Key` header. Octav uses a standard `Authorization: Bearer` token. ```bash Sim theme={null} curl "https://api.sim.dune.com/v1/evm/balances/0x6426af179aabebe47666f345d69fd9079673f6cd?metadata=logo&exclude_spam_tokens=true" \ -H "X-Sim-Api-Key: YOUR_SIM_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances Sim's `evm/balances` returns a flat `balances[]` array. In Octav, wallet tokens live under the `wallet` protocol, grouped by chain: ``` assetByProtocols.wallet.chains..protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "0x6426af179aabebe47666f345d69fd9079673f6cd"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "0x6426af179aabebe47666f345d69fd9079673f6cd" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | Sim (`balances[]`) | Octav (`…assets[]`) | Notes | | --------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `address` | `contract` | Token address. Native tokens use the zero address in Octav. | | `chain` | `chainKey` | e.g. `ethereum`, `arbitrum`. See [supported chains](/api/reference/supported-chains). | | `symbol` | `symbol` | | | `name` | `name` | | | `decimals` | `decimal` | | | `price_usd` | `price` | USD, precomputed. | | `amount` | `balance` | Sim returns the **raw** integer; you divide by `10^decimals`. Octav returns `balance` already human-readable. | | `value_usd` | `value` | Precomputed in both. | | `token_metadata.logo` | asset logo (with `includeImages=true`) | | ## Net worth and per-chain breakdown Sim has no net-worth endpoint — you would sum `value_usd` across the `balances[]` array yourself. Octav returns `networth` and a per-chain breakdown directly on the same portfolio call. | Sim | Octav | Notes | | --------------------------- | ------------------------------------------ | ----------------------------------------------- | | `sum(balances[].value_usd)` | `networth` | Total net worth in USD, precomputed. | | — | `chains..value` | Per-chain total. | | — | `chains..key` / `chainId` | Chain identifier + numeric ID. | | — | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | — | `openPnl` / `closedPnl` / `totalCostBasis` | P\&L and cost basis (Octav adds these). | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions Sim's DeFi Positions API was **sunset and now returns HTTP 410**, so there is nothing to map here — a DeFi wallet on Sim shows up as only its loose tokens. This is net-new capability from Octav: the same portfolio call decodes positions under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …). ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` Because Octav decodes DeFi in the same call, migrating off Sim closes the gap its sunset left: lending, LP, staking, and perps positions (on EVM **and** Solana) come back priced, with no extra endpoint. ## Transaction history Swap Sim's `evm/transactions` and `evm/activity` for [`GET /v1/transactions`](/api/endpoints/transactions). ```bash Sim theme={null} curl "https://api.sim.dune.com/v1/evm/activity/0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "X-Sim-Api-Key: YOUR_SIM_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Scope.** Sim is token balances only; its DeFi Positions API is sunset (HTTP 410). Octav returns wallet tokens **and** decoded DeFi positions from one `GET /v1/portfolio`. * **Chains.** Sim's balances are EVM-only. Octav covers EVM **and** Solana from the same endpoint and shape, including Solana DeFi. * **Balances.** Sim returns a raw `amount` you divide by `10^decimals`. Octav returns `balance` already human-readable. * **Values.** Sim returns `value_usd` per token but no portfolio total. Octav also returns `networth` and a per-chain breakdown, so you never sum values yourself. * **Grouping.** Sim returns a flat `balances[]` array. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **Billing.** Sim's free tier is winding down. Octav charges **1 credit per call** (see [pricing](/api/pricing)). * **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. ## Need help migrating? Share your Sim response shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate from TopLedger Source: https://docs.octav.fi/api/migrate/from-topledger A 1:1 mapping from the TopLedger API to the Octav API A drop-in mapping from the **TopLedger Wallets API** to Octav. The biggest wins: * **Solana *and* EVM.** TopLedger is Solana-only. The same Octav [`GET /v1/portfolio`](/api/endpoints/portfolio) accepts base58 Solana addresses **and** EVM addresses in the same shape. * **Loose token holdings included.** TopLedger's `holdings` endpoint returns empty for most wallets. Octav returns your loose SOL/SPL tokens with per-asset detail (`symbol`, `balance`, `price`, `value`, `contract`). * **Perps marked to market.** TopLedger values perps at deposited collateral and returns `pnl_usd` on the side. Octav folds unrealized PnL into net worth (collateral + PnL). * **Per-asset detail, not aggregates.** TopLedger returns aggregate category values. Octav returns each asset inside every position. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. ## Endpoint mapping | Use case | TopLedger | Octav | | ------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens | `GET /{wallet}/holdings` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | `GET /{wallet}/analyze` → `categories` | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | `GET /{wallet}/analyze` → `total_net_worth_usd` | same response → `networth` + `chains` | | Transaction history | *(not available)* | [`GET /v1/transactions`](/api/endpoints/transactions) | | Chains covered | Solana only | Solana **and** EVM | Wallet tokens, DeFi positions, and net worth all come from a **single** `GET /v1/portfolio` call. There is no separate `analyze` and `holdings` request to merge. ## Authentication TopLedger uses an `x-api-key` header. Octav uses a standard `Authorization: Bearer` token. ```bash TopLedger theme={null} curl "https://api.topledger.xyz/api/wallets/9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM/analyze" \ -H "x-api-key: YOUR_TOPLEDGER_KEY" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances TopLedger's `holdings` endpoint returns `{ holdings: [], holdings_count }`, and in practice `holdings` is empty for most wallets, so loose SOL and SPL tokens go unreported. Octav returns them under the `wallet` protocol, grouped by chain, with per-asset detail: ``` assetByProtocols.wallet.chains.solana.protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | TopLedger (`categories.holdings`) | Octav (`…assets[]`) | Notes | | --------------------------------- | ----------------------- | ------------------------------------------------------------ | | `value_usd` | sum of `assets[].value` | TopLedger gives one aggregate USD figure; Octav itemizes it. | | `token_count` | `assets.length` | | | *(no per-token list)* | `symbol` | Per-token symbol, absent in TopLedger. | | *(no per-token list)* | `balance` | Human-readable balance. | | *(no per-token list)* | `price` | USD, precomputed. | | *(no per-token list)* | `value` | USD value per token. | | *(no per-token list)* | `contract` | SPL mint address (native SOL uses the zero address). | ## Net worth and per-chain breakdown TopLedger's `total_net_worth_usd` maps onto the top-level `networth` returned by the same portfolio call. Octav also breaks the total down per chain and includes loose token holdings and marked-to-market perps that TopLedger omits. | TopLedger (`analyze`) | Octav | Notes | | --------------------- | ------------------------------------------ | --------------------------------------------------------------------------------- | | `total_net_worth_usd` | `networth` | Total net worth in USD. Octav also counts loose tokens and marks perps to market. | | *(Solana only)* | `chains.solana.value` | Per-chain total. EVM chains appear here too for multi-chain wallets. | | *(none)* | `chains..key` / `chainId` | Chain identifier + numeric ID. | | *(none)* | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | *(none)* | `openPnl` / `closedPnl` / `totalCostBasis` | P\&L and cost basis (Octav adds these). | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions TopLedger returns decoded positions as **aggregate category values** under `categories` (lending, perpetuals, staking, rewards, and so on). Octav returns the same money under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `MARGIN`, `STAKED`, `LIQUIDITYPOOL`, `FARMING`, …), with each underlying asset itemized. ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` **Field mapping** | TopLedger (`categories`) | Octav | Notes | | ------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------------ | | `lending.protocols[].protocol` | `assetByProtocols..name` | Keyed by protocol. | | `lending.protocols[].net_value_usd` | `protocolPositions.LENDING.totalValue` | Net position value. | | `lending.protocols[].deposit_value_usd` / `borrow_value_usd` | `assets[]` (supplied / borrowed) | Debt is decoded, not netted away. | | `perpetuals.protocols[]` | `protocolPositions.MARGIN` | `collateral_usd` + `pnl_usd` become one marked-to-market position. | | `staking.protocols[]` (`token_symbol`, `staked_value_usd`) | `protocolPositions.STAKED` | Staked asset itemized in `assets[]`. | | `rewards.protocols[].pending_rewards_usd` | `assets[]` (rewards) | Pending reward assets. | | `lp_positions` / `yield` / `governance` | matching `protocolPositions.` | `LIQUIDITYPOOL`, `FARMING`, governance, etc. | | *(all Solana)* | `assetByProtocols..chains.solana` | EVM protocols also land here for multi-chain wallets. | TopLedger reports perps at deposited collateral and hands you `pnl_usd` separately. Octav folds unrealized PnL into the `MARGIN` position and into `networth`, so the collateral + PnL figure is already marked to market. ## Transaction history TopLedger has no wallet transaction endpoint. Octav adds [`GET /v1/transactions`](/api/endpoints/transactions), covering Solana **and** EVM. ```bash TopLedger theme={null} # No wallet transaction endpoint available. ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Chains.** TopLedger is Solana-only. Octav covers Solana **and** EVM from the same endpoint and shape. * **Loose tokens.** TopLedger's `holdings` endpoint returns empty for most wallets. Octav returns loose SOL/SPL tokens with per-asset detail in the `wallet` bucket. * **Granularity.** TopLedger returns aggregate category values (`value_usd`, `token_count`). Octav returns each asset (`symbol`, `balance`, `price`, `value`) inside every position. * **Perps.** TopLedger values perps at deposited collateral and reports `pnl_usd` separately. Octav marks perps to market (collateral + unrealized PnL) and includes them in `networth`. * **Shape.** TopLedger groups by `categories` → `protocols[]`. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **Billing.** TopLedger charges roughly \$0.0004 per call. Octav charges **1 credit per call** (see [pricing](/api/pricing)). * **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. ## Need help migrating? Share your TopLedger response shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate from Zapper Source: https://docs.octav.fi/api/migrate/from-zapper A 1:1 mapping from the Zapper API to the Octav API A drop-in mapping from the **Zapper API** to Octav. The biggest wins: * **A REST GET instead of a GraphQL query.** Zapper makes you write a nested `portfolioV2` query with `edges`, `nodes`, inline fragments, and hand-picked field selections. Octav returns wallet tokens, DeFi positions, and net worth from a single [`GET /v1/portfolio`](/api/endpoints/portfolio) with no query body. * **USD values precomputed.** Both return `balanceUSD` / `value`, but Octav layers on P\&L and cost basis in the same response. * **Deeper, more consistent DeFi decoding.** Zapper is known to under-value Pendle-style yield tokens; Octav decodes them into their underlying assets so positions carry their real value. * **P\&L and cost basis** come back in the same response. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. ## Endpoint mapping | Use case | Zapper | Octav | | -------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens (all chains) | `portfolioV2.tokenBalances.byToken` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | `portfolioV2.appBalances.byApp` | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | `tokenBalances.totalBalanceUSD` + `appBalances.totalBalanceUSD` | same response → `networth` + `chains` | | Transaction history | GraphQL activity query | [`GET /v1/transactions`](/api/endpoints/transactions) | | Token metadata / price | field selection on each `node` | inline on every asset (`price`, `value`, `decimal`) | Everything except history comes from a **single** `GET /v1/portfolio` call. There is no GraphQL query to write, and no `tokenBalances` / `appBalances` branches to stitch back together. ## Authentication Zapper uses an `x-zapper-api-key` header on a GraphQL POST. Octav uses a standard `Authorization: Bearer` token on a GET. ```bash Zapper theme={null} curl "https://public.zapper.xyz/graphql" \ -X POST \ -H "Content-Type: application/json" \ -H "x-zapper-api-key: YOUR_ZAPPER_KEY" \ -d '{ "query": "query($addresses: [Address!]!) { portfolioV2(addresses: $addresses) { tokenBalances { totalBalanceUSD byToken(first: 50) { edges { node { symbol tokenAddress balance balanceUSD price network { name } } } } } } }", "variables": { "addresses": ["0x6426af179aabebe47666f345d69fd9079673f6cd"] } }' ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances Zapper's `tokenBalances.byToken` returns a paginated connection of `edges { node }`. In Octav, wallet tokens live under the `wallet` protocol, grouped by chain: ``` assetByProtocols.wallet.chains..protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "0x6426af179aabebe47666f345d69fd9079673f6cd"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "0x6426af179aabebe47666f345d69fd9079673f6cd" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | Zapper (`tokenBalances.byToken.edges.node`) | Octav (`…assets[]`) | Notes | | ------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `tokenAddress` | `contract` | Token address. Native tokens use the zero address in Octav. | | `network` | `chainKey` | Zapper returns a `network` object; Octav returns a flat chain key. See [supported chains](/api/reference/supported-chains). | | `symbol` | `symbol` | | | `balance` | `balance` | Human-readable in both. | | `price` | `price` | USD, precomputed. | | `balanceUSD` | `value` | Position value in USD. | | — | `name` / `decimal` | Octav returns token name and decimals inline. | ## Net worth and per-chain breakdown Zapper splits net worth across two branches you have to add together. Octav returns the same total on the top-level `networth` field, plus a per-chain `chains` breakdown. | Zapper | Octav | Notes | | --------------------------------------------------------------- | ------------------------------------------ | ----------------------------------------------- | | `tokenBalances.totalBalanceUSD` + `appBalances.totalBalanceUSD` | `networth` | Octav sums tokens and DeFi for you. | | per-app `network` totals | `chains..value` | Per-chain total. | | — | `chains..key` / `chainId` | Chain identifier + numeric ID. | | — | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | — | `openPnl` / `closedPnl` / `totalCostBasis` | P\&L and cost basis (Octav adds these). | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions Zapper returns app positions under `appBalances.byApp.edges[].node`, with each position split into `AppTokenPositionBalance` and `ContractPositionBalance` inline fragments, and token roles hidden behind a `metaType` enum. Octav returns the same money under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …). ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` **Field mapping** | Zapper (`appBalances.byApp.edges.node`) | Octav | Notes | | ---------------------------------------------- | --------------------------------------- | --------------------------------- | | `app.displayName` | `assetByProtocols..name` | Keyed by protocol. | | `network` | `assetByProtocols..chains.` | | | `balanceUSD` | `protocolPositions..totalValue` | Net position value. | | `positionBalances…tokens.metaType = SUPPLIED` | `assets[]` (supplied) | | | `positionBalances…tokens.metaType = BORROWED` | `assets[]` (borrowed) | Debt is decoded, not netted away. | | `positionBalances…tokens.metaType = CLAIMABLE` | `assets[]` (rewards) | | | inner `token.symbol` / `balanceUSD` | asset `symbol` / `value` | | Zapper is known to under-value Pendle-style yield tokens. Octav decodes those positions into their underlying assets, so `assets[]` and `totalValue` reflect the real position value. ## Transaction history Swap Zapper's GraphQL activity query for [`GET /v1/transactions`](/api/endpoints/transactions). ```bash Zapper theme={null} curl "https://public.zapper.xyz/graphql" \ -X POST \ -H "Content-Type: application/json" \ -H "x-zapper-api-key: YOUR_ZAPPER_KEY" \ -d '{ "query": "query($addresses: [Address!]!) { ... }", "variables": { "addresses": ["0x6426af179aabebe47666f345d69fd9079673f6cd"] } }' ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Protocol shape.** Zapper is a single GraphQL POST with a nested `portfolioV2` query (`edges`, `nodes`, inline fragments, field selection). Octav is one plain `GET /v1/portfolio` with no query body. * **Branches.** Zapper splits a portfolio across `tokenBalances` and `appBalances`, each with its own `totalBalanceUSD`. Octav returns both plus `networth` in one shape. * **Values.** Both return `balanceUSD` / `value`. Octav also returns `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. * **Grouping.** Zapper groups DeFi by app → `positionBalances` with a `metaType` enum. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **DeFi decoding.** Zapper under-values Pendle-style yield tokens; Octav decodes them into their underlying assets. * **Chains.** Both cover EVM **and** Solana from the same endpoint and shape. * **Billing.** Zapper bills roughly 3 credits per query. Octav charges **1 credit per call** (see [pricing](/api/pricing)). ## Need help migrating? Share your Zapper query shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate from Zerion Source: https://docs.octav.fi/api/migrate/from-zerion A 1:1 mapping from the Zerion API to the Octav API A drop-in mapping from the **Zerion API** to Octav. The biggest wins: * **A flat, already-priced object.** Zerion returns JSON:API (`data[].attributes`, related entities via `relationships`) that you traverse and stitch together. Octav returns a flat object where every asset is already USD-priced from a single [`GET /v1/portfolio`](/api/endpoints/portfolio). * **Compact responses.** Zerion payloads can grow very large. Octav responses stay compact. * **Decode once, no double-counting.** On LST and vault wallets Zerion can surface a receipt token as a `wallet` position *and* again as its decoded `deposit`/`staked` position, so a naive sum counts it twice. Octav decodes each asset once. * **Deeper protocol decoding.** Perps, options, and complex positions come back fully decoded, and **P\&L and cost basis** ship in the same response. **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`. ## Endpoint mapping | Use case | Zerion | Octav | | -------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------- | | Wallet tokens (all chains) | `GET /wallets/{address}/positions?filter[positions]=only_simple` | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | `GET /wallets/{address}/positions?filter[positions]=only_complex` | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth | `GET /wallets/{address}/portfolio` | same response → `networth` + `chains` | | Transaction history | `GET /wallets/{address}/transactions` | [`GET /v1/transactions`](/api/endpoints/transactions) | | Token metadata / price | `fungible_info` on each position | inline on every asset (`price`, `value`, `decimal`) | Everything except history comes from a **single** `GET /v1/portfolio` call. There is no `only_simple` / `only_complex` filter to run twice and merge. ## Authentication Zerion uses HTTP Basic auth: your API key followed by a colon, base64-encoded, in an `Authorization: Basic` header. Octav uses a standard `Authorization: Bearer` token. ```bash Zerion theme={null} # Basic auth: base64("YOUR_ZERION_KEY:") — note the trailing colon curl "https://api.zerion.io/v1/wallets/0x6426af179aabebe47666f345d69fd9079673f6cd/positions?filter[positions]=only_simple&sort=-value" \ -H "Authorization: Basic $(printf 'YOUR_ZERION_KEY:' | base64)" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` ## Wallet token balances Zerion's `only_simple` positions return a JSON:API array where each token lives under `data[].attributes`. In Octav, wallet tokens live under the `wallet` protocol, grouped by chain: ``` assetByProtocols.wallet.chains..protocolPositions.WALLET.assets[] ``` ```javascript JavaScript theme={null} const OCTAV_KEY = process.env.OCTAV_API_KEY; const address = "0x6426af179aabebe47666f345d69fd9079673f6cd"; const res = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`, { headers: { Authorization: `Bearer ${OCTAV_KEY}` } } ); const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address // Flatten wallet tokens across every chain const wallet = portfolio.assetByProtocols.wallet; const tokens = Object.values(wallet.chains).flatMap((chain) => Object.values(chain.protocolPositions).flatMap((pos) => pos.assets) ); tokens.forEach((t) => console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`) ); ``` ```python Python theme={null} import os import requests OCTAV_KEY = os.environ["OCTAV_API_KEY"] address = "0x6426af179aabebe47666f345d69fd9079673f6cd" res = requests.get( "https://api.octav.fi/v1/portfolio", params={"addresses": address, "includeImages": True}, headers={"Authorization": f"Bearer {OCTAV_KEY}"}, ) portfolio = res.json()[0] # one entry per address wallet = portfolio["assetByProtocols"]["wallet"] for chain in wallet["chains"].values(): for pos in chain["protocolPositions"].values(): for t in pos["assets"]: print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})") ``` ```bash cURL theme={null} curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` **Field mapping** | Zerion (`only_simple` position) | Octav (`…assets[]`) | Notes | | ----------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------ | | `attributes.fungible_info.symbol` | `symbol` | | | `attributes.fungible_info.name` | `name` | | | `attributes.quantity.float` | `balance` | Human-readable in both. | | `attributes.price` | `price` | USD, precomputed. | | `attributes.value` | `value` | Octav's `value` is always populated; Zerion's can be `null`. | | `attributes.fungible_info.implementations[].address` | `contract` | Token address. Native tokens use the zero address in Octav. | | `attributes.fungible_info.implementations[].decimals` | `decimal` | | | `relationships.chain.data.id` | `chainKey` | e.g. `ethereum`. See [supported chains](/api/reference/supported-chains). | | `attributes.flags.displayable` | — | Octav applies its own spam filtering; there is no `displayable` flag to check. | ## Net worth and per-chain breakdown Zerion's `/portfolio` endpoint maps directly onto the top-level `networth` and `chains` fields returned by the same portfolio call. | Zerion (`/portfolio`) | Octav | Notes | | --------------------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------- | | `data.attributes.total.positions` | `networth` | Total net worth in USD. | | `data.attributes.positions_distribution_by_chain.` | `chains..value` | Per-chain total. | | — | `chains..key` / `chainId` | Chain identifier + numeric ID (Octav adds this). | | — | `chains..valuePercentile` | Share of net worth per chain (Octav adds this). | | `data.attributes.changes.absolute_1d` (24h change) | — | No direct equivalent; Octav returns `openPnl` / `closedPnl` / `totalCostBasis` instead. | ```javascript JavaScript theme={null} console.log(`Net worth: $${portfolio.networth}`); Object.values(portfolio.chains).forEach((c) => console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`) ); ``` ## DeFi positions Zerion returns decoded positions under `only_complex`, each with a `position_type` (`deposit`, `loan`, `staked`, `reward`, `locked`). Octav returns the same money under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …). ```javascript JavaScript theme={null} // Every protocol except the "wallet" bucket is a DeFi position const defi = Object.entries(portfolio.assetByProtocols).filter( ([key]) => key !== "wallet" ); defi.forEach(([key, protocol]) => { console.log(`${protocol.name}: $${protocol.value}`); Object.values(protocol.chains).forEach((chain) => { Object.entries(chain.protocolPositions).forEach(([type, pos]) => { console.log(` ${type}: $${pos.totalValue}`); pos.assets.forEach((a) => console.log(` ${a.symbol}: $${a.value}`)); }); }); }); ``` ```python Python theme={null} for key, protocol in portfolio["assetByProtocols"].items(): if key == "wallet": continue print(f"{protocol['name']}: ${protocol['value']}") for chain in protocol["chains"].values(): for ptype, pos in chain["protocolPositions"].items(): print(f" {ptype}: ${pos['totalValue']}") ``` **Field mapping** | Zerion (`only_complex` position) | Octav | Notes | | -------------------------------------- | --------------------------------------- | -------------------------------------- | | `attributes.protocol` | `assetByProtocols..name` | Keyed by protocol. | | `attributes.position_type` = `deposit` | `assets[]` (supplied) | | | `attributes.position_type` = `loan` | `assets[]` (borrowed) | Debt is decoded, not netted away. | | `attributes.position_type` = `staked` | `assets[]` (staked) | | | `attributes.position_type` = `reward` | `assets[]` (rewards) | | | `attributes.value` | `protocolPositions..totalValue` | Contributes to the net position value. | | `relationships.chain.data.id` | `assetByProtocols..chains.` | | On LST and vault wallets, Zerion can list a receipt token twice — once as a `wallet` position and again as its decoded `deposit`/`staked` position. Octav decodes each asset once, so summing `assetByProtocols` never double-counts. ## Transaction history Swap `GET /wallets/{address}/transactions` for [`GET /v1/transactions`](/api/endpoints/transactions). ```bash Zerion theme={null} curl "https://api.zerion.io/v1/wallets/0x6426af179aabebe47666f345d69fd9079673f6cd/transactions" \ -H "Authorization: Basic $(printf 'YOUR_ZERION_KEY:' | base64)" ``` ```bash Octav theme={null} curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_OCTAV_KEY" ``` Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types). ## Key differences * **Response shape.** Zerion returns JSON:API — you walk `data[].attributes` and resolve related entities through `relationships`. Octav returns a flat object; no JSON:API traversal. * **Payload size.** Zerion responses can be very large. Octav responses stay compact. * **Values.** Zerion's `attributes.value` can be `null`; Octav returns a populated `value` (and `price`) on every asset. * **Decode once.** Zerion may surface an LST/vault receipt token as both a `wallet` and a `deposit`/`staked` position. Octav decodes each asset once, so naive sums don't double-count. * **Grouping.** Zerion splits `only_simple` vs `only_complex` and groups by `position_type`. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens. * **Chains.** Both cover EVM **and** Solana. Octav serves both from one flat response and shape. * **Billing.** Octav charges **1 credit per call** (see [pricing](/api/pricing)). * **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call. * **NFTs.** Octav's `/portfolio` focuses on fungible assets and DeFi positions; it does not enumerate NFTs the way Zerion's `/nft-positions` does. ## Need help migrating? Share your Zerion response shape and we'll map it. Full reference for the endpoint you'll be calling. # Migrate to Octav Source: https://docs.octav.fi/api/migrate/overview Move from any portfolio or wallet-data API to Octav with drop-in endpoint and field mappings Already integrated another portfolio, wallet-data, or DeFi API? Octav is designed as a **drop-in upgrade**. In most cases a multi-call, multi-vendor integration collapses into a **single `GET /v1/portfolio` call** that returns wallet tokens, decoded DeFi positions, net worth, and P\&L across EVM **and** Solana. **Get your API key** at [data.octav.fi](https://data.octav.fi/), then follow the guide for your current provider below. ## Why teams switch to Octav Wallet tokens **and** decoded DeFi positions **and** net worth in a single request. No merging three endpoints, no deduping receipt tokens. The same endpoint and response shape work for `0x…` and base58 addresses across 90+ chains. No separate Solana integration. Lending, LP, staking, perps, options, vesting and prediction markets are turned into real positions with underlying assets, health rates, and a per-asset price source. Every asset ships with `price`, `value`, cost basis and open/closed P\&L. No client-side pricing, no separate price feed. ## Pick your provider Collapse `all_token_list` + `all_complex_protocol_list` + `total_balance` into one call, and add Solana. Swap JSON:API positions for a flat, USD-priced portfolio, with receipt tokens decoded once (no double-count). Replace the `portfolioV2` GraphQL query with a single REST call. Merge `wallet/portfolio` + `wallet/defi-positions` into one response, with sanity-checked pricing. Replace the two profiler/portfolio POST endpoints with one call, no receipt-token double-count. Keep token balances, add the decoded DeFi positions Sim sunset, plus Solana. Replace `balances_v2` and add the DeFi decoding GoldRush deprecated. Keep Solana DeFi decoding, add loose token holdings, EVM, and mark-to-market perps. ## Authentication mapping Whatever header your current provider uses, Octav uses a standard **Bearer** token. | Provider | Their auth | Octav | | ---------- | ------------------------------------- | ----------------------------- | | DeBank | `AccessKey: ` | `Authorization: Bearer ` | | Zerion | `Authorization: Basic base64(:)` | `Authorization: Bearer ` | | Zapper | `x-zapper-api-key: ` | `Authorization: Bearer ` | | Mobula | `Authorization: ` | `Authorization: Bearer ` | | Nansen | `apikey: ` | `Authorization: Bearer ` | | Sim (Dune) | `X-Sim-Api-Key: ` | `Authorization: Bearer ` | | GoldRush | `Authorization: Bearer ` | `Authorization: Bearer ` | | TopLedger | `x-api-key: ` | `Authorization: Bearer ` | ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_API_KEY" ``` See [Authentication](/api/authentication) for details. ## The core idea: one endpoint Most providers split a portfolio across several calls (tokens, DeFi, net worth) and sometimes several products (EVM vs Solana). Octav returns all of it from one endpoint: | What you want | Typical elsewhere | Octav | | ------------------- | ----------------- | --------------------------------------------------------------------------- | | Wallet tokens | one call | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` | | DeFi positions | separate call | same `GET /v1/portfolio` → `assetByProtocols.` | | Net worth + P\&L | separate call | same response → `networth`, `openPnl`, `closedPnl` | | Per-chain breakdown | separate call | same response → `chains` | | Solana | separate product | same call, base58 address | | Transaction history | separate call | [`GET /v1/transactions`](/api/endpoints/transactions) | `GET /v1/portfolio` returns an **array with one entry per address**, so unwrap the first element for a single address (`const [portfolio] = await res.json()` / `res.json()[0]`). Each portfolio groups assets by protocol, then chain, then position: ``` portfolio.assetByProtocols..chains..protocolPositions..assets[] ``` Valid `` keys include `WALLET`, `LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, `MARGIN`, `DEPOSIT`, `REWARDS`, `YIELD`, `VAULT`, and `LOCKED`. Each asset carries `symbol`, `name`, `balance`, `price`, `value`, `decimal`, `contract`, and `chainContract` — already priced, so you never compute USD values yourself. Prefer not to touch REST at all? The [Octav MCP server](/api/ai-development/mcp-server) and [CLI](/api/ai-development/cli) expose the same data to agents and scripts. ## Need a hand? Migrating a live integration? We are happy to map your exact response shape or unblock an edge case. Ask the team directly and get migration help. Create your API key and manage credits. # API Pricing & Credit Costs Source: https://docs.octav.fi/api/pricing Credit-based Octav API pricing: what each endpoint costs, how credits work, and why they never expire. Octav uses a flexible credit-based system that allows you to pay only for what you use. Credits never expire, giving you complete control over your API costs. **Purchase Credits** - Buy credit packages at [data.octav.fi](https://data.octav.fi/) *** ## Credit Packages Choose the package that fits your needs. All credits never expire. **400 Credits** \$10.00 **\$0.025** per credit Try the API with minimal commitment **4,000 Credits** \$100.00 **\$0.025** per credit Perfect for small projects and testing **100,000 Credits** \$2,500.00 **\$0.025** per credit Ideal for growing applications **1,000,000 Credits** \$20,000.00 **\$0.020** per credit Best value for high-volume usage **Volume Discount** - Save 20% with the Intensive Usage package ($0.020 vs $0.025 per credit) *** ## Credit Costs ### API Endpoints These endpoints are completely free to call: | Endpoint | Method | Cost | | ------------- | ------ | ------------- | | `/v1/credits` | GET | **0 credits** | | `/v1/status` | GET | **0 credits** | Perfect for testing authentication and monitoring sync status. Most API calls cost 1 credit: | Endpoint | Method | Cost | | ----------------------- | ------ | -------------- | | `/v1/portfolio` | GET | **1 credit** | | `/v1/transactions` | GET | **1 credit** | | `/v1/token-overview` | GET | **1 credit** P | | `/v1/historical` | GET | **1 credit** | | `/v1/sync-transactions` | POST | **1 credit** | P *Requires Octav PRO subscription* When syncing transactions, indexing costs apply: **1 credit per 250 transactions indexed** * First-time indexing of an address incurs indexing costs * Once indexed, transactions are stored permanently * Future queries don't re-index (no additional cost) * Only new transactions since last sync require indexing **One-Time Cost:** Indexing happens once. After initial sync, you only pay the 1 credit API call fee. *** ## Cost Examples ### Example 1: Portfolio Tracking Tracking 5 wallets, checking portfolios once per hour: 5 addresses x 24 hours x 30 days = **3,600 API calls** 3,600 calls x 1 credit = **3,600 credits** 3,600 credits x $0.025 = **$90/month\*\* ### Example 2: Transaction History Initial sync for an address with 10,000 transactions: 1 sync-transactions call = **1 credit** 10,000 transactions / 250 = 40 batches x 1 credit = **40 credits** 1 + 40 = **41 credits** (\$1.03) Only 1 credit per call for new transactions ### Example 3: High-Volume Application Application making 100,000 API calls per month: **Not recommended** - Would need 25 packages 100,000 credits x $0.025 = **$2,500\*\* **Perfect fit** - Exactly 100,000 credits 1 package = **\$2,500** **\$0.025** per credit **Best value** if you need flexibility Buy 1M credits for future growth 100,000 used x $0.020 = **$2,000\*\* **Save \$500** with volume discount *** ## How Credits Work ### Credit System Benefits Credits never expire - buy when convenient, use when needed Know exactly how much each API call costs No monthly fees or commitments - pay as you grow Save money with larger credit packages ### Permanent Indexing Advantage When you sync an address for the first time: 1. **Pay indexing cost** - 1 credit per 250 transactions 2. **Transactions stored permanently** - Data saved in Octav's system 3. **Future queries are cheap** - Only 1 credit per API call 4. **No re-indexing costs** - Historical data already available **Example:** * First sync: 10,000 transactions = 41 credits (\$1.03) * Future calls: 1 credit each (\$0.025) * Query 100 times: 100 credits (\$2.50) * **Total: 141 credits vs re-indexing 4,100 credits each time** This makes regular monitoring very cost-effective! *** ## Monitoring Usage ### Check Your Credits Use the free `/v1/credits` endpoint anytime: ```bash cURL theme={null} curl -X GET https://api.octav.fi/v1/credits \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const credits = await response.json(); console.log(`Remaining credits: ${credits}`); ``` ```python Python theme={null} response = requests.get( 'https://api.octav.fi/v1/credits', headers={'Authorization': f'Bearer {api_key}'} ) credits = response.json() print(f'Remaining credits: {credits}') ``` ### Developer Portal Track detailed usage at [data.octav.fi](https://data.octav.fi/): * Credit balance and history * API call volumes * Usage by endpoint * Historical usage patterns * Purchase more credits *** ## Cost Optimization Tips Leverage the free `/v1/status` and `/v1/credits` endpoints: * Check sync status before calling expensive endpoints * Monitor credit balance without cost * Verify authentication without using credits Portfolio data is cached for 1 minute: * Multiple calls within 1 minute return cached data * No need to implement your own caching for under 1 minute intervals * Use `waitForSync=false` (default) unless you need the absolute latest data Use filters to reduce unnecessary data: * Filter by `networks` to query specific chains * Use `txTypes` to get only relevant transaction types * Apply `startDate` and `endDate` for time-based queries * Set appropriate `limit` values for pagination For addresses with 100,000+ transactions: * Contact support for assistance * Consider filtering by specific chains * Use date ranges to limit initial sync * Addresses over 100k transactions are not auto-indexed *** ## Enterprise Solutions Need more than standard packages? **Volume discounts** for 10M+ credits Contact us for enterprise pricing **Priority support** and SLAs Technical account management **Custom rate limits** beyond 360/min Dedicated infrastructure options **White-label options** Custom integrations and features *** ## Frequently Asked Questions **No, credits never expire.** Purchase credits whenever convenient and use them at your own pace. Credits are non-refundable, but since they never expire, you can always use them in the future. API calls will fail with an insufficient credits error. Purchase more credits at [data.octav.fi](https://data.octav.fi/) to continue. Yes, all API keys under your account share the same credit pool. If you sync 125 transactions, you're charged for a full 250-transaction batch (1 credit). Batches are rounded up. We don't offer a free tier, but the Hobby package (\$10) provides 400 credits to try the API. The `/v1/credits` and `/v1/status` endpoints are always free. *** ## Get Started Sign up at [data.octav.fi](https://data.octav.fi/) Choose a credit package that fits your needs Create an API key in the developer portal Make your first API call using the [Quickstart Guide](/api/quickstart) *** ## Need Help? Ask questions about pricing and usage Discuss enterprise pricing and custom solutions # API Quickstart: Your First Call Source: https://docs.octav.fi/api/quickstart Make your first Octav API request in minutes — get a key, authenticate, and fetch a multi-chain portfolio in a single call. Get started with the Octav API and make your first request in minutes. **Prerequisites** — You'll need an API key from [data.octav.fi](https://data.octav.fi/). See [Authentication](/api/authentication) for details. *** ## Your First Request Let's start by checking your remaining credits - this endpoint is free and perfect for testing authentication. ```bash cURL theme={null} curl -X GET https://api.octav.fi/v1/credits \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const apiKey = 'YOUR_API_KEY'; const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const credits = await response.json(); console.log(`Remaining credits: ${credits}`); ``` ```python Python theme={null} import requests api_key = 'YOUR_API_KEY' response = requests.get( 'https://api.octav.fi/v1/credits', headers={'Authorization': f'Bearer {api_key}'} ) credits = response.json() print(f'Remaining credits: {credits}') ``` ```typescript TypeScript theme={null} const apiKey: string = process.env.OCTAV_API_KEY!; const response = await fetch('https://api.octav.fi/v1/credits', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const credits: number = await response.json(); console.log(`Remaining credits: ${credits}`); ``` ### Response ```json theme={null} 19033 ``` The credits endpoint returns a simple number indicating your remaining credit balance. **Success!** If you see a number, your API key is working correctly. *** ## Fetch a Portfolio Now let's retrieve portfolio data for a blockchain address. ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolio = await response.json(); console.log('Net Worth:', portfolio.networth); console.log('Protocols:', Object.keys(portfolio.assetByProtocols).length); ``` ```python Python theme={null} address = '0x6426af179aabebe47666f345d69fd9079673f6cd' response = requests.get( 'https://api.octav.fi/v1/portfolio', params={'addresses': address}, headers={'Authorization': f'Bearer {api_key}'} ) portfolio = response.json() print(f"Net Worth: {portfolio['networth']}") print(f"Protocols: {len(portfolio['assetByProtocols'])}") ``` ```typescript TypeScript theme={null} const address = '0x6426af179aabebe47666f345d69fd9079673f6cd'; interface Portfolio { address: string; networth: string; assetByProtocols: Record; chains: Record; } const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const portfolio: Portfolio = await response.json(); console.log('Net Worth:', portfolio.networth); console.log('Protocols:', Object.keys(portfolio.assetByProtocols).length); ``` ### Response Structure ```json theme={null} { "address": "0x6426af179aabebe47666f345d69fd9079673f6cd", "cashBalance": "0", "dailyIncome": "0", "dailyExpense": "0", "fees": "0", "feesFiat": "0", "lastUpdated": "1715173392020", "networth": "45231.89", "assetByProtocols": { "wallet": { "key": "wallet", "name": "Wallet", "value": "12453.20", "assets": [ { "balance": "1.5", "symbol": "ETH", "price": "3200.50", "value": "4800.75", "chain": "ethereum" } ] }, "aave_v3": { "key": "aave_v3", "name": "Aave V3", "value": "8934.12", "assets": [...] } }, "chains": { "ethereum": { "value": "25123.45", "protocols": [...] }, "arbitrum": { "value": "20108.44", "protocols": [...] } } } ``` **Cost:** 1 credit per call *** ## Query Transaction History Retrieve transaction history with pagination and filtering. ```bash cURL theme={null} curl -X GET "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&limit=10&offset=0&sort=DESC" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const params = new URLSearchParams({ addresses: '0x6426af179aabebe47666f345d69fd9079673f6cd', limit: '10', offset: '0', sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const transactions = await response.json(); console.log(`Retrieved ${transactions.length} transactions`); transactions.forEach(tx => { console.log(`${tx.type} - ${tx.chain.name} - ${tx.timestamp}`); }); ``` ```python Python theme={null} params = { 'addresses': '0x6426af179aabebe47666f345d69fd9079673f6cd', 'limit': 10, 'offset': 0, 'sort': 'DESC' } response = requests.get( 'https://api.octav.fi/v1/transactions', params=params, headers={'Authorization': f'Bearer {api_key}'} ) transactions = response.json() print(f'Retrieved {len(transactions)} transactions') for tx in transactions: print(f"{tx['type']} - {tx['chain']['name']} - {tx['timestamp']}") ``` ```typescript TypeScript theme={null} interface Transaction { hash: string; type: string; timestamp: string; chain: { key: string; name: string; }; from: string; to: string; value: string; fees: string; } const params = new URLSearchParams({ addresses: '0x6426af179aabebe47666f345d69fd9079673f6cd', limit: '10', offset: '0', sort: 'DESC' }); const response = await fetch( `https://api.octav.fi/v1/transactions?${params}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const transactions: Transaction[] = await response.json(); console.log(`Retrieved ${transactions.length} transactions`); ``` ### Key Parameters Wallet address (EVM or SOL) Number of transactions per page (1-250) Pagination offset Sort order: `DESC` (newest first) or `ASC` (oldest first) **Cost:** 1 credit per call *** ## Common Patterns ### Environment Variables Store your API key securely in environment variables: ```bash .env theme={null} OCTAV_API_KEY=your_api_key_here ``` ```javascript JavaScript theme={null} // Load from .env file require('dotenv').config(); const apiKey = process.env.OCTAV_API_KEY; ``` ```python Python theme={null} import os from dotenv import load_dotenv load_dotenv() api_key = os.getenv('OCTAV_API_KEY') ``` ```typescript TypeScript theme={null} import dotenv from 'dotenv'; dotenv.config(); const apiKey = process.env.OCTAV_API_KEY; ``` ### Error Handling Always handle errors gracefully: ```javascript JavaScript theme={null} async function fetchPortfolio(address) { try { const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); if (!response.ok) { const error = await response.json(); throw new Error(`API Error: ${error.message}`); } return await response.json(); } catch (error) { console.error('Failed to fetch portfolio:', error); throw error; } } ``` ```python Python theme={null} def fetch_portfolio(address): try: response = requests.get( 'https://api.octav.fi/v1/portfolio', params={'addresses': address}, headers={'Authorization': f'Bearer {api_key}'} ) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as e: print(f'HTTP Error: {e}') raise except requests.exceptions.RequestException as e: print(f'Request failed: {e}') raise ``` ```typescript TypeScript theme={null} async function fetchPortfolio(address: string): Promise { try { const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); if (!response.ok) { const error = await response.json(); throw new Error(`API Error: ${error.message}`); } return await response.json(); } catch (error) { console.error('Failed to fetch portfolio:', error); throw error; } } ``` ### Rate Limit Handling Respect rate limits with retry logic: ```javascript JavaScript theme={null} async function fetchWithRetry(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') || 60; console.log(`Rate limited. Retrying after ${retryAfter}s...`); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); continue; } return response; } throw new Error('Max retries exceeded'); } ``` ```python Python theme={null} import time def fetch_with_retry(url, headers, max_retries=3): for i in range(max_retries): response = requests.get(url, headers=headers) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) print(f'Rate limited. Retrying after {retry_after}s...') time.sleep(retry_after) continue return response raise Exception('Max retries exceeded') ``` *** ## Next Steps Detailed documentation for portfolio data Detailed documentation for nav data Query and filter transaction history Explore all available endpoints Understand credit costs *** ## Need Help? Get help from the community Reach out to our team # DeFi Protocol Position Types Source: https://docs.octav.fi/api/reference/protocol-types Every DeFi position type Octav decodes — lending, liquidity pools, staking, farming and more — and how each is categorised in the API response. Octav categorizes DeFi positions into specific protocol types, allowing you to understand how assets are being used across different protocols and strategies. **Protocol Positions** - These types appear in the `protocolPositions` object within the portfolio response *** ## Protocol Position Types **WALLET** - Direct wallet custody * Tokens held directly in wallet * Not deposited into any protocol * Fully liquid and transferable **LOCKED** - Time-locked tokens * Tokens with transfer restrictions * Vesting schedules * Cannot be transferred until unlock date **LENDING** - Supplied assets earning interest * Aave deposits * Compound supplies * Liquidity provided to money markets **DEPOSIT** - General protocol deposits * Funds deposited but not specifically lending * May earn yield or rewards **NFTLENDING** - NFT-backed lending * NFTs used as collateral * Borrowing against NFT value **NFTBORROWER** - Borrowing with NFT collateral * Active loan against NFT **NFTLENDER** - Lending to NFT borrowers * Providing liquidity for NFT loans **LIQUIDITYPOOL** - DEX liquidity positions * Uniswap V2/V3 positions * SushiSwap pools * Curve pools * Earn trading fees **NFTLIQUIDITYPOOL** - NFT liquidity pools * Sudoswap positions * NFT AMMs **FARMING** - Yield farming positions * Staking LP tokens for rewards * Farm protocol tokens * Multiple reward streams **LEVERAGEDFARMING** - Leveraged yield farming * Borrowed funds used for farming * Amplified returns (and risks) * Examples: Alpaca Finance, Gearbox **STAKED** - Staking positions * Protocol staking (e.g., ETH 2.0) * Single-sided staking * Earn staking rewards **NFTSTAKED** - Staked NFTs * NFTs staked for rewards * Gaming NFTs earning yield **YIELD** - General yield positions * Auto-compounding vaults * Yield aggregators **VAULT** - Strategy vaults * Yearn vaults * Auto-compounding strategies * Managed yield optimization **VAULT\_PS** - Vault with protocol-specific features * Specialized vault mechanics * Custom strategies **MARGIN** - Margin trading positions * Leveraged spot trades * Open long/short positions **MARGIN\_PS** - Protocol-specific margin * Custom margin implementations **PERPETUALS** - Perpetual futures * Perpetual swaps (dYdX, GMX) * Funding rate exposure **LEVERAGE** - General leveraged positions * Amplified exposure * Borrowed capital **OPTIONSBUYER** - Long options positions * Purchased calls or puts * Defined risk exposure **OPTIONSSELLER** - Short options positions * Sold calls or puts * Premium collection * Unlimited risk potential **DCA** - Dollar-cost averaging positions * Automated recurring purchases * Scheduled buy orders * Time-weighted accumulation **LIMITORDER** - Limit order positions * Pending limit orders * Price-triggered trades * Order book positions **AIRDROP** - Airdrop allocations * Claimable airdrop tokens * Pending distributions * Protocol rewards **REWARDS** - Claimable rewards * Unclaimed farming rewards * Staking rewards pending * Airdrop allocations **VESTING** - Vesting schedules * Team/investor tokens vesting * Gradual unlock over time **GOVERNANCE** - Governance positions * Locked governance tokens * Vote-escrowed positions (veTokens) * Protocol governance power **INVESTMENT** - Strategic investments * Protocol treasury positions * Long-term holdings **SPOT** - Spot trading * CEX-style spot positions * On-chain order books **INSURANCEBUYER** - Insurance coverage * Nexus Mutual coverage * Protocol insurance **INSURANCESELLER** - Insurance underwriter * Capital at risk for premiums * Providing insurance coverage **NFTFRACTION** - Fractionalized NFTs * Partial NFT ownership * Fractional.art positions **Minimal smart contract risk, no liquidation risk:** * WALLET - Direct custody * LOCKED - Time-locked tokens * STAKED - Simple staking * GOVERNANCE - Governance tokens **Smart contract risk, potential impermanent loss:** * LENDING - Money market deposits * DEPOSIT - Protocol deposits * LIQUIDITYPOOL - DEX liquidity * FARMING - Yield farming * VAULT - Strategy vaults * YIELD - Yield aggregators * DCA - Dollar-cost averaging * LIMITORDER - Limit orders * AIRDROP - Airdrop allocations **Liquidation risk, high complexity:** * LEVERAGE - Leveraged positions * MARGIN - Margin trading * LEVERAGEDFARMING - Leveraged farming * PERPETUALS - Perpetual futures * OPTIONSBUYER - Long options * OPTIONSSELLER - Short options (highest risk) ```typescript TypeScript Enum theme={null} export enum ProtocolPositionType { AIRDROP = 'AIRDROP', DEPOSIT = 'DEPOSIT', DCA = 'DCA', FARMING = 'FARMING', GOVERNANCE = 'GOVERNANCE', INSURANCEBUYER = 'INSURANCEBUYER', INSURANCESELLER = 'INSURANCESELLER', INVESTMENT = 'INVESMENT', LENDING = 'LENDING', LEVERAGE = 'LEVERAGE', LEVERAGEDFARMING = 'LEVERAGED FARMING', LIMITORDER = 'LIMITORDER', LIQUIDITYPOOL = 'LIQUIDITYPOOL', LOCKED = 'LOCKED', MARGIN = 'MARGIN', MARGIN_PS = 'MARGINPS', NFTBORROWER = 'NFTBORROWER', NFTFRACTION = 'NFTFRACTION', NFTLENDER = 'NFTLENDER', NFTLENDING = 'NFTLENDING', NFTLIQUIDITYPOOL = 'NFTLIQUIDITYPOOL', NFTSTAKED = 'NFTSTAKED', OPTIONSBUYER = 'OPTIONSBUYER', OPTIONSSELLER = 'OPTIONSSELLER', PERPETUALS = 'PERPETUALS', REWARDS = 'REWARDS', SPOT = 'SPOT', STAKED = 'STAKED', VAULT = 'VAULT', VAULT_PS = 'VAULTPS', VESTING = 'VESTING', WALLET = 'WALLET', YIELD = 'YIELD', } ``` ```json All Position Types theme={null} [ "AIRDROP", "DEPOSIT", "DCA", "FARMING", "GOVERNANCE", "INSURANCEBUYER", "INSURANCESELLER", "INVESMENT", "LENDING", "LEVERAGE", "LEVERAGED FARMING", "LIMITORDER", "LIQUIDITYPOOL", "LOCKED", "MARGIN", "MARGINPS", "NFTBORROWER", "NFTFRACTION", "NFTLENDER", "NFTLENDING", "NFTLIQUIDITYPOOL", "NFTSTAKED", "OPTIONSBUYER", "OPTIONSSELLER", "PERPETUALS", "REWARDS", "SPOT", "STAKED", "VAULT", "VAULTPS", "VESTING", "WALLET", "YIELD" ] ``` ```javascript Get Positions by Type theme={null} // Filter positions by type function getPositionsByType(portfolio, type) { const positions = []; Object.values(portfolio.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { if (chain.protocolPositions[type]) { positions.push({ protocol: protocol.name, chain: chain.name, position: chain.protocolPositions[type] }); } }); }); return positions; } // Get all lending positions const lendingPositions = getPositionsByType(portfolio, 'LENDING'); console.log(`Found ${lendingPositions.length} lending positions`); ``` ```javascript Calculate Risk Exposure theme={null} // Categorize portfolio by risk const RISK_CATEGORIES = { low: ['WALLET', 'LOCKED', 'STAKED', 'GOVERNANCE'], medium: ['LENDING', 'DEPOSIT', 'LIQUIDITYPOOL', 'FARMING', 'VAULT', 'YIELD', 'DCA', 'LIMITORDER', 'AIRDROP'], high: ['LEVERAGE', 'MARGIN', 'LEVERAGEDFARMING', 'PERPETUALS', 'OPTIONSBUYER', 'OPTIONSSELLER'] }; function calculateRiskExposure(portfolio) { const exposure = { low: 0, medium: 0, high: 0 }; Object.values(portfolio.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { Object.entries(chain.protocolPositions).forEach(([type, position]) => { const value = parseFloat(position.totalValue || position.value || 0); for (const [risk, types] of Object.entries(RISK_CATEGORIES)) { if (types.includes(type)) { exposure[risk] += value; break; } } }); }); }); return exposure; } const risk = calculateRiskExposure(portfolio); console.log(`Low risk: $${risk.low.toFixed(2)}`); console.log(`Medium risk: $${risk.medium.toFixed(2)}`); console.log(`High risk: $${risk.high.toFixed(2)}`); ``` *** ## Common Use Cases Track asset allocation across DeFi strategies: ```javascript theme={null} function analyzeDefiAllocation(portfolio) { const allocation = {}; Object.values(portfolio.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { Object.entries(chain.protocolPositions).forEach(([type, position]) => { const value = parseFloat(position.totalValue || position.value || 0); allocation[type] = (allocation[type] || 0) + value; }); }); }); // Calculate percentages const total = Object.values(allocation).reduce((sum, val) => sum + val, 0); const percentages = {}; Object.entries(allocation).forEach(([type, value]) => { percentages[type] = ((value / total) * 100).toFixed(2); }); return { allocation, percentages, total }; } const analysis = analyzeDefiAllocation(portfolio); console.log('DeFi Allocation:', analysis.percentages); ``` Monitor all yield-generating positions: ```javascript theme={null} const YIELD_TYPES = [ 'FARMING', 'LEVERAGEDFARMING', 'STAKED', 'LENDING', 'VAULT', 'YIELD', 'LIQUIDITYPOOL' ]; function getYieldPositions(portfolio) { const yieldPositions = []; Object.values(portfolio.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { Object.entries(chain.protocolPositions).forEach(([type, position]) => { if (YIELD_TYPES.includes(type)) { yieldPositions.push({ protocol: protocol.name, chain: chain.name, type: type, value: position.totalValue || position.value, assets: position.assets || [] }); } }); }); }); return yieldPositions; } const yields = getYieldPositions(portfolio); const totalYield = yields.reduce((sum, p) => sum + parseFloat(p.value), 0 ); console.log(`Total in yield: $${totalYield.toFixed(2)}`); ``` Identify positions with liquidation risk: ```javascript theme={null} const LIQUIDATION_RISK_TYPES = [ 'LEVERAGE', 'MARGIN', 'MARGIN_PS', 'LEVERAGEDFARMING', 'PERPETUALS' ]; function checkLiquidationRisk(portfolio) { const riskyPositions = []; Object.values(portfolio.assetByProtocols).forEach(protocol => { Object.values(protocol.chains).forEach(chain => { Object.entries(chain.protocolPositions).forEach(([type, position]) => { if (LIQUIDATION_RISK_TYPES.includes(type)) { riskyPositions.push({ protocol: protocol.name, chain: chain.name, type: type, value: position.totalValue, healthRate: position.healthRate, // If available assets: position.assets }); } }); }); }); return riskyPositions; } const risky = checkLiquidationRisk(portfolio); if (risky.length > 0) { console.warn(`⚠️ ${risky.length} positions with liquidation risk`); } ``` *** ## Best Practices Always check the position type before making assumptions: ```javascript theme={null} const position = chain.protocolPositions.LENDING; if (position) { // This is a lending position const supplied = position.supplyAssets || []; const borrowed = position.borrowAssets || []; console.log(`Supplied: ${supplied.length} assets`); console.log(`Borrowed: ${borrowed.length} assets`); } ``` Not all positions will have all asset types: ```javascript theme={null} const position = chain.protocolPositions.LIQUIDITYPOOL; // Safely access optional asset arrays const baseAssets = position?.baseAssets || []; const quoteAssets = position?.quoteAssets || []; const rewardAssets = position?.rewardAssets || []; ``` Use `totalValue` when available: ```javascript theme={null} function getPositionValue(position) { // Prefer totalValue if available if (position.totalValue) { return parseFloat(position.totalValue); } // Fall back to summing assets const assets = position.assets || []; return assets.reduce((sum, asset) => sum + parseFloat(asset.value || 0), 0 ); } ``` *** ## Related Resources See protocol positions in action View all supported blockchains Understand transaction categorization # Supported Chains Source: https://docs.octav.fi/api/reference/supported-chains Complete list of blockchain networks supported by Octav API Octav supports over 65 blockchain networks with varying levels of integration. Each chain has different capabilities for portfolio tracking and transaction indexing. **Growing Network** - We regularly add support for new chains. Check this page for the latest updates. *** ## Support Levels **Portfolio Support** means the chain is integrated with our portfolio tracking system. * Real-time balance tracking * Token holdings across protocols * DeFi position tracking * Net worth calculations * Multi-chain aggregation Available on: [/v1/portfolio](/api/endpoints/portfolio), [/v1/wallet](/api/endpoints/wallet), [/v1/airdrop](/api/endpoints/airdrop) **Transaction Support** means the chain provides full transaction history indexing. * Complete transaction history * Transaction categorization * Fee tracking * Search and filtering * Advanced analytics Available on: [/v1/transactions](/api/endpoints/transactions) *** ## Supported Chains These chains support both portfolio tracking and transaction history: | Chain | Key | Chain ID | | -------------- | --------------- | -------- | | Arbitrum | `arbitrum` | 42161 | | Avalanche | `avalanche` | 43114 | | Base | `base` | 8453 | | Binance | `binance` | 56 | | Blast | `blast` | 81457 | | Ethereum | `ethereum` | 1 | | Fraxtal | `fraxtal` | 252 | | Gnosis | `gnosis` | 100 | | Linea | `linea` | 59144 | | Optimism | `optimism` | 10 | | Plasma Testnet | `plasmatestnet` | - | | Polygon | `polygon` | 137 | | Solana | `solana` | - | | Sonic | `sonic` | 146 | | Starknet | `starknet` | - | | Unichain | `unichain` | - | These chains offer the most comprehensive data coverage for building full-featured applications. These chains support portfolio tracking but not full transaction history: | Chain | Key | | ------------- | --------------- | | Abstract | `abstract` | | Arbitrum Nova | `arbitrum_nova` | | Berachain | `berachain` | | BOB | `bob` | | Boba | `boba` | | BounceBit | `bouncebit` | | Celo | `celo` | | Core | `core` | | Corn | `corn` | | Cronos | `cronos` | | Etherlink | `ethlink` | | Everclear | `everclear` | | Fantom | `fantom` | | Flare | `flare` | | Fuse | `fuse` | | HashKey | `hsk` | | Hemi | `hemi` | | HyperEVM | `hyperevm` | | Hyperliquid | `hyperliquid` | | Ink | `ink` | | IoTeX | `iotex` | | Katana | `katana` | | Kava | `kava` | | Manta | `manta` | | Mantle | `mantle` | | Merlin | `merlin` | | Metis | `metis` | | Mint | `mint` | | Mode | `mode` | | Morph | `morph` | | Plasma | `plasma` | | Plume | `plume` | | Polygon zkEVM | `polygon_zkevm` | | Rari | `rari` | | Rootstock | `rsk` | | Scroll | `scroll` | | Shibarium | `shib` | | Soneium | `soneium` | | Sophon | `sophon` | | SwellChain | `swellchain` | | Taiko | `taiko` | | Telos | `telos` | | World Chain | `wc` | | X Layer | `xlayer` | | zkSync Era | `era` | | Zora | `zora` | Layer 2 scaling solutions for Ethereum: * **Arbitrum** - Full support * **Arbitrum Nova** - Portfolio only * **Base** - Full support * **Blast** - Full support * **Linea** - Full support * **Optimism** - Full support * **Polygon zkEVM** - Portfolio only * **Scroll** - Portfolio only * **zkSync Era** - Portfolio only Alternative Layer 1 blockchains: * **Avalanche** - Full support * **Binance** - Full support * **Fantom** - Portfolio only * **Solana** - Full support * **Sonic** - Full support Purpose-built chains: * **Berachain** - Portfolio only * **Hyperliquid** - Portfolio only (DeFi-focused) * **Starknet** - Transactions only * **Unichain** - Full support Filter chains by key or name: ```javascript theme={null} // Example: Get all chains with transaction support const chainsWithTxSupport = [ 'arbitrum', 'avalanche', 'base', 'binance', 'blast', 'ethereum', 'fraxtal', 'gnosis', 'linea', 'optimism', 'plasmatestnet', 'polygon', 'solana', 'sonic', 'starknet', 'unichain' ]; // Filter by chain key async function getPortfolioForChain(address, chainKey) { const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [portfolio] = await response.json(); return portfolio.chains[chainKey]; } ``` *** ## Using Chain Keys Chain keys are used throughout the Octav API to filter and identify blockchain networks: ```javascript Filter Transactions by Chain theme={null} // Get only Ethereum transactions const response = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&networks=ethereum`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); ``` ```javascript Check Chain Holdings theme={null} // Get holdings on specific chain const response = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const [portfolio] = await response.json(); const arbitrumHoldings = portfolio.chains.arbitrum; console.log(`Arbitrum holdings: $${arbitrumHoldings.value}`); ``` ```javascript Multi-Chain Query theme={null} // Query multiple chains const chains = ['ethereum', 'arbitrum', 'base']; const response = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&networks=${chains.join(',')}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); ``` *** ## Chain Data Structure When querying the API, chain data is returned in this format: ```json theme={null} { "chains": { "ethereum": { "name": "Ethereum", "key": "ethereum", "chainId": "1", "value": "5432.10", "valuePercentile": "65.3", "imgSmall": "https://images.octav.fi/chains/ethereum_icon.svg", "imgLarge": "https://images.octav.fi/chains/ethereum_icon.svg", "color": "#627EEA" } } } ``` Human-readable chain name Unique identifier for API queries Numeric chain ID (EVM chains) Total USD value on this chain Percentage of total portfolio *** ## Best Practices Always verify the chain supports your required endpoint: ```javascript theme={null} const SUPPORTED_CHAINS = { portfolio: ['ethereum', 'arbitrum', 'base', /* ... */], transactions: ['ethereum', 'arbitrum', 'base', /* ... */] }; function isChainSupported(chainKey, endpoint) { return SUPPORTED_CHAINS[endpoint].includes(chainKey); } if (isChainSupported('ethereum', 'transactions')) { // Query transactions } ``` Filter API responses to specific chains for better performance: ```javascript theme={null} // Instead of fetching all chains and filtering client-side const allPortfolio = await fetch( `https://api.octav.fi/v1/portfolio?addresses=${address}` ); // Prefer using network filters when available const ethOnly = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&networks=ethereum` ); ``` Not all addresses will have holdings on all chains: ```javascript theme={null} const [portfolio] = await response.json(); // Safely check for chain const arbitrumData = portfolio.chains?.arbitrum; if (arbitrumData) { console.log(`Arbitrum value: $${arbitrumData.value}`); } else { console.log('No Arbitrum holdings'); } ``` *** ## Frequently Asked Questions Transaction support requires deep integration with chain-specific indexers and explorers. We prioritize chains based on: * User demand * Chain activity and TVL * Technical feasibility * Data source availability Portfolio support is faster to implement and covers the most common use case (checking balances). We add new chains regularly based on ecosystem growth and user requests. Major chains typically get portfolio support within weeks of mainnet launch. To request a chain, contact us via [Discord](https://discord.com/invite/qvcknAa73A). Currently only Plasma Testnet is supported. We generally focus on mainnet data for production applications. * **key**: Octav's internal identifier (e.g., `ethereum`, `arbitrum`) * **chainId**: Standard EVM chain ID (e.g., `1` for Ethereum, `42161` for Arbitrum) Use **key** for Octav API queries. ChainId is provided for reference. *** ## Related Resources Query holdings across all chains Filter transactions by chain View all supported DeFi protocols Request a new chain on Discord # Transaction Types Source: https://docs.octav.fi/api/reference/transaction-types Complete guide to transaction categorization in Octav Octav automatically categorizes all blockchain transactions into specific types, making it easy to filter, analyze, and understand on-chain activity. **Smart Categorization** - Transactions are analyzed and categorized automatically based on method signatures, protocols, and transfer patterns *** ## Transaction Type Enum ```typescript theme={null} export enum TransactionType { ADDLIQUIDITY = "ADDLIQUIDITY", AIRDROP = "AIRDROP", APPROVAL = "APPROVAL", BORROW = "BORROW", BRIDGEIN = "BRIDGEIN", BRIDGEOUT = "BRIDGEOUT", CLAIM = "CLAIM", COLLECT = "COLLECT", CLOSEVAULT = "CLOSEVAULT", DEPOSIT = "DEPOSIT", DONATION = "DONATION", EXPENSE = "EXPENSE", FAILED = "FAILED", FOLLOW = "FOLLOW", IGNORE = "IGNORE", INTERACTION = "INTERACTION", LEND = "LEND", MINT = "MINT", MULTITYPE = "MULTITYPE", OPENVAULT = "OPENVAULT", REPAYVAULT = "REPAYVAULT", REMOVELIQUIDITY = "REMOVELIQUIDITY", SIGN = "SIGN", SELFTRANSFER = "SELFTRANSFER", SPAM = "SPAM", STAKE = "STAKE", SWAP = "SWAP", TRANSFERIN = "TRANSFERIN", TRANSFEROUT = "TRANSFEROUT", UNDEFINED = "UNDEFINED", UNSTAKE = "UNSTAKE", UNWRAP = "UNWRAP", VOTE = "VOTE", WITHDRAW = "WITHDRAW", WRAP = "WRAP" } ``` *** ## Transaction Categories **Receiving tokens from another address** * Incoming token transfers * Received payments * Peer-to-peer receipts Example: Receiving USDC from a friend **Sending tokens to another address** * Outgoing token transfers * Payments sent * Peer-to-peer sends Example: Sending ETH to another wallet **Moving tokens between your own wallets** * Transfers between addresses you control * Portfolio rebalancing * Moving funds to cold storage Example: Moving USDT from hot wallet to hardware wallet **Receiving assets from another blockchain** * Cross-chain bridge receipts * Assets arriving from L1 to L2 * Multi-chain transfers Example: Bridging USDC from Ethereum to Arbitrum **Sending assets to another blockchain** * Cross-chain bridge sends * Moving assets from L2 to L1 * Multi-chain transfers Example: Bridging ETH from Base to Ethereum **Exchanging one token for another** * DEX trades (Uniswap, Curve, etc.) * Token swaps * Trading activity Example: Swapping 1000 USDC for ETH on Uniswap **Depositing assets into a liquidity pool** * Providing liquidity on DEXs * Entering LP positions * Earning trading fees Example: Adding ETH/USDC to Uniswap V3 pool **Withdrawing assets from a liquidity pool** * Removing liquidity from DEXs * Exiting LP positions * Collecting principal + fees Example: Removing liquidity from Curve 3pool **Locking up assets to earn rewards** * Staking tokens * Entering staking pools * Beginning reward accrual Example: Staking ETH on Lido **Withdrawing previously staked assets** * Unstaking tokens * Exiting staking positions * May forfeit some rewards Example: Unstaking stETH from Lido **Supplying assets to lending protocols** * Depositing to Aave, Compound * Earning interest * Becoming a lender Example: Supplying USDC to Aave **Taking a loan from lending protocols** * Borrowing against collateral * Opening debt positions * Paying interest Example: Borrowing DAI on Aave against ETH collateral **Depositing funds into a protocol** * General protocol deposits * Entering vaults * Starting strategies Example: Depositing into Yearn vault **Withdrawing funds from a protocol** * Removing deposits * Exiting strategies * Claiming principal Example: Withdrawing from Yearn vault **Claiming rewards or distributions** * Harvesting farming rewards * Claiming airdrop tokens * Collecting yield Example: Claiming UNI rewards from liquidity mining **Receiving free tokens from a project** * Airdrop distributions * Free token allocations * Promotional distributions Example: Receiving ENS airdrop **Creating new tokens or NFTs** * Minting NFTs * Creating new ERC-20 tokens * Issuing assets Example: Minting an NFT on OpenSea **Converting native assets to wrapped versions** * ETH → WETH * Native tokens → ERC-20 versions * Required for DeFi usage Example: Wrapping ETH to WETH **Converting wrapped assets back to native** * WETH → ETH * Returning to native form * Gas optimization Example: Unwrapping WETH back to ETH **Participating in onchain governance** * Voting on proposals * DAO participation * Protocol governance Example: Voting on a Compound governance proposal **Granting permission to access tokens** * Token approvals for contracts * Required before interacting with DeFi * Security-relevant Example: Approving Uniswap to spend USDC **Creating a new vault position** * Opening MakerDAO CDP * Creating collateralized positions * Starting leverage strategies Example: Opening a vault on MakerDAO **Closing a vault after repaying debt** * Closing MakerDAO CDP * Exiting collateralized positions * Retrieving collateral Example: Closing a MakerDAO vault **Paying back borrowed funds in vault** * Repaying debt * Reducing liquidation risk * Unlocking collateral Example: Repaying DAI debt on MakerDAO **Gathering yield, fees, or rewards** * Collecting LP fees * Harvesting protocol rewards * Claiming earnings Example: Collecting fees from Uniswap V3 position **Transaction with multiple categorizations** * Complex transactions * Multiple actions in one tx * Zaps and batch operations Example: Swap + Add Liquidity in single transaction **General smart contract interaction** * Custom contract calls * Protocol-specific functions * Non-standard operations Example: Calling a custom function on a protocol **Transaction that did not succeed** * Insufficient gas * Reverted transactions * Failed smart contract calls Example: Swap failed due to slippage **Low-quality or unwanted transaction** * Spam airdrops * Phishing attempts * Irrelevant transfers Example: Unsolicited token airdrop **Unknown or uncategorized transaction** * Novel transaction types * Unsupported protocols * Edge cases Example: Interaction with new, unrecognized protocol **Transaction deliberately disregarded** * User-marked to ignore * Filtered out * Hidden from analysis Example: Test transaction marked to ignore **Message signing transaction** * Off-chain signature requests * Authentication signatures * Gasless approvals Example: Signing a message for authentication **Social action on blockchain** * Following users on Lens, Farcaster * Social graph interactions * Decentralized social Example: Following someone on Lens Protocol **Charitable or gift transfers** * Donations to causes * Tips to creators * Charitable contributions Example: Donating to Gitcoin grants **Payment for goods or services** * Paying for services in crypto * Business expenses * Off-chain purchases Example: Paying for SaaS subscription in USDC *** ## Using Transaction Types ```javascript Filter by Type theme={null} // Get only swaps const response = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&txTypes=SWAP`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const transactions = await response.json(); console.log(`Found ${transactions.length} swaps`); ``` ```javascript Multiple Types theme={null} // Get swaps and transfers const types = ['SWAP', 'TRANSFERIN', 'TRANSFEROUT']; const response = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&txTypes=${types.join(',')}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); ``` ```javascript Analyze Transaction Mix theme={null} // Count transactions by type function analyzeTransactionTypes(transactions) { const counts = {}; transactions.forEach(tx => { const type = tx.txType; counts[type] = (counts[type] || 0) + 1; }); // Sort by frequency const sorted = Object.entries(counts) .sort(([,a], [,b]) => b - a); return Object.fromEntries(sorted); } const analysis = analyzeTransactionTypes(transactions); console.log('Most common:', Object.keys(analysis)[0]); ``` *** ## Common Patterns ```javascript theme={null} // Get all DeFi-related transactions const DEFI_TYPES = [ 'SWAP', 'ADDLIQUIDITY', 'REMOVELIQUIDITY', 'STAKE', 'UNSTAKE', 'LEND', 'BORROW', 'DEPOSIT', 'WITHDRAW', 'CLAIM' ]; const response = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&txTypes=${DEFI_TYPES.join(',')}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); ``` ```javascript theme={null} // Track transactions that changed portfolio const PORTFOLIO_CHANGING = [ 'TRANSFERIN', 'TRANSFEROUT', 'SWAP', 'ADDLIQUIDITY', 'REMOVELIQUIDITY', 'STAKE', 'UNSTAKE', 'DEPOSIT', 'WITHDRAW', 'CLAIM', 'AIRDROP', 'MINT' ]; function getPortfolioChanges(transactions) { return transactions.filter(tx => PORTFOLIO_CHANGING.includes(tx.txType) ); } ``` ```javascript theme={null} // Filter out unwanted transactions const EXCLUDE_TYPES = ['SPAM', 'FAILED', 'APPROVAL', 'SIGN', 'IGNORE']; function cleanTransactions(transactions) { return transactions.filter(tx => !EXCLUDE_TYPES.includes(tx.txType) ); } const clean = cleanTransactions(allTransactions); console.log(`Kept ${clean.length} of ${allTransactions.length} txs`); ``` *** ## Best Practices Filter on the API side for better performance: ```javascript theme={null} // Good - Filter on API const swaps = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}&txTypes=SWAP` ); // Less efficient - Filter client-side const all = await fetch( `https://api.octav.fi/v1/transactions?addresses=${address}` ); const swaps = all.filter(tx => tx.txType === 'SWAP'); ``` MULTITYPE transactions may need special handling: ```javascript theme={null} if (tx.txType === 'MULTITYPE') { // Transaction has multiple aspects // Check other fields for details console.log('Complex transaction:', tx); } ``` Convert type enums to readable labels: ```javascript theme={null} const TYPE_LABELS = { 'TRANSFERIN': 'Received', 'TRANSFEROUT': 'Sent', 'SWAP': 'Swapped', 'ADDLIQUIDITY': 'Added Liquidity', 'REMOVELIQUIDITY': 'Removed Liquidity', // ... etc }; function getTypeLabel(txType) { return TYPE_LABELS[txType] || txType; } ``` *** ## Related Resources Query and filter transactions Understand protocol positions View supported blockchains # Automations Source: https://docs.octav.fi/cli/automations Production-ready automation workflows — scheduled airdrop scanning, transaction monitoring, nightly exports, portfolio reports, and credit management Real-world automation scripts that run on a schedule and notify you when something matters. Each workflow includes the full script, scheduler setup (cron + macOS launchd), realistic output, and customization tips. All scripts assume you've already authenticated with `octav auth set-key YOUR_API_KEY`. See [Authentication](/cli/overview#authentication) for setup. For basic CLI usage and one-liners, see [Examples](/cli/examples). *** ## 1. Daily Airdrop Scanner Checks a Solana wallet for unclaimed airdrops every day at 2pm. Sends a macOS desktop notification when new airdrops are found and logs results for history. ```bash theme={null} #!/bin/bash # airdrop-scanner.sh — Daily airdrop check with desktop notifications ADDR="7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU" LOG_DIR="$HOME/.octav/logs" LOG_FILE="$LOG_DIR/airdrops.log" mkdir -p "$LOG_DIR" RESULT=$(octav airdrop --address "$ADDR" --raw 2>&1) if [ $? -ne 0 ]; then echo "[$(date)] ERROR: $RESULT" >> "$LOG_FILE" exit 1 fi # Count unclaimed airdrops UNCLAIMED=$(echo "$RESULT" | jq '[.airdrops[] | select(.eligible == true)] | length') TOTAL_VALUE=$(echo "$RESULT" | jq '[.airdrops[] | select(.eligible == true) | .value_usd] | add // 0') # Log the check echo "[$(date)] Checked $ADDR — $UNCLAIMED unclaimed airdrop(s), \$$TOTAL_VALUE total" >> "$LOG_FILE" # Send macOS notification if airdrops found if [ "$UNCLAIMED" -gt 0 ]; then DETAILS=$(echo "$RESULT" | jq -r ' [.airdrops[] | select(.eligible == true) | "\(.program): \(.amount) \(.token) (~$\(.value_usd))"] | join(", ") ') osascript -e "display notification \"$DETAILS\" with title \"Octav Airdrop Alert\" subtitle \"$UNCLAIMED unclaimed — \$$TOTAL_VALUE total\"" echo "[$(date)] NOTIFIED: $DETAILS" >> "$LOG_FILE" fi ``` **Sample log output:** ```text theme={null} [Mon Jan 15 14:00:01 2025] Checked 7xKXtg...gAsU — 2 unclaimed airdrop(s), $601 total [Mon Jan 15 14:00:01 2025] NOTIFIED: Jupiter (JUP): 847 JUP (~$512), Parcl (PRCL): 340 PRCL (~$89) [Tue Jan 16 14:00:01 2025] Checked 7xKXtg...gAsU — 2 unclaimed airdrop(s), $601 total [Wed Jan 17 14:00:02 2025] Checked 7xKXtg...gAsU — 1 unclaimed airdrop(s), $89 total ``` ```bash theme={null} # Run daily at 2pm 0 14 * * * /path/to/airdrop-scanner.sh ``` Add with `crontab -e` or pipe it in: ```bash theme={null} (crontab -l 2>/dev/null; echo "0 14 * * * $HOME/scripts/airdrop-scanner.sh") | crontab - ``` Save as `~/Library/LaunchAgents/fi.octav.airdrop-scanner.plist`: ```xml theme={null} Label fi.octav.airdrop-scanner ProgramArguments /bin/bash /Users/YOU/scripts/airdrop-scanner.sh StartCalendarInterval Hour 14 Minute 0 StandardOutPath /tmp/airdrop-scanner.stdout StandardErrorPath /tmp/airdrop-scanner.stderr ``` Load it: ```bash theme={null} launchctl load ~/Library/LaunchAgents/fi.octav.airdrop-scanner.plist ``` * **Multiple wallets**: Loop over an array of addresses and check each one * **Slack instead of macOS notifications**: Replace the `osascript` line with a `curl` to a Slack webhook (see the Transaction Watchdog below for an example) * **Minimum value filter**: Add `| select(.value_usd > 50)` to the jq filter to ignore dust airdrops *** ## 2. Transaction Watchdog Monitors wallets every 10 minutes for new activity. Tracks state to avoid duplicate alerts and supports both macOS notifications and Slack webhooks. ```bash theme={null} #!/bin/bash # tx-watchdog.sh — Detect new transactions and send alerts ADDR="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" STATE_DIR="$HOME/.octav/state" STATE_FILE="$STATE_DIR/tx-watchdog-last-tx.txt" LOG_FILE="$HOME/.octav/logs/tx-watchdog.log" # Optional: set to your Slack webhook URL, or leave empty for macOS notifications only SLACK_WEBHOOK="" mkdir -p "$STATE_DIR" "$(dirname "$LOG_FILE")" # Get the last known transaction hash LAST_SEEN="" [ -f "$STATE_FILE" ] && LAST_SEEN=$(cat "$STATE_FILE") # Fetch recent transactions RESULT=$(octav transactions get --addresses "$ADDR" --limit 10 --raw 2>&1) if [ $? -ne 0 ]; then echo "[$(date)] ERROR: $RESULT" >> "$LOG_FILE" exit 1 fi # Get the latest transaction hash LATEST_TX=$(echo "$RESULT" | jq -r '.transactions[0].hash // empty') if [ -z "$LATEST_TX" ]; then echo "[$(date)] No transactions found" >> "$LOG_FILE" exit 0 fi # Compare with last seen if [ "$LATEST_TX" = "$LAST_SEEN" ]; then echo "[$(date)] No new transactions" >> "$LOG_FILE" exit 0 fi # New transactions found — collect all new ones NEW_TXS=$(echo "$RESULT" | jq -r --arg last "$LAST_SEEN" ' .transactions | if $last == "" then .[:5] else [.[] | select(.hash != $last)] end | .[] | "\(.type) \(.amount) \(.token_symbol) ($\(.value_usd)) on \(.chain) — \(.date)" ') NEW_COUNT=$(echo "$RESULT" | jq --arg last "$LAST_SEEN" ' .transactions | if $last == "" then .[:5] else [.[] | select(.hash != $last)] end | length ') # Update state echo "$LATEST_TX" > "$STATE_FILE" # Log echo "[$(date)] $NEW_COUNT new transaction(s) detected:" >> "$LOG_FILE" echo "$NEW_TXS" >> "$LOG_FILE" # Send macOS notification osascript -e "display notification \"$NEW_COUNT new transaction(s) on ${ADDR:0:6}...${ADDR: -4}\" with title \"Octav Transaction Alert\"" # Send Slack notification if webhook is configured if [ -n "$SLACK_WEBHOOK" ]; then SLACK_MSG=$(echo "$NEW_TXS" | sed 's/"/\\"/g' | paste -sd '\n' -) curl -s -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ -d "{\"text\": \"*Transaction Alert* — ${ADDR:0:6}...${ADDR: -4}\n\`\`\`$SLACK_MSG\`\`\`\"}" \ > /dev/null fi ``` **Sample alert output:** ```text theme={null} [2025-01-15 14:30:01] 2 new transaction(s) detected: swap 2.5 ETH ($5875.00) on ethereum — 2025-01-15 transfer 1000.0 USDC ($1000.00) on ethereum — 2025-01-15 ``` ```bash theme={null} # Run every 10 minutes */10 * * * * /path/to/tx-watchdog.sh ``` Save as `~/Library/LaunchAgents/fi.octav.tx-watchdog.plist`: ```xml theme={null} Label fi.octav.tx-watchdog ProgramArguments /bin/bash /Users/YOU/scripts/tx-watchdog.sh StartInterval 600 StandardOutPath /tmp/tx-watchdog.stdout StandardErrorPath /tmp/tx-watchdog.stderr ``` Load it: ```bash theme={null} launchctl load ~/Library/LaunchAgents/fi.octav.tx-watchdog.plist ``` * **Multiple wallets**: Create separate state files per address — `tx-watchdog-${ADDR:0:8}.txt` * **Value filter**: Add `| select(.value_usd > 1000)` to only alert on large transactions * **Slack setup**: Create a webhook at [api.slack.com/messaging/webhooks](https://api.slack.com/messaging/webhooks) and paste the URL into `SLACK_WEBHOOK` *** ## 3. Nightly Transaction Export Runs at midnight, exports the day's transactions to a monthly CSV file. Handles pagination for wallets with high daily activity and appends without duplicates. ```bash theme={null} #!/bin/bash # nightly-export.sh — Export today's transactions to a monthly CSV ADDR="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" EXPORT_DIR="$HOME/.octav/exports" TODAY=$(date +%Y-%m-%d) MONTH_FILE="$EXPORT_DIR/transactions-$(date +%Y-%m).csv" mkdir -p "$EXPORT_DIR" # Initialize CSV with header if it doesn't exist if [ ! -f "$MONTH_FILE" ]; then echo "date,type,chain,token,amount,value_usd,hash" > "$MONTH_FILE" fi # Fetch today's transactions with pagination OFFSET=0 LIMIT=250 TOTAL_EXPORTED=0 while true; do RESULT=$(octav transactions get \ --addresses "$ADDR" \ --start-date "$TODAY" \ --end-date "$TODAY" \ --limit "$LIMIT" \ --offset "$OFFSET" \ --raw 2>&1) if [ $? -ne 0 ]; then echo "[$(date)] ERROR: $RESULT" >&2 exit 1 fi # Extract transaction count COUNT=$(echo "$RESULT" | jq '.transactions | length') if [ "$COUNT" -eq 0 ]; then break fi # Append to CSV (skip duplicates by checking hash) EXISTING_HASHES="" [ -f "$MONTH_FILE" ] && EXISTING_HASHES=$(cut -d',' -f7 "$MONTH_FILE" | tail -n +2) echo "$RESULT" | jq -r --arg existing "$EXISTING_HASHES" ' .transactions[] | select(.hash as $h | ($existing | split("\n") | index($h)) | not) | [.date, .type, .chain, .token_symbol, .amount, .value_usd, .hash] | @csv ' >> "$MONTH_FILE" NEW_ROWS=$(echo "$RESULT" | jq --arg existing "$EXISTING_HASHES" ' [.transactions[] | select(.hash as $h | ($existing | split("\n") | index($h)) | not)] | length ') TOTAL_EXPORTED=$((TOTAL_EXPORTED + NEW_ROWS)) # If we got fewer than the limit, we've reached the end if [ "$COUNT" -lt "$LIMIT" ]; then break fi OFFSET=$((OFFSET + LIMIT)) done echo "[$(date)] Exported $TOTAL_EXPORTED transaction(s) for $TODAY to $MONTH_FILE" ``` **Sample CSV output (`transactions-2025-01.csv`):** ```csv theme={null} date,type,chain,token,amount,value_usd,hash "2025-01-01","swap","ethereum","ETH","2.5","5875.00","0x1a2b3c..." "2025-01-01","swap","ethereum","USDC","-5875.00","-5875.00","0x1a2b3c..." "2025-01-03","transfer","arbitrum","ARB","500.0","475.00","0x4d5e6f..." "2025-01-07","transfer","ethereum","ETH","1.0","2340.00","0x7g8h9i..." ``` **File structure:** ```text theme={null} ~/.octav/exports/ transactions-2025-01.csv transactions-2025-02.csv transactions-2025-03.csv ``` ```bash theme={null} # Run at midnight every day 0 0 * * * /path/to/nightly-export.sh ``` Save as `~/Library/LaunchAgents/fi.octav.nightly-export.plist`: ```xml theme={null} Label fi.octav.nightly-export ProgramArguments /bin/bash /Users/YOU/scripts/nightly-export.sh StartCalendarInterval Hour 0 Minute 0 StandardOutPath /tmp/nightly-export.stdout StandardErrorPath /tmp/nightly-export.stderr ``` * **Multiple wallets**: Loop over addresses and write to separate files or add an `address` column * **Weekly instead of monthly files**: Change the filename to `transactions-$(date +%Y-W%V).csv` * **Compression**: Add `gzip "$MONTH_FILE.bak"` at the end of each month to archive old files *** ## 4. Portfolio Tracker with Weekly Report Two scripts that work together: a daily NAV snapshot recorder and a weekly report generator that runs every Sunday. ### Daily snapshot ```bash theme={null} #!/bin/bash # portfolio-tracker.sh — Record daily NAV snapshot ADDR="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" DATA_DIR="$HOME/.octav/portfolio" NAV_FILE="$DATA_DIR/nav-history.csv" mkdir -p "$DATA_DIR" # Initialize CSV if it doesn't exist [ ! -f "$NAV_FILE" ] && echo "date,nav" > "$NAV_FILE" # Fetch current NAV RESULT=$(octav portfolio nav --addresses "$ADDR" --raw 2>&1) if [ $? -ne 0 ]; then echo "[$(date)] ERROR: $RESULT" >&2 exit 1 fi NAV=$(echo "$RESULT" | jq -r '.nav') TODAY=$(date +%Y-%m-%d) # Skip if already recorded today if grep -q "^$TODAY," "$NAV_FILE" 2>/dev/null; then echo "[$(date)] Already recorded NAV for $TODAY" exit 0 fi echo "$TODAY,$NAV" >> "$NAV_FILE" echo "[$(date)] Recorded NAV: \$$NAV" ``` ### Weekly report (Sundays) ```bash theme={null} #!/bin/bash # weekly-report.sh — Generate weekly portfolio summary DATA_DIR="$HOME/.octav/portfolio" NAV_FILE="$DATA_DIR/nav-history.csv" ADDR="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" # Optional: Slack webhook for posting the report SLACK_WEBHOOK="" # Get this week's data (last 7 days) WEEK_START=$(date -v-6d +%Y-%m-%d 2>/dev/null || date -d '6 days ago' +%Y-%m-%d) WEEK_END=$(date +%Y-%m-%d) # Extract this week's NAV values WEEK_DATA=$(awk -F',' -v start="$WEEK_START" -v end="$WEEK_END" ' NR > 1 && $1 >= start && $1 <= end { print $2 } ' "$NAV_FILE") if [ -z "$WEEK_DATA" ]; then echo "No data for this week" exit 0 fi # Calculate stats OPEN=$(echo "$WEEK_DATA" | head -1) CLOSE=$(echo "$WEEK_DATA" | tail -1) HIGH=$(echo "$WEEK_DATA" | sort -rn | head -1) LOW=$(echo "$WEEK_DATA" | sort -n | head -1) CHANGE=$(echo "scale=2; (($CLOSE - $OPEN) / $OPEN) * 100" | bc) SIGN=$([ "$(echo "$CHANGE >= 0" | bc)" -eq 1 ] && echo "+" || echo "") # Fetch current top holdings for the report TOP_HOLDINGS=$(octav portfolio get --addresses "$ADDR" --raw 2>/dev/null | jq -r ' [.tokens | sort_by(-.value) | .[:3][] | " \(.symbol): $\(.value | round) (\(.chain))"] | join("\n") ') # Build report REPORT="Portfolio Weekly Report $WEEK_START to $WEEK_END ================================ Opening NAV: \$$OPEN Closing NAV: \$$CLOSE Weekly High: \$$HIGH Weekly Low: \$$LOW Change: ${SIGN}${CHANGE}% ================================ Top Holdings: $TOP_HOLDINGS ================================" echo "$REPORT" # Save report REPORT_FILE="$DATA_DIR/report-$(date +%Y-W%V).txt" echo "$REPORT" > "$REPORT_FILE" # Post to Slack if configured if [ -n "$SLACK_WEBHOOK" ]; then SLACK_TEXT=$(echo "$REPORT" | sed 's/"/\\"/g') curl -s -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ -d "{\"text\": \"\`\`\`$SLACK_TEXT\`\`\`\"}" \ > /dev/null echo "Posted to Slack." fi ``` **Sample weekly report:** ```text theme={null} Portfolio Weekly Report 2025-01-13 to 2025-01-19 ================================ Opening NAV: $181043.22 Closing NAV: $184230.41 Weekly High: $186102.88 Weekly Low: $178934.10 Change: +1.76% ================================ Top Holdings: ETH: $82903 (ethereum) USDC: $38088 (ethereum) WBTC: $27634 (ethereum) ================================ ``` **Scheduler setup:** ```bash theme={null} # Daily NAV snapshot at 9am 0 9 * * * /path/to/portfolio-tracker.sh # Weekly report every Sunday at 10am 0 10 * * 0 /path/to/weekly-report.sh ``` * **Multiple wallets**: Track each wallet in a separate CSV and combine totals in the weekly report * **Monthly reports**: Add a similar script triggered on the 1st of each month with `0 10 1 * *` * **Historical chart**: Feed `nav-history.csv` into a plotting tool like `gnuplot` or a Google Sheet for visual trends *** ## 5. Credit Usage Guardian A wrapper script that checks remaining credits before running any CLI command. Tracks daily consumption and alerts when credits drop below a configurable threshold. ```bash theme={null} #!/bin/bash # octav-safe — Credit-aware CLI wrapper # Usage: octav-safe portfolio get --addresses 0x... # Drop-in replacement for `octav` that prevents accidental credit burn CREDIT_THRESHOLD=100 # Warn below this many credits LOG_DIR="$HOME/.octav/logs" USAGE_LOG="$LOG_DIR/credit-usage.log" mkdir -p "$LOG_DIR" # Check current credits CREDITS_RESULT=$(octav credits --raw 2>&1) if [ $? -ne 0 ]; then echo "ERROR: Could not check credits — $CREDITS_RESULT" >&2 exit 1 fi CREDITS=$(echo "$CREDITS_RESULT" | jq -r '.credits') # Log usage echo "[$(date)] Credits remaining: $CREDITS | Command: octav $*" >> "$USAGE_LOG" # Block if below threshold if [ "$CREDITS" -lt "$CREDIT_THRESHOLD" ]; then echo "CREDIT GUARD: Only $CREDITS credits remaining (threshold: $CREDIT_THRESHOLD)" echo "" echo " Command blocked: octav $*" echo "" echo " To override, run the command directly with \`octav\` instead of \`octav-safe\`." echo " To adjust the threshold, edit CREDIT_THRESHOLD in $(realpath "$0")" exit 1 fi # Show credit count and proceed echo "[octav-safe] Credits: $CREDITS — running: octav $*" octav "$@" ``` **Installation:** ```bash theme={null} # Make it executable and add to PATH chmod +x /path/to/octav-safe ln -s /path/to/octav-safe /usr/local/bin/octav-safe # Now use it as a drop-in replacement octav-safe portfolio nav --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Sample output — normal operation:** ```text theme={null} [octav-safe] Credits: 4872 — running: octav portfolio nav --addresses 0x742d... { "nav": 184230.41, "currency": "USD", "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" } ``` **Sample output — credit guard triggered:** ```text theme={null} CREDIT GUARD: Only 47 credits remaining (threshold: 100) Command blocked: octav portfolio nav --addresses 0x742d... To override, run the command directly with `octav` instead of `octav-safe`. To adjust the threshold, edit CREDIT_THRESHOLD in /Users/you/scripts/octav-safe ``` **Sample daily usage log (`credit-usage.log`):** ```text theme={null} [Mon Jan 15 09:00:01 2025] Credits remaining: 4872 | Command: octav portfolio nav --addresses 0x742d... [Mon Jan 15 09:00:05 2025] Credits remaining: 4871 | Command: octav transactions get --addresses 0x742d... [Mon Jan 15 14:00:01 2025] Credits remaining: 4870 | Command: octav airdrop --address 7xKXtg... [Mon Jan 15 14:10:01 2025] Credits remaining: 4869 | Command: octav transactions get --addresses 0x742d... ``` * **Shell alias**: Add `alias octav="octav-safe"` to your `~/.bashrc` or `~/.zshrc` to protect all commands by default * **Daily summary**: Add a cron job that parses the usage log and reports daily consumption — `grep "$(date +%Y-%m-%d)" "$USAGE_LOG" | wc -l` gives you the day's command count * **Tiered warnings**: Add a second threshold (e.g., 500) that prints a warning but still allows the command to run *** ## Setting Up launchd on macOS macOS uses `launchd` instead of cron for scheduled tasks. While cron works on macOS, `launchd` is the native scheduler and handles sleep/wake correctly — your task runs after waking up even if the Mac was asleep at the scheduled time. ### Step 1: Create the plist file Each scheduled task needs a `.plist` file in `~/Library/LaunchAgents/`. Use reverse-DNS naming: ```xml theme={null} Label fi.octav.your-script-name ProgramArguments /bin/bash /Users/YOU/scripts/your-script.sh StartCalendarInterval Hour 14 Minute 0 StandardOutPath /tmp/your-script.stdout StandardErrorPath /tmp/your-script.stderr ``` ### Step 2: Load and manage ```bash theme={null} # Load (start scheduling) launchctl load ~/Library/LaunchAgents/fi.octav.your-script-name.plist # Unload (stop scheduling) launchctl unload ~/Library/LaunchAgents/fi.octav.your-script-name.plist # Check if it's running launchctl list | grep fi.octav # Run immediately (for testing) launchctl start fi.octav.your-script-name ``` ### Step 3: Debug If your script isn't running, check the logs: ```bash theme={null} # Check launchd's own logs log show --predicate 'subsystem == "com.apple.xpc.launchd"' --last 1h | grep fi.octav # Check your script's output cat /tmp/your-script.stdout cat /tmp/your-script.stderr ``` **Common pitfall:** launchd doesn't source your shell profile. If `octav` isn't found, use the full path (`/usr/local/bin/octav` or wherever it's installed). Find it with `which octav`. *** ## Next Steps Installation, authentication, and full command reference One-liners, scripts, and jq patterns Connect the Octav API directly to AI assistants # Examples Source: https://docs.octav.fi/cli/examples Copy-pasteable CLI commands and scripts for portfolio monitoring, data export, and AI agent workflows Practical examples that build from one-liners to full automation scripts. Every command is copy-pasteable and every JSON output is realistic. Want scheduled automations? See the [Automations](/cli/automations) page for production-ready cron jobs — airdrop scanners, transaction monitors, nightly exports, and more. All examples assume you've already authenticated with `octav auth set-key YOUR_API_KEY`. See [Authentication](/cli/overview#authentication) for setup. *** ## 1. Quick Portfolio Check Start with simple one-liners to explore a wallet. ```bash theme={null} # Get net asset value octav portfolio nav --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` ```json theme={null} { "nav": 184230.41, "currency": "USD", "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" } ``` ```bash theme={null} # Get top tokens by value using jq octav portfolio get --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 --raw \ | jq '[.tokens | sort_by(-.value) | .[:5][] | {symbol, value: (.value | round), chain}]' ``` ```json theme={null} [ { "symbol": "ETH", "value": 82903, "chain": "ethereum" }, { "symbol": "USDC", "value": 38088, "chain": "ethereum" }, { "symbol": "WBTC", "value": 27634, "chain": "ethereum" }, { "symbol": "ARB", "value": 14738, "chain": "arbitrum" }, { "symbol": "AAVE", "value": 11053, "chain": "ethereum" } ] ``` ```bash theme={null} # Check remaining API credits octav credits ``` ```json theme={null} { "credits": 4872 } ``` *** ## 2. Daily Portfolio Monitor A cron-ready script that logs NAV to a CSV and alerts on drops. ```bash theme={null} #!/bin/bash # daily-monitor.sh — Track portfolio value and alert on drops ADDR="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" LOG_FILE="$HOME/portfolio-nav.csv" THRESHOLD=150000 # Alert if NAV drops below this # Initialize CSV if it doesn't exist [ ! -f "$LOG_FILE" ] && echo "date,nav" > "$LOG_FILE" # Fetch current NAV NAV=$(octav portfolio nav --addresses "$ADDR" --raw | jq -r '.nav') DATE=$(date +%Y-%m-%d) # Append to log echo "$DATE,$NAV" >> "$LOG_FILE" echo "[$DATE] Portfolio NAV: \$$NAV" # Alert on significant drop if (( $(echo "$NAV < $THRESHOLD" | bc -l) )); then echo "ALERT: Portfolio below \$$THRESHOLD — current value: \$$NAV" # Add your notification here (email, Slack webhook, etc.) fi ``` Set it up with cron: ```bash theme={null} # Run daily at 8am crontab -e # Add: 0 8 * * * /path/to/daily-monitor.sh ``` *** ## 3. Multi-Wallet Aggregation Aggregate NAV across multiple wallets into a summary. ```bash theme={null} #!/bin/bash # aggregate-nav.sh — Sum NAV across all treasury wallets WALLETS="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68,0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B,7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU" echo "Treasury NAV Report — $(date +%Y-%m-%d)" echo "================================" # Fetch NAV for all wallets in one call RESULT=$(octav portfolio nav --addresses "$WALLETS" --raw) # Parse and display per-wallet breakdown echo "$RESULT" | jq -r ' .wallets[] | " \(.address[0:6])...\(.address[-4:]): $\(.nav | round)" ' # Calculate total TOTAL=$(echo "$RESULT" | jq '[.wallets[].nav] | add | round') echo "================================" echo " Total: \$$TOTAL" ``` ```text theme={null} Treasury NAV Report — 2025-01-15 ================================ 0x742d...2bD68: $184230 0xAb58...eC9B: $2847102 7xKXtg...gAsU: $163230 ================================ Total: $3194562 ``` *** ## 4. Transaction Export to CSV Fetch a year of transactions and convert to a spreadsheet-ready CSV. ```bash theme={null} # Fetch all 2024 transactions octav transactions get \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --start-date 2024-01-01 \ --end-date 2024-12-31 \ --limit 250 \ --raw \ | jq -r ' ["date","type","chain","token","amount","value_usd"], (.transactions[] | [.date, .type, .chain, .token_symbol, .amount, .value_usd] ) | @csv ' > transactions-2024.csv # Preview the first few rows head -5 transactions-2024.csv ``` ```csv theme={null} "date","type","chain","token","amount","value_usd" "2024-01-03","swap","ethereum","ETH","2.5","5875.00" "2024-01-03","swap","ethereum","USDC","-5875.00","-5875.00" "2024-01-07","transfer","ethereum","ETH","1.0","2340.00" ``` *** ## 5. Whale Alert Script Monitor a wallet for large transactions and log alerts. ```bash theme={null} #!/bin/bash # whale-alert.sh — Detect large transactions ADDR="0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" ALERT_THRESHOLD=50000 # USD value CHECK_INTERVAL=300 # seconds (5 minutes) LAST_CHECK_FILE="/tmp/whale-alert-last-check" # Initialize last check time [ ! -f "$LAST_CHECK_FILE" ] && date -u +%Y-%m-%d > "$LAST_CHECK_FILE" SINCE=$(cat "$LAST_CHECK_FILE") while true; do echo "[$(date)] Checking for whale transactions since $SINCE..." # Fetch recent transactions TXNS=$(octav transactions get \ --addresses "$ADDR" \ --start-date "$SINCE" \ --limit 50 \ --raw) # Filter for large transactions echo "$TXNS" | jq -r --argjson threshold "$ALERT_THRESHOLD" ' .transactions[] | select(.value_usd > $threshold) | "WHALE ALERT: \(.type) \(.amount) \(.token_symbol) ($\(.value_usd)) on \(.chain) — \(.date)" ' # Update last check date -u +%Y-%m-%d > "$LAST_CHECK_FILE" sleep "$CHECK_INTERVAL" done ``` ```text theme={null} [2025-01-15 14:30:01] Checking for whale transactions since 2025-01-15... WHALE ALERT: swap 80.0 ETH ($276000) on ethereum — 2025-01-15 WHALE ALERT: transfer 500000.0 USDC ($500000) on ethereum — 2025-01-15 ``` *** ## 6. AI Agent Tool-Use See how an AI agent like Claude Code uses the CLI as a tool. ```text theme={null} Check the portfolio value for 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68, then analyze the DeFi positions and suggest rebalancing opportunities. ``` ```bash theme={null} octav portfolio get --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 --raw ``` ```json theme={null} { "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68", "nav": 184230.41, "tokens": [ { "symbol": "ETH", "balance": 22.4, "value": 82903, "chain": "ethereum" }, { "symbol": "USDC", "balance": 38088, "value": 38088, "chain": "ethereum" }, { "symbol": "WBTC", "balance": 0.42, "value": 27634, "chain": "ethereum" } ], "protocols": [ { "name": "Aave V3", "chain": "ethereum", "supplied": 55400, "borrowed": 8200, "health_factor": 2.14 }, { "name": "Uniswap V3", "chain": "arbitrum", "liquidity": 12303, "in_range": true } ] } ``` ```text theme={null} Portfolio Analysis — $184,230 Your DeFi positions look healthy: • Aave V3: $55.4K supplied, $8.2K borrowed (health factor 2.14 — safe) • Uniswap V3: $12.3K LP, currently in range Rebalancing Suggestions: 1. Your WBTC ($27.6K) is sitting idle — deposit into Aave to earn 0.5-2% APY and improve capital efficiency 2. Consider narrowing your Uniswap V3 range for higher fee capture (current range may be too wide) 3. 45% ETH concentration is aggressive — if you're bearish short-term, rotating 10% to stables would bring you to a more neutral position ``` *** ## 7. Airdrop Checker Quick check for Solana airdrop eligibility with formatted output. ```bash theme={null} # Check airdrop eligibility octav airdrop --address 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU --raw \ | jq -r ' "Airdrop Eligibility — \(.address[0:6])...\(.address[-4:])\n", (.airdrops[] | if .eligible then " ✓ \(.program): \(.amount) \(.token) (~$\(.value_usd))" elif .claimed then " ✔ \(.program): CLAIMED" else " ✗ \(.program): Not eligible" end ), "", "Unclaimed value: $\([.airdrops[] | select(.eligible) | .value_usd] | add)" ' ``` ```text theme={null} Airdrop Eligibility — 7xKXtg...gAsU ✓ Jupiter (JUP): 847 JUP (~$512) ✔ Tensor (TNSR): CLAIMED ✓ Parcl (PRCL): 340 PRCL (~$89) ✗ Drift (DRIFT): Not eligible Unclaimed value: $601 ``` *** ## Tips & Patterns The `--raw` flag outputs compact JSON and includes the full API response — ideal for piping to `jq` or feeding to AI agents. ```bash theme={null} # Pretty-printed (human-friendly) octav credits # Compact JSON (script-friendly) octav credits --raw ``` Use `watch` for a live-updating dashboard in your terminal. ```bash theme={null} # Update NAV every 60 seconds watch -n 60 'octav portfolio nav \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --raw | jq .' ``` Always check the exit code and handle errors gracefully. ```bash theme={null} RESULT=$(octav portfolio nav --addresses "$ADDR" --raw 2>&1) if [ $? -ne 0 ]; then echo "Error: $RESULT" >&2 exit 1 fi NAV=$(echo "$RESULT" | jq -r '.nav') ``` Most commands cost 1 credit per address. Plan your scripts accordingly. ```bash theme={null} # Check credits before a big batch job CREDITS=$(octav credits --raw | jq -r '.credits') NEEDED=30 # 10 addresses × 3 commands if [ "$CREDITS" -lt "$NEEDED" ]; then echo "Need $NEEDED credits but only have $CREDITS" exit 1 fi ``` *** ## Next Steps One-command install for macOS and Linux Full command reference with flags Connect to AI assistants directly via MCP # CLI Source: https://docs.octav.fi/cli/overview Query portfolio data, transactions, net worth, and historical snapshots across 20+ blockchains from your terminal or AI agent workflows The official Octav CLI lets you interact with the Octav API directly from your terminal. Built in Rust for speed, it outputs structured JSON — making it ideal for shell scripts, CI pipelines, and AI agent tool-use. **Quick Install** — One command to get started: ```bash theme={null} curl -sSf https://raw.githubusercontent.com/Octav-Labs/octav-cli/main/install.sh | sh ``` *** ## Why Use the CLI? Structured JSON output that AI agents can parse and act on — no SDK or HTTP client needed Pipe portfolio data into jq, scripts, cron jobs, or monitoring systems Written in Rust — single binary, no runtime dependencies, instant startup Built-in support for the x402 payment protocol — AI agents can pay per request without API keys *** ## Installation Works on macOS and Linux: ```bash theme={null} curl -sSf https://raw.githubusercontent.com/Octav-Labs/octav-cli/main/install.sh | sh ``` If you have Rust installed: ```bash theme={null} cargo install octav ``` Clone and build: ```bash theme={null} git clone https://github.com/Octav-Labs/octav-cli.git cd octav-cli cargo build --release cp target/release/octav /usr/local/bin/ ``` *** ## Authentication Store your API key once and it's used automatically for all commands. ```bash theme={null} # Store your API key (get one at https://data.octav.fi) octav auth set-key YOUR_API_KEY # Verify it's set octav auth show ``` The API key is resolved in priority order: 1. `--api-key` flag (highest precedence) 2. `OCTAV_API_KEY` environment variable 3. `~/.octav/config.json` config file For AI agent workflows, set the `OCTAV_API_KEY` environment variable so the agent doesn't need to manage config files. *** ## Commands ### Portfolio **Full portfolio including DeFi positions across all chains.** ```bash theme={null} octav portfolio get --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit per address **Wallet token balances only (excludes DeFi protocol positions).** ```bash theme={null} octav portfolio wallet --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit per address **Net asset value in a specified currency.** ```bash theme={null} octav portfolio nav --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 --currency EUR ``` Supported fiat currencies: `USD` (default), `EUR`, `CAD`, `AED`, `CHF`, `SGD`. Crypto: `ETH`, `SOL`, `cbBTC`, `EURC`, `BNB`. **Cost:** 1 credit per address **Aggregated token distribution across all chains for a specific date.** ```bash theme={null} octav portfolio token-overview \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --date 2024-06-01 ``` **Cost:** 1 credit per address ### Transactions **Query transaction history with filtering and pagination.** ```bash theme={null} octav transactions get \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --chain ethereum \ --type swap \ --start-date 2024-01-01 \ --end-date 2024-06-30 \ --limit 100 ``` | Flag | Description | Default | | -------------- | -------------------------- | ------- | | `--chain` | Filter by chain | all | | `--type` | Filter by transaction type | all | | `--start-date` | Start date (YYYY-MM-DD) | — | | `--end-date` | End date (YYYY-MM-DD) | — | | `--offset` | Pagination offset | `0` | | `--limit` | Results per page (max 250) | `50` | **Cost:** 1 credit per address **Trigger manual transaction synchronization.** ```bash theme={null} octav transactions sync --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit per address ### Historical **Portfolio snapshot for a specific date.** ```bash theme={null} octav historical get \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --date 2024-01-01 ``` **Cost:** 1 credit per address **Subscribe to automatic daily portfolio snapshots.** ```bash theme={null} octav historical subscribe-snapshot \ --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 \ --description "Main wallet daily snapshot" ``` **Cost:** 1 credit per address ### Metadata & Specialized **Check sync status for addresses.** ```bash theme={null} octav status --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** Free **Check API credit balance.** ```bash theme={null} octav credits ``` **Cost:** Free **Check Solana airdrop eligibility.** ```bash theme={null} octav airdrop --address 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU ``` **Cost:** 1 credit **Get Polymarket prediction market positions.** ```bash theme={null} octav polymarket --address 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Cost:** 1 credit ### Agent Commands (x402) For AI agents that pay per request using the [x402 payment protocol](https://www.x402.org/) — no API key required. Use these only when the user explicitly asks for x402, or when the agent has a funded wallet and no API key — otherwise use the API-key commands above. Only `wallet`, `portfolio`, `nav`, `status`, and `chains` are available over x402; everything else, including **transaction history**, needs an API key. See [Agent Endpoints (x402)](/api/endpoints/agent-x402). **Wallet holdings via x402 payment.** ```bash theme={null} octav agent wallet --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` **Full portfolio via x402 payment.** ```bash theme={null} octav agent portfolio --addresses 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` *** ## Using with AI Agents The CLI outputs structured JSON, making it a natural fit for AI agent tool-use. Agents can invoke `octav` commands via shell execution and parse the JSON response directly. ### Example: Claude Code / Cursor Agent ```text theme={null} "Check the portfolio value for 0xABC... using the octav CLI, then analyze the DeFi positions and suggest rebalancing opportunities." ``` The agent runs: ```bash theme={null} octav portfolio get --addresses 0xABC... --raw ``` And receives structured JSON it can reason over. ### Example: Autonomous Monitoring Script ```bash theme={null} #!/bin/bash # Daily portfolio snapshot with alerts ADDR="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68" NAV=$(octav portfolio nav --addresses $ADDR --raw | jq -r '.nav') echo "Portfolio value: $NAV" # Agent can parse this output and decide on actions ``` ### Output Format All commands return JSON. Pretty-printed by default, compact with `--raw`: ```bash theme={null} # Pretty-printed (default) octav credits # => { # => "credits": 42 # => } # Compact JSON (ideal for piping / agent parsing) octav credits --raw # => {"credits":42} ``` The `--raw` flag also disables field stripping, returning the full API response. *** ## Multiple Addresses Most commands accept multiple addresses as a comma-separated list: ```bash theme={null} octav portfolio get \ --addresses 0xABC...123,0xDEF...456,7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU ``` Maximum 10 addresses per request. *** ## Error Handling Errors are returned as JSON on stdout with a non-zero exit code: ```json theme={null} { "error": { "type": "auth", "message": "Invalid API key", "status": 401 } } ``` | Error | Cause | Solution | | ---------------------- | ------------------------------------------ | -------------------------------------------------------- | | Invalid address format | Address doesn't match EVM or Solana format | Use `0x...` (40 hex chars) for EVM or base58 for Solana | | Authentication failure | Missing or invalid API key | Run `octav auth set-key` or set `OCTAV_API_KEY` | | Insufficient credits | Not enough credits for the request | Purchase more at [data.octav.fi](https://data.octav.fi/) | *** ## Resources Source code and issues Full REST API reference Credit packages and costs # Account Source: https://docs.octav.fi/docs/account Manage your Octav account settings and preferences Manage your Octav account settings, preferences, and billing all in one place. **Global & Address-Specific Settings** — Configure global account settings that apply everywhere, or customize settings per address for widgets, protocols, and chains. *** ## Global Settings Settings that apply across your entire Octav account. ### General Manage your basic account details: **Display Name** * Your account display name **Email Address** * Primary email: Used for login with magic links * Cannot be changed after account creation **Telegram (Optional)** * Link your Telegram account for notifications * Format: @username * Used for alerts and updates **API Key** * Your personal API key for programmatic access * Click to reveal or regenerate * Keep secure - never share publicly [View API Documentation →](/api/introduction) ### Credit Balance Track your remaining credits for API calls and premium features. **Current Balance:** * View remaining credits * Credits used for API requests and premium widgets * Buy more credits as needed **What Uses Credits:** * API Access - Real-time blockchain data and DeFi protocol information * Premium Widgets - Enhanced widgets with real-time updates * Advanced Features - Analytics, exports, and portfolio tools Learn more about API usage and credit consumption *** ### Price Sources (Coming Soon) **Custom Price Configuration** — Configure your preferred price sources and their priority order. **Available Price Sources:** **Priority 1** Octav internal pricing data - Active by default **Priority 2** DeBank API pricing data - Requires API key **Priority 3** DeFiLlama pricing data - Requires API key **Priority 4** CoinGecko API pricing data - Requires API key **Features:** * Drag to reorder priority * Enable/disable specific sources * Enter your own API keys for non-Octav sources * Fallback to next source if primary unavailable * Save custom configuration **API Keys Required** — To use DeBank, DeFiLlama, or CoinGecko as price sources, you'll need to provide your own API keys from those services. *** ### Token Prices Set fixed prices for specific tokens across the application. Click "Add Fixed Price" to create a new override Choose the blockchain network Paste the token contract address Enter your fixed price in USD **Use Cases:** * Override incorrect market prices * Set custom valuations for unlisted tokens * Fix prices for internal accounting * Lock prices for reporting periods *** ### Invoices View your billing history and download invoice details. **Invoice History Table:** * **Date** - Invoice issue date * **Status** - Payment status (paid, pending, failed) * **Credits** - Number of credits purchased * **Amount** - Total amount paid * **Invoice** - Download PDF invoice **Invoice Actions:** * View invoice details * Download PDF * Resend invoice email * Request refund (if applicable) *** ### Credits & Usage **Current Balance** View remaining credits and purchase history [Buy More Credits →](https://pro.octav.fi/account/credits) **Developer Access** Use credits to access real-time blockchain data and DeFi protocol information [API Documentation →](https://docs.octav.fi/api/introduction) **Premium Widgets** Enhanced widgets with real-time updates and advanced features **Advanced Tools** Advanced analytics, CSV/PDF exports, and portfolio management tools *** ## By Address Settings Settings that are specific to individual wallet addresses you're tracking. ### Widgets Manage dashboard widgets for specific addresses. **Address-Specific Widgets** — Each tracked address can have its own custom widget configuration. **Widget Management:** 1. **Select an address** from the dropdown 2. View widgets configured for that address 3. See widget status and expiration dates 4. Add, edit, or remove widgets per address **Widget Table:** * **Widget** - Widget name and type * **Address** - Associated wallet address * **Status** - Active, expired, or disabled * **Expiration Date** - When widget access expires (for premium widgets) [Learn more about widgets →](/docs/widget-explorer) *** ### Protocols and Chains Manage the protocols and chains activated in your dashboard for each address. **Per-Address Configuration:** * Enable/disable specific chains for each wallet * Activate or deactivate protocol tracking * Optimize data fetching by tracking only what you need * Reduce API usage by limiting tracked protocols **Benefits:** * Faster dashboard loading * Lower credit consumption * Cleaner, more focused data * Better performance for large portfolios *** ## Appearance ### Theme Customize how Octav looks on your device. **Bright Interface** Clean, bright interface ideal for daytime use **Easy on the Eyes** Comfortable viewing for extended sessions **System Default** Follows your system/browser preferences *** ## Need Help? Email our support team: [info@octav.fi](mailto:info@octav.fi) Get help from the community Browse our guides and tutorials # Address Book Source: https://docs.octav.fi/docs/address-book Organize and manage your wallet addresses Keep track of your wallet addresses with custom labels. The Octav Address Book helps you organize all your wallets in one place with easy-to-remember names. Add friendly names to any wallet address Automatic resolution of ENS, SNS, and other naming services Track addresses across 90+ blockchains *** ## Overview The Address Book helps you: * Label your wallet addresses with memorable names * Organize wallets you track across different chains * Auto-resolve ENS, SNS, and other blockchain name services * Quickly search and find addresses by label or address **Portfolio Tracking** — All addresses in your Address Book are tracked automatically across 90+ supported blockchains. *** ## Adding Addresses Click **Address Book** in the sidebar navigation Click the **Add Address** button in the top right Paste your wallet address, ENS name, or other naming service identifier Give it a memorable name like "Main Wallet", "Hardware Wallet", or "Trading Wallet" Click **Save** to add the address to your book *** ## Supported Address Formats ### Ethereum & EVM Chains **Standard Addresses:** * Full address: `0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb` * Automatically tracked across all EVM chains: * Ethereum, Base, Arbitrum, Optimism, Polygon * BSC, Avalanche, Fantom * And 80+ more EVM-compatible chains **ENS Names:** * `.eth` domains: `vitalik.eth` * Automatically resolves to address * Shows ENS name in dashboard ### Solana Addresses **Base58 Format:** * Full address: `DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK` * Automatically tracked on Solana blockchain **SNS Names:** * `.sol` domains: `example.sol` * Automatically resolves to Solana address * Shows SNS name in dashboard ### Hyperliquid Addresses **Format:** * Hyperliquid wallet addresses * Tracked automatically for perpetuals positions * Portfolio and trading data ### Additional Name Services Octav automatically resolves various blockchain naming services: **`.eth` domains** * Most popular Ethereum naming service * Reverse resolution supported * Multi-coin address support **`.sol` domains** * Solana naming service * Auto-resolution to Solana addresses **Additional naming services** * More naming services automatically detected * Auto-resolution where supported *** ## Address List Your Address Book displays all your tracked wallets with key information. **What You See:** **Custom Name** The friendly name you assigned (e.g., "Main Wallet") **Wallet Address** Shortened address format (e.g., `0x89d1...ad473a`) Full address visible on hover **ENS/SNS/etc.** Shows resolved name if available (e.g., `vitalik.eth`) **Quick Copy** Click to copy full address to clipboard **Available Actions:** Click address to view full portfolio for that wallet Change the custom label name Copy full address to clipboard Delete address from your Address Book *** ## Search & Find Addresses **Quick Search** — Use the search bar to find addresses quickly by label or address. ### Search Options **Find by Custom Name** Type any part of the label name: * Search is case-insensitive * Partial matching supported * Results appear instantly **Examples:** * Search "main" finds "Main Wallet" * Search "hardware" finds "Hardware Wallet 1" and "Hardware Wallet 2" * Search "trading" finds "Trading Wallet" **Find by Wallet Address** Paste or type any part of the address: * Partial address matching * Works with full or shortened addresses * Case-insensitive **Examples:** * Search "0x89d1" finds addresses starting with those characters * Search "ad473a" finds addresses ending with those characters * Paste full address for exact match *** ## Editing Addresses Locate the address you want to edit in your Address Book Click the edit button next to the address Change the custom label to a new name Click **Save** to update the address label **Address Cannot Be Changed** — You can only edit the label name. To track a different address, add it as a new entry. *** ## Removing Addresses Locate the address you want to remove Click the delete/remove button Confirm you want to remove the address The address is removed from your Address Book **Historical Data Preserved** — Removing an address from your Address Book doesn't delete historical transaction data. You can add it back anytime to resume tracking. *** ## Automatic Name Resolution ### Ethereum Name Service **How ENS Works:** When you add an address with an ENS name: 1. Enter the ENS name (e.g., `vitalik.eth`) 2. Octav automatically resolves to the address 3. Both ENS name and address are displayed 4. You can still add a custom label **Display:** * **Label:** Your custom name (e.g., "Vitalik Wallet") * **ENS:** `vitalik.eth` * **Address:** `0xd8dA...6045` **Best Practice** — Use custom labels even with ENS names for additional context (e.g., "Partnership - vitalik.eth") ### Solana Name Service **How SNS Works:** When you add a Solana address with SNS: 1. Enter the SNS name (e.g., `example.sol`) 2. Octav resolves to the Solana address 3. Both SNS name and address are shown 4. Add custom label for organization **Display:** * **Label:** Your custom name (e.g., "Trading SOL") * **SNS:** `example.sol` * **Address:** `DYw8jC...CNSKK` ### Automatic Name Detection **Adding by Address:** When you add an address that has an ENS/SNS name: 1. Paste the wallet address 2. Octav automatically checks for naming service 3. If found, the name is displayed alongside address 4. You add your custom label **Example:** * You paste: `0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045` * Octav detects: `vitalik.eth` * You see both address and ENS name * You add label: "Example Wallet" *** ## Multi-Chain Tracking **Automatic Cross-Chain Tracking** — When you add an address, Octav automatically tracks it across all 90+ supported blockchains. ### How It Works **One Address, Multiple Chains** When you add an EVM address (0x...): * Automatically tracked on **all EVM chains** * Ethereum, Base, Arbitrum, Optimism, Polygon, BSC, Avalanche * And 80+ more EVM-compatible chains * All balances and transactions across chains appear in your dashboard **Example:** * Add address: `0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb` * See balances on Ethereum, Base, Arbitrum simultaneously * Track DeFi positions across all chains * View transactions from any chain **Native Chain Addresses** Some chains have their own address formats: **Base58 Format** Solana addresses are tracked on Solana blockchain only **Native Addresses** Hyperliquid addresses tracked for perpetuals and portfolio [View all supported blockchains →](/docs/supported-blockchains) *** ## Address Bundles **Omnichain Bundles** — Group multiple wallet addresses together to see combined portfolio value across all chains. Bundle works with EVM, Solana, and Hyperliquid addresses simultaneously. ### What are Bundles? Bundles allow you to group related wallet addresses together to view their cumulative net worth and combined portfolio analytics. **Mix Any Chains** Bundles are chain-agnostic - combine addresses from any blockchain: Bundle Ethereum addresses with Solana wallets for total portfolio view Group Base, Arbitrum, and Optimism wallets together Combine DeFi wallets across different chains See cumulative net worth across all bundled addresses **Example Bundle:** * ETH Main Wallet: `0x742d...bEb` * Solana Trading: `DYw8jC...CNSKK` * Base DeFi: `0x89d1...473a` * Hyperliquid Perps: (Hyperliquid address) → **Total Net Worth:** Combined value across all 4 addresses **Why Use Bundles?** **Organize by Strategy** * "Long-Term Holdings" - Cold storage wallets across chains * "Active Trading" - Trading wallets on Base, Arbitrum, Solana * "DeFi Strategies" - Yield farming positions across protocols * "NFT Portfolio" - NFT collection wallets **Track by Ownership** * Personal wallets (all your addresses) * Business wallets (company-related addresses) * Family members (spouse/children portfolios) * Partnership addresses (shared project wallets) **Multi-Chain Operations** * Protocol treasuries across multiple chains * DAO assets on different networks * Project allocations spanning L2s * Investment funds with multi-chain exposure **Consolidated Views** * View total net worth across all chains instantly * Track combined portfolio performance * Monitor aggregate positions * Simplified reporting ### How to Create a Bundle Go to Address Book and find the Bundles section Click **Create Bundle** button Give it a descriptive name (e.g., "DeFi Portfolio", "Trading Accounts") Select which addresses from your Address Book to include Save the bundle to view cumulative analytics **Bundle Configuration:** * Add unlimited addresses to a bundle * Mix EVM, Solana, and Hyperliquid addresses * Edit bundle membership anytime * Rename or delete bundles as needed ### What You See **Bundle Dashboard Shows:** **Total Value** Combined portfolio value across all bundled addresses **Allocation Charts** How assets are distributed across chains and protocols **Combined Performance** Historical value tracking for entire bundle **Individual Wallets** See contribution of each address to total **Analytics Features:** * Total net worth across all chains * Chain distribution (% on Ethereum vs Solana vs Base, etc.) * Protocol distribution (DeFi positions aggregated) * Token holdings (combined balances across addresses) * Performance over time (1D, 1W, 1M, etc.) *** ## Best Practices **Use Descriptive Labels:** * Be specific with labels (e.g., "Hardware Wallet - Ledger" vs just "Wallet") * Include context (e.g., "Trading - DeFi", "Cold Storage - Long Term") * Use consistent naming patterns * Consider adding dates for time-based wallets **Good Examples:** * "Main Wallet - Daily Use" * "Hardware Wallet - Ledger Nano X" * "Trading Wallet - DeFi Strategies" * "Cold Storage - 2024 Holdings" **Poor Examples:** * "Wallet" * "My Address" * "Address 1" **Logical Organization:** * Group similar wallets with naming prefixes * Use labels to indicate purpose * Create bundles for related addresses * Keep frequently used wallets at the top **Organization Examples:** * **By Purpose:** "Trading - Base", "Trading - Arbitrum", "Holding - Long Term" * **By Device:** "Ledger - Main", "Ledger - Backup", "MetaMask - Mobile" * **By Chain:** "ETH Main", "SOL Trading", "ARB DeFi" **Bundle Organization:** * Create bundles for portfolio segments (e.g., "DeFi Holdings", "Long-Term Storage") * Use bundles to track cross-chain strategies * Bundle addresses by ownership or purpose **Keep Your Address Book Clean:** * Review addresses periodically * Remove wallets you no longer use * Update labels as wallet purposes change * Verify ENS/SNS names are still resolving correctly *** ## Need Help? Manage your account and preferences Learn about dashboard features Get help from our community # Alerts Source: https://docs.octav.fi/docs/alerts Get notified in real time when on-chain activity matches your rules — via webhook, email, or Telegram. Alerts watch the chain for you. Define a rule for the on-chain activity you care about — a token transfer, a successful or failed transaction, a specific function call or contract event — and Octav delivers a notification the moment it happens. HMAC-signed `POST` to your own HTTPS endpoint — the developer-facing destination A formatted notification to any email address A message to your chat, paired with @OctavBot **Availability** — Alerts require a paid Octav wallet; the Alerts section is hidden until you have one. Each account can create up to **5 alerts** by default (contact support to raise the quota). Alerts currently run on **Ethereum Mainnet** — more networks are on the way. *** ## Create an alert rule Open **Alerts** and choose **New alert** to launch the four-step wizard. Pick what fires the alert. Every alert starts from one on-chain event type: * **ERC-20 Token Transfer** — Fire when an ERC-20 `Transfer` log is emitted matching your filter. * **Successful Transaction** — Fire on confirmed, successful transactions touching your address. * **Failed Transaction** — Fire on reverted transactions touching your address. * **Function Call** — Fire when a function selector matches the transaction input. * **Event Emitted** — Fire on arbitrary contract events by topic0, optionally with param matchers. Pick the network and the address to watch. The address means something different per trigger: * **Token contract (optional)** for ERC-20 transfers * **Wallet address** for successful / failed transactions * **Contract address** for function calls and emitted events For ERC-20 transfers you must set a **From** or **To** wallet — a token contract alone matches *every* transfer of that token across the network. You can also set an optional **minimum value**, a decimal amount in the token's display units (e.g. `100` USDC). Refine when the rule matches. * **Successful / Failed Transaction** — match by direction: *Sent by me*, *Received by me*, or *Either* (default). * **Function Call** — pick a function from the contract's verified ABI, or enter a function signature / 4-byte selector manually. Add per-argument matchers. * **Event Emitted** — pick an event (its topic0 is derived for you), or enter the topic0 and event signature manually. Add per-parameter matchers. Matchers use exactly one operator each: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, or `in`. Tuple, struct, and array parameters aren't supported yet. ERC-20 transfers fold their filters (token, From / To, minimum value) into the **Target** step, so the wizard skips a separate Conditions step for them. Name the alert — the name shows in your alert list and is included in every delivery payload — and attach at least one destination. Save the alert to arm it. *** ## Destinations A destination is where a matching alert is sent. Add one from the wizard's **Delivery** step or manage them from the Alerts page. A single alert can fan out to several destinations. Each destination must be **unique** while active — one webhook per URL, one per email address, and a single Telegram link per account. Adding a duplicate is rejected; disable or delete a destination to free that slot. Enter an HTTPS URL (for example `https://hooks.example.com/incoming`). Octav creates a signing secret for the destination and shows it to you **once**: > This is the only time you will see this secret. Store it securely — after > this dialog closes we only keep a hashed copy. Copy it somewhere safe before closing the dialog. If you lose it, **rotate** the secret from the destination's edit dialog to generate a new one. The webhook secret is shown only once and stored as a hash. You can't read it back later — rotate it if it's lost. Enter the email address that should receive the notification. Pair your Octav account with **@OctavBot** using the one-click deep link (**Open @OctavBot on Telegram**). Once paired, *Alerts will be sent to your linked chat* — you don't enter a chat ID, it's resolved from your pairing. If pairing is ever lost, re-pair with @OctavBot and refresh. *** ## Webhook deep-dive (for developers) Webhook destinations receive an HMAC-signed `POST` for every matching event. This section is the contract your receiver needs to implement. ### The request Octav sends * **Method / body** — `POST` with `Content-Type: application/json`. The body is the JSON payload below. * **Timeout** — 10 seconds. * **HTTPS only** — Octav rejects non-HTTPS URLs and any host that resolves to a private, loopback, or link-local address. You can't point a webhook at an internal host. * **Headers** — ``` X-Webhook-Signature: t=,did=,sig=sha256= X-Webhook-Signature-Version: 2 ``` ### Payload reference Every payload carries a top-level `version: 1` and a `kind` discriminator — one of `alert`, `rule_suspended`, or `rule_replay_digest`. For `alert`, the nested `event.kind` is one of `block`, `tx`, or `log`. Sent for `tx_success`, `tx_failed`, and `function_call` rules. ```json theme={null} { "version": 1, "kind": "alert", "ruleUuid": "rule-uuid", "ruleName": "a rule", "ruleAddress": "0xwatched", "chain": "ethereum", "type": "tx_success", "emittedAt": "2026-04-22T00:00:00Z", "event": { "kind": "tx", "chain": "ethereum", "hash": "0xtx", "from": "0xfrom", "to": "0xto", "status": "success", "blockNumber": 42, "value": "1000", "function": { "selector": "0xa9059cbb", "name": "transfer", "args": { "to": "0xto", "amount": "1000" }, "decoded": true } } } ``` When the calldata can't be decoded, `function` is `{ "selector": "0x…", "name": null, "args": null, "decoded": false }` and a bounded `rawInput` hex string is included instead. If the raw input is too large it's dropped in favor of `"rawOmitted": true` and `"rawBytes": `. Sent for `erc20_transfer` and `event_emitted` rules. ```json theme={null} { "version": 1, "kind": "alert", "ruleUuid": "rule-uuid", "ruleName": "a rule", "ruleAddress": "0xwatched", "chain": "ethereum", "type": "erc20_transfer", "emittedAt": "2026-04-22T00:00:00Z", "event": { "kind": "log", "chain": "ethereum", "txHash": "0xtx", "address": "0xcontract", "topics": ["0xtopic0", "0xtopic1"], "blockNumber": 42, "logIndex": 3, "decodedEvent": { "name": "Transfer", "params": { "from": "0xfrom", "to": "0xto", "value": "5" }, "decoded": true }, "rawData": "0xdata" } } ``` As with tx events, an undecoded log carries `decodedEvent.decoded: false` plus a bounded `rawData`, or `rawOmitted` / `rawBytes` when the data is too large to include. Sent once when a rule is auto-suspended for exceeding its rate limits. ```json theme={null} { "version": 1, "kind": "rule_suspended", "ruleUuid": "rule-uuid", "ruleName": "a rule", "reason": "rate exceeded", "suspendedAt": "2026-04-22T00:00:00Z", "rate": { "softCount": 120, "hardCount": 1200, "softMax": 100, "softWindowSec": 300, "hardMax": 1000, "hardWindowSec": 3600 }, "lastDeliveries": [ { "uuid": "d1", "eventFingerprint": "fp1", "chain": "ethereum", "createdAt": "2026-04-22T00:00:00Z" } ] } ``` Sent after a reconnect, summarizing catch-up events instead of delivering each one individually. ```json theme={null} { "version": 1, "kind": "rule_replay_digest", "ruleUuid": "rule-uuid", "ruleName": "a rule", "chain": "ethereum", "delivered": 10, "suppressed": 40, "windowFromBlock": 100, "windowToBlock": 200, "sampleEvents": [ { "eventFingerprint": "fp1", "emittedAt": "2026-04-22T00:00:00Z" } ] } ``` ### Verify the signature The `X-Webhook-Signature` header packs three fields: `t` (the millisecond timestamp), `did` (the delivery UUID), and `sig` (the signature). The signature is `HMAC-SHA256(secret, "{t}.{did}.{body}")` in lowercase hex. Reconstruct that string from the header fields and the **raw** request body, compare in constant time, and reject stale timestamps to guard against replay. ```js Node theme={null} const crypto = require("crypto"); function verify(rawBody, header, secret, toleranceMs = 5 * 60 * 1000) { const p = Object.fromEntries( header.split(",").map((kv) => { const i = kv.indexOf("="); return [kv.slice(0, i).trim(), kv.slice(i + 1).trim()]; }) ); const sig = p.sig.replace(/^sha256=/, ""); const expected = crypto .createHmac("sha256", secret) .update(`${p.t}.${p.did}.${rawBody}`) .digest("hex"); const a = Buffer.from(sig, "hex"); const b = Buffer.from(expected, "hex"); const fresh = Math.abs(Date.now() - Number(p.t)) < toleranceMs; return fresh && a.length === b.length && crypto.timingSafeEqual(a, b); } ``` ```python Python theme={null} import hashlib import hmac import time def verify(raw_body: bytes, header: str, secret: str, tolerance_ms: int = 5 * 60 * 1000) -> bool: parts = {} for kv in header.split(","): key, _, value = kv.partition("=") parts[key.strip()] = value.strip() sig = parts["sig"].removeprefix("sha256=") body = raw_body.decode() if isinstance(raw_body, bytes) else raw_body expected = hmac.new( secret.encode(), f"{parts['t']}.{parts['did']}.{body}".encode(), hashlib.sha256 ).hexdigest() fresh = abs(int(time.time() * 1000) - int(parts["t"])) < tolerance_ms return fresh and hmac.compare_digest(sig, expected) ``` Sign over the **raw** request body, not a re-serialized object — even a whitespace difference breaks the HMAC. Capture the raw bytes before parsing (for example `express.raw({ type: "application/json" })` in Express, or `request.get_data()` in Flask). ### Respond & retry * Return any **2xx** status to acknowledge a delivery. * Non-2xx responses and timeouts are retried up to **5 attempts** with exponential backoff (base 2 seconds). * Use the delivery UUID (`did`) as an **idempotency key** so a retried delivery isn't processed twice. ### Security * Only the bounded public payload is sent — internal fields are stripped at the boundary. * Large raw `input` / `data` is capped (\~8 KB) and replaced with `rawOmitted` / `rawBytes`. * Octav rejects non-HTTPS URLs and any host resolving to a private or loopback address. *** ## Delivery history & suspension Open any alert to see its delivery history. Each delivery shows one of: * **Pending** — queued or in flight * **Success** — acknowledged with a 2xx * **Failed** — exhausted its retries; you can **Retry** it manually * **Dropped** — not attempted (for example, the destination was removed) Two synthetic rows can also appear: **Rule suspended** and **Catch-up digest** (see below). If a rule fires faster than its rate limits allow, Octav automatically suspends it to protect you and your destinations. You receive a `rule_suspended` webhook and an in-app banner — *This alert is suspended* — with a **Reactivate** button. A rule's status pill reads **Active**, **Suspended**, or **Disabled**. After Octav reconnects to the chain, missed events may be folded into a single `rule_replay_digest` delivery summarizing what was delivered and suppressed during the gap, rather than one delivery per event. *** ## Related Label wallets and reuse them when targeting alerts Review the on-chain activity your alerts watch Turn tracked activity into financial reports # Passwordless Login & Account Security Source: https://docs.octav.fi/docs/authentication How Octav's passwordless email authentication works, and best practices for keeping your account secure. Learn about Octav's secure, passwordless authentication system and best practices for protecting your account. Passwordless email authentication Industry-leading security standards Read-only platform, your keys stay safe Encrypted connections and secure storage *** ## How Sign-In Works Octav uses passwordless authentication via email magic links for a secure, convenient experience. Go to [pro.octav.fi](https://pro.octav.fi) Enter your email address on the login page Look for an email from Octav with your magic link (check spam folder if needed) Click the link in the email to instantly sign in No password needed - you're now signed in securely **Passwordless Authentication** — Magic links eliminate the need for passwords, reducing the risk of password theft, phishing, and credential reuse. *** ## Why Magic Links? ### More Secure Than Passwords Can't reuse credentials from breached sites No risk of weak or guessable passwords Harder to phish than traditional credentials Proves you control the email address ### Simpler & Faster **Benefits:** * No passwords to remember * No password reset flows * No password complexity rules * Faster sign-in process * Works on any device **One Click Access** — Magic links provide secure access with just one click from your email. ### Technical Details **Magic Link Flow:** 1. You enter your email address 2. Octav generates a unique, time-limited token 3. Token is sent to your email 4. You click the link containing the token 5. Octav validates the token and creates a session 6. You're signed in securely **Security Features:** * Tokens expire after 15 minutes * Single-use tokens (can't be reused) * Secure token generation * HTTPS encryption in transit *** ## Magic Link Best Practices **Protect Your Email Account:** * Use a strong, unique password for your email * Enable two-factor authentication on your email account * Don't share your email credentials * Use a reputable email provider **Email Access = Account Access** — Anyone with access to your email can sign in to your Octav account. Secure your email with strong authentication. **Magic Links Expire:** * Links are valid for 15 minutes * Each link can only be used once * Request a new link if yours has expired * Old links become invalid after use **If Your Link Expired:** 1. Return to [pro.octav.fi](https://pro.octav.fi) 2. Enter your email again 3. Request a new magic link 4. Check your inbox for the new link **Always Verify:** * Official domain: **pro.octav.fi** * Look for HTTPS in the address bar * Check for the padlock icon * Bookmark the official site **Red Flags:** * Suspicious domains (octav-login.com, octav.io, etc.) * No HTTPS encryption * Unusual email sender addresses * Unexpected login requests *** ## API Authentication For developers integrating with the Octav API. Complete API authentication guide Manage your API keys **API Authentication Methods:** * **API Keys** - For server-to-server communication * **JWT Tokens** - For client-side applications [View complete API documentation →](/api/authentication) *** ## Account Security ### Read-Only Platform **Your Keys Stay Safe** — Octav is a read-only portfolio tracker. We never ask for private keys or seed phrases, and we cannot execute transactions on your behalf. **What Octav Can See:** * Your wallet addresses (that you provide) * Public blockchain transaction data * Token balances and positions **What Octav Cannot Access:** * Your private keys * Your seed phrases * Your exchange credentials * Ability to move your funds ### SOC 2 Compliance **Design Compliance** Security controls properly designed **Operational Compliance** Security controls operating effectively over time **What This Means:** * Industry-leading security standards * Regular third-party security audits * Comprehensive security controls * Data protection best practices * Continuous monitoring and improvement *** ## Data Protection ### How We Protect Your Data **Encryption:** * Data encrypted in transit (TLS 1.3) * Secure data storage * API keys encrypted at rest * Secure communication protocols **Infrastructure:** * SOC 2 Type 1 & Type 2 certified * Regular security audits * Monitored 24/7 * Secure hosting infrastructure ### Your Privacy Matters **We Never:** * Sell your personal data * Share data with advertisers * Track you across other websites * Ask for private keys or seed phrases **We Only Collect:** * Email address for authentication * Wallet addresses you provide * Public blockchain data * Essential account information ### Data Control **You Can:** * Access your data anytime * Export your data * Delete your account and data * Control what wallets we track **GDPR & CCPA Compliant:** * Right to access * Right to deletion * Right to portability * Right to correction [View Privacy Policy →](https://pro.octav.fi/privacy) *** ## Need Help? ### Account Access Issues If you're having trouble signing in or accessing your account, please contact our support team. Make sure the magic link hasn't expired (15 minute limit) Magic link emails may sometimes end up in spam Try requesting a new magic link if yours expired If issues persist, email [info@octav.fi](mailto:info@octav.fi) **Account Recovery** — For account access issues, please contact [info@octav.fi](mailto:info@octav.fi) with your account email address. ### Support Options [info@octav.fi](mailto:info@octav.fi) Community support Get help from our team # Brand Guidelines Source: https://docs.octav.fi/docs/brand-guidelines Official Octav brand color palette Official brand color palette for using Octav's visual identity. *** ## Color Palette ### Primary Colors

Usage: Primary CTAs, emphasis, brand moments

Usage: Interactive elements, buttons, links

*** ### Secondary Colors

Usage: Dark backgrounds, headers

Usage: Success states, positive indicators

Usage: Positive changes, success messages

Usage: Warnings, important notices

Usage: Errors, negative changes

*** ### Neutral Colors #### Light Backgrounds

Usage: Primary light background

Usage: Secondary light background

Usage: Borders, dividers

#### Dark Backgrounds

Usage: Primary dark background

Usage: Secondary dark background

Usage: Dark UI elements

#### Text Colors

Usage: Main text on light backgrounds

Usage: Secondary text on light backgrounds

Usage: Main text on dark backgrounds

Usage: Secondary text on dark backgrounds

# Build Custom Widgets Source: https://docs.octav.fi/docs/build-custom-widgets Create your own custom dashboard widgets for Octav The Octav Widget SDK is currently in development. Join our early access program to be the first to build custom widgets. ## Overview The ability to build custom widgets for Octav is coming soon. This will enable you to: * Build custom visualizations * Create personalized analytics * Integrate external data sources * Share widgets with the community * Monetize premium widgets *** ## Widget Marketplace **Coming Soon** — The Widget Marketplace will allow community members to create, share, and monetize custom widgets. **Browse and Install:** * Discover community-created widgets * Install third-party widgets with one click * Rate and review widgets * Report issues or bugs * Share your favorites * Premium widget options Open-source community contributions Advanced features from professional developers **Build and Publish Widgets:** Get started with our comprehensive Widget SDK and documentation Create custom widgets using React and our widget framework Test your widget and submit for review Publish to the marketplace and earn from premium widgets Learn how to create custom widgets with our SDK **Custom Widget Development:** For teams and organizations needing specialized widgets: * Dedicated developer support * Custom widget development service * White-label widget options * Priority feature requests * SLA guarantees * On-premise deployment options Discuss custom widget development for your organization *** ## Get Notified Join our early access program to be notified when the Widget SDK launches. Join our #widget-development channel for updates Express interest in custom widget development *** ## Learn More Browse available widgets by category View all currently supported widgets Learn about the home dashboard # Changelog Source: https://docs.octav.fi/docs/changelog Latest updates and changes to Octav Track the latest features and bug fixes in Octav. We're constantly shipping new updates to make your portfolio tracking experience better. **Stay Updated** — Follow us on [Twitter](https://twitter.com/OctavApp) or join our [Discord](https://discord.com/invite/qvcknAa73A) for real-time update announcements. *** ## October 2025 ### October 28, 2025 **API Error Resolution** Fixed 402 payment error on `/v1/agent/wallet` and `/v1/agent/portfolio` endpoints *** ### October 23, 2025 **New Reports System** Complete reports page with comprehensive reporting features for portfolio analysis and data exports. [Learn more about Reports →](/docs/reports) **Advanced Pricing System** * Custom price token configuration * Multiple price sources (Debank, Coingecko, Defillama) * Price source priority settings * User asset pricing controls * Unified pricing across portfolio **New Chain Support** Added TAC (The Abyss Chain) portfolio tracking *** ## September 2025 ### September 24, 2025 **New Chain Added** Added Lighter Chain with full portfolio tracking **DeFi Protocol Support** * Public vaults * Spot positions * Margin positions *** ### September 10, 2025 **Portfolio-Only License** New license option without transaction tracking - access to portfolio and snapshots only **Testnet Support** Added transaction support for Plasma Testnet *** ### September 3, 2025 **Snapshot Title Display** Fixed an issue with snapshot title display when the name is too long **New Chain** Added Unichain transaction support *** ## August 2025 ### August 27, 2025 **Portfolio Tracking Added** Added portfolio support for: * **Corn** - DeFi layer * **Ink** - Kraken's L2 * **Katana** - Ronin DEX chain * **Morph** - Consumer L2 * **Swellchain** - Liquid staking L2 **Multi-Chain Lagoon Support** Support for Lagoon balances on: * Arbitrum * Avalanche * Base * Ethereum *** ### August 21, 2025 **DeFi Protocol Integration** * **Ekubo** - AMM protocol on Starknet * **Vesu** - Lending protocol on Starknet **New Field Added** Added `priceSource` field on API payloads for better price transparency **New Chain** Added portfolio support for Plume (RWA-focused chain) *** ### August 13, 2025 **Performance Improvement** Rate limit doubled from **180 to 360 requests/minute** on all endpoints for better API performance *** ### August 6, 2025 **New Endpoint** New API endpoint for tracking Solana airdrops [View API Documentation →](/api/introduction) *** ## Feature Requests Have a feature you'd like to see? We'd love to hear from you! Share your ideas and vote on features Discuss features with the community **Want to see a new feature or blockchain?** Submit your request on [Canny](https://octav.canny.io/) or chat with us on [Discord](https://discord.com/invite/qvcknAa73A) *** ## Stay Informed Follow for real-time updates Join our community Subscribe for monthly updates **Release Notes** — Detailed release notes are posted on our [Discord announcements](https://discord.com/invite/qvcknAa73A) channel. # Contact Us Source: https://docs.octav.fi/docs/contact-us Get in touch with the Octav team Get in touch with the Octav team through any of our channels below. **Discord is the easiest way to get support** — Join our community for quick help from the team and other users. *** ## Connect With Us **Twitter/X** [https://x.com/OctavFi](https://x.com/OctavFi) **LinkedIn** [https://www.linkedin.com/company/octav-labs](https://www.linkedin.com/company/octav-labs/?viewAsMember=true) **Discord** [https://discord.com/invite/qvcknAa73A](https://discord.com/invite/qvcknAa73A) **Email** [info@octav.fi](mailto:info@octav.fi) # Crypto Portfolio Dashboard Source: https://docs.octav.fi/docs/home Build a customisable workspace tracking your crypto portfolio across every supported chain, with multiple boards for wallet and DeFi positions. Your Octav Home dashboard is a powerful, customizable workspace for tracking your crypto portfolio across 90+ blockchains. Create multiple dashboard boards, add widgets, and arrange everything exactly how you want it. Create up to 10 different dashboards for different portfolios Add, resize, and arrange widgets to build your perfect view Live portfolio value updates across 90+ chains *** ## Dashboard Boards **Multiple Dashboards** — Create up to 10 different dashboard boards for different portfolios, trading strategies, or viewing preferences. ### Board Management Your dashboard header shows: **Active Wallet Display** * Wallet label (e.g., "Hot Wallet 1") * Shortened address (e.g., "0x89d1...ad473a") * Quick wallet switching dropdown **Sync Status** * Last data refresh timestamp * Manual refresh button * Auto-updates every 30 seconds ### Board Actions Menu Click the menu button on your board to access: Open the Widget Manager to browse and add new widgets to your current board Change the name of your current dashboard board (e.g., "Main Board", "Trading View", "DeFi Dashboard") Create a copy of your current board with all widgets and layout intact Restore the default widget layout for this board Add a new empty dashboard board (up to 10 boards total) Remove the current board (cannot delete your last remaining board) **Quick Board Switching** — Use the plus sign next to your board name to quickly create a new board, or click the board name dropdown to switch between existing boards. *** ## Widget System **Drag, Drop, and Resize** — All widgets can be freely moved around your dashboard and resized to your preference. Create the perfect layout for your needs. ### Widget Manager Click "Add Widget" from your board menu to open the Widget Manager, where you can browse and add widgets to your dashboard. **Widget Categories:** Portfolio summaries, net worth displays, and high-level metrics Filter and display data by specific blockchains DeFi protocol positions, yields, and interactions Chain-specific balances and activity Wallet-level data, transactions, and balances **Each Widget Shows:** * Widget name and description * Author: Octav Labs * Price (Free, or cost in credits) * License tier (Free, Lite, Pro) * Category tag View all widgets currently added to your dashboard: * See which widgets are active on the current board * Remove widgets you no longer need * Quick access to widget settings * Reorder widgets in the list ### Widget Pricing Widgets come in three tiers: **\$0** Core portfolio widgets available to all users **$10 - $50** Enhanced widgets with additional features and data **$25 - $50** Advanced analytics, exports, and premium data **License Types:** * **Free License** - Access to basic widgets * **Lite License** - Unlocks enhanced widgets (\$10-50 one-time or monthly) * **Pro License** - Full access to all widgets (\$25-50 one-time or monthly) **Credits Required** — Premium widgets require credits. Purchase credits in your [Account Settings](/docs/account). [Explore all available widgets →](/docs/supported-widgets) *** ## Portfolio Summary Widget The Portfolio Summary widget is your main overview, showing key portfolio metrics at a glance. **Total Portfolio Value** Your net worth displayed prominently, showing: * Total value in USD * Value in ETH equivalent * 24h change (percentage and absolute) **Value Breakdown:** * **Protocols** - Value held in DeFi protocols (percentage of total) * **Wallet** - Value in wallet balances (percentage of total) **Visual Portfolio Breakdown** Three interactive pie charts showing your distribution: **By Blockchain** See which chains hold the most value (Ethereum, Base, Arbitrum, etc.) **By DeFi Protocol** View your exposure across protocols (Aave, Uniswap, Lighter, etc.) **Asset Location** Percentage split between wallet balances and DeFi positions **Historical Value Tracking** Track your portfolio value over time with multiple time ranges: **Time Range Options:** * **1D** - Last 24 hours (hourly data) * **1W** - Last 7 days (4-hour data) * **1M** - Last 30 days (daily data) * **3M** - Last 90 days (daily data) * **6M** - Last 180 days (weekly data) * **YTD** - Year to date * **1Y** - Last 12 months (weekly data) **Chart Features:** * Interactive hover details * Value at specific timestamps * Percentage change indicators * Export chart as image *** ## Token List View all your token holdings organized by blockchain. **Chain-Organized View** Tokens grouped by blockchain with: * Chain logo and name * Token symbol and logo * Balance quantity * USD value * 24h price change * Percentage of portfolio **Supported Chains:** * Ethereum, Base, Arbitrum, Optimism, Polygon * Solana, BSC, Avalanche, Fantom * And 80+ more chains **Filter Options:** Show tokens from specific blockchains only Hide small balances below a threshold (dust filter) Sort by value, name, change %, or balance Quickly find specific tokens by name or symbol *** ## Protocol List Track your DeFi positions across protocols. **DeFi Protocol Holdings** See all protocols where you have active positions: * Aave V3 * Lighter Protocol * Resolv * Hyperliquid * friend.tech * And 10,000+ more protocols **Real-Time Valuations** Each protocol shows: * Total value locked in protocol * Individual position details * APY/APR on yield positions * Claimable rewards [Learn more about supported protocols →](/docs/supported-blockchains) *** ## Dashboard Settings Access dashboard settings from the top-right settings button. **Display Net Worth In:** Choose how your portfolio value is displayed: **Ethereum** Display value in ETH **Solana** Display value in SOL **Bitcoin** Display value in BTC Default is USD, but you can switch to crypto-denominated values for a different perspective on your portfolio. **Show Value Above** Set a minimum value threshold to hide small balances (dust): * Enter a dollar amount (e.g., $1, $10, \$100) * Tokens below this value are hidden from your dashboard * Helps declutter your view by hiding dust and airdrops * Does not affect your total portfolio value calculation **Dust Filtering** — Set a threshold like $1 or $10 to hide low-value tokens and keep your dashboard clean. **Appearance Settings** Clean, bright interface for daytime use Easy on the eyes for extended viewing Follows your system preferences [More appearance options in Account Settings →](/docs/account#appearance) *** ## Widget Customization **Fully Customizable** — Every widget on your dashboard can be moved, resized, and configured to create your perfect workspace. ### Layout Control **Reposition Widgets** * Click and hold any widget header * Drag to a new position * Other widgets automatically adjust * Layout saves automatically **Adjust Widget Size** * Hover over widget corners * Drag corner to resize * Snap to grid alignment * Minimum and maximum sizes enforced ### Widget Settings Each widget has its own settings accessible by clicking the widget's settings icon: **Customize Display Options** * Time ranges for charts * Display currency preferences * Filter by chains or protocols * Toggle data fields on/off **Update Frequency** * Auto-refresh interval * Manual refresh button * Last update timestamp * Loading state indicators **Delete from Board** * Remove widget from current board * Does not affect other boards * Can be re-added anytime from Widget Manager *** ## Quick Actions Click the plus sign next to your board name to create a new dashboard Open the board menu and click "Add Widget" to browse available widgets Drag widgets to reposition and resize from corners Use the settings button to adjust display preferences and filters Use the wallet selector dropdown to view different addresses *** ## Need Help? Browse all available widgets and their features Detailed documentation for each widget type Get help from our community and team # Logo Files Source: https://docs.octav.fi/docs/logo-files Download official Octav logos and brand assets Download official Octav logos and brand assets in various formats. By downloading these assets, you agree to follow our [brand guidelines](/docs/brand-guidelines) and use them appropriately. *** ## Logo Variations ### Primary Logos
Octav Bevel Logo Light

Octav Bevel Logo (Light)

Primary logo with bevel effect for depth and dimension

When to use: Marketing materials, website headers, presentations, print materials

Octav Bevel Logo Dark

Octav Bevel Logo (Dark)

Primary logo with bevel effect for dark backgrounds

When to use: Dark backgrounds, dark mode applications

Octav Solid Logo Light

Octav Solid Logo (Light)

Simplified solid version without bevel effect

When to use: Small sizes, embroidery, engraving, single-color applications, favicons

Octav Solid Logo Dark

Octav Solid Logo (Dark)

Simplified solid version for dark backgrounds

When to use: Small sizes on dark backgrounds, dark mode

*** ### Icon Marks
Octav Icon

Octav Icon

Standalone "O" icon mark for app icons and small applications

When to use: App icons, favicons, social media profile images, small UI elements

Octav Icon Red

Octav Icon (Red)

Red variant of the "O" icon mark

When to use: Brand accent applications, special marketing materials, favicon

Octav Icon Top View

Octav Icon (Top View)

Top-down view of the "O" icon

When to use: Alternative icon placement, decorative elements

Octav Icon Side View

Octav Icon (Side View)

Side-facing "O" icon with unique perspective

When to use: Alternative icon placement, decorative elements, special applications

*** ### Product Logos
Octav Pro Logo

Octav Pro Logo

Official Octav Pro product logo

When to use: Octav Pro marketing, product pages, promotional materials

Octav API Logo

Octav API Logo

Official Octav API product logo

When to use: API documentation, developer resources, technical materials

# Crypto Portfolio Tracker Pricing Source: https://docs.octav.fi/docs/pricing Compare Octav plans for tracking crypto portfolios across every supported chain, from free wallet tracking to institutional reporting. Transform how you track and analyze your crypto portfolio across 90+ blockchains with Octav's flexible pricing plans.
Try out

Free

\$0
Portfolio Tracking
All blockchains included
Starter plan

Lite

\$149 \* / Year
Daily Portfolio Snapshot
Historical Portfolio Timeline
Financial Analytics Widgets
Asset Exposure Overview
\*per address
Growth plan

Pro

\$499 \* / Year
Transaction Tagging & Labeling
PDF / CSV Reports
Asset Variation report
P\&L Calendar
\*per address
Enterprise solution

Institutional

Custom
Dedicated Support team
Advanced integrations
Custom development
*** ## Why Choose Octav? Track your portfolio across all major ecosystems including Ethereum, Solana, Bitcoin layers, and more Get instant portfolio updates and transaction tracking with live price feeds Automatic tracking of 10,000+ DeFi protocols including staking, lending, and liquidity positions Net Asset Value reporting for accurate portfolio valuation and performance tracking Export comprehensive reports for financial analysis and accounting purposes SOC 2 certified with enterprise-grade security and compliance *** ## Frequently Asked Questions For Lite and Pro plans, pricing is based on the number of wallet addresses you track. Each address is billed separately at the annual rate. Yes! You can upgrade from Free to Lite or Pro at any time. When you upgrade, you'll be charged the prorated amount for the remainder of the billing period. We accept cryptocurrency payments via Coinbase Commerce through the app. For Institutional plans, we also support wire transfers. The Institutional plan is fully customizable based on your organization's needs. It includes everything in Pro, plus dedicated support, custom integrations, white-label options, advanced API access, and custom development. [Contact our sales team](mailto:info@octav.fi?subject=Institutional%20Plan%20Inquiry) to discuss your requirements. Yes! We offer volume discounts for teams and organizations tracking 30+ addresses. Contact us at [info@octav.fi](mailto:info@octav.fi) to discuss custom pricing for your needs. *** ## Ready to get started? Choose the plan that fits your needs and start tracking your crypto portfolio across 90+ blockchains today. Launch Octav and connect your first wallet Discuss custom enterprise solutions # Quickstart: Crypto Portfolio Tracking Source: https://docs.octav.fi/docs/quickstart Set up Octav and start tracking a crypto portfolio across every supported chain in a few minutes, with analytics on wallet and DeFi positions. Get up and running with Octav in just a few minutes. Track your crypto portfolio across 90+ blockchains with powerful analytics and insights. Visit [pro.octav.fi](https://pro.octav.fi) and create your account: 1. Enter your email address 2. Check your inbox for a magic link 3. Click the magic link to sign in 4. Complete your profile: * Enter your account name * Add your Telegram username (optional - for updates and direct support) 5. Click **Complete Sign Up** 6. You're in! No password needed **Passwordless Authentication** — Octav uses secure magic links for authentication. Simply enter your email and click the link we send you. Add the wallets you want to track: 1. Navigate to **Address Book** from the sidebar 2. Click the **Add Address** button 3. Paste your wallet address 4. Give it a friendly label (e.g., "Main Wallet", "Hardware Wallet") **90+ Blockchains Supported** — Octav automatically tracks all supported blockchains by default. No need to select which chains to monitor! **Major Blockchains Supported:** * Ethereum * Base * Arbitrum * Solana * Hyperliquid * BSC (BNB Chain) * Polygon * And 80+ more chains Once your wallet is added, your dashboard comes to life with: See your total portfolio value, 24h changes, and asset distribution at a glance View all your tokens organized by chain, with real-time prices and balances Monitor your latest swaps, transfers, and DeFi interactions across all chains Track your portfolio's historical performance over time Make Octav work for you by adding widgets: 1. Open the **Widget Explorer** from your dashboard 2. Browse widgets by category (DeFi, Analytics, Reports, etc.) 3. Click **Add to Dashboard** on widgets you want 4. Drag and drop to arrange your perfect layout Track staking, liquidity pools, and yield View detailed token balances and values Generate comprehensive data reports *** ## What's Next? Learn about all the features available on your home dashboard Discover all available widgets to customize your experience See the complete list of 90+ supported chains and networks Dive deep into your transaction history and analytics *** ## Quick Tips You can add as many wallet addresses as you want! Each wallet can be labeled and organized independently. Go to **Address Book** and click **Add Address** for each wallet you want to track. We support: * Ethereum addresses (0x...) * Solana addresses (base58) * ENS names (e.g., vitalik.eth) * And addresses from 90+ blockchains Absolutely! Octav takes security seriously: * **Read-only platform** - We never ask for private keys * **Public blockchain data** - We only read publicly available data * **SOC 2 Type 1 & Type 2 Compliant** - Industry-leading data security standards * **Secure storage** - Your wallet addresses are stored securely with encryption [Learn more about security →](/docs/authentication#security-best-practices) Octav supports **90+ blockchains** including: **Major EVM Chains:** * Ethereum, Base, Arbitrum, Optimism, Polygon, Avalanche, BSC **Non-EVM Chains:** * Solana, Hyperliquid **And many more chains** - all automatically tracked by default! [View all supported blockchains →](/docs/supported-blockchains) *** ## Need Help? **Having trouble?** Join our [Discord community](https://discord.com/invite/qvcknAa73A) for instant support from the Octav team and community members. Get help from our community Email our support team Build with Octav's API # Reports Source: https://docs.octav.fi/docs/reports Generate portfolio snapshots and transaction reports Generate comprehensive portfolio snapshots, transaction reports, and asset variation reports to analyze your crypto holdings and activity. ## Overview Octav Reports provide: View your portfolio by wallet, NFTs, and DeFi positions Create custom reports of your transaction data Track how your assets change over time WEB, PDF, and CSV export options Report on individual addresses or bundles Generate reports for any time period ## Report Types ### Portfolio Snapshot Report Get a comprehensive view of your portfolio including wallets, NFTs, and protocol positions at any point in time. **What's Included:** * Complete wallet balances * NFT holdings * DeFi protocol positions * Current asset values * Portfolio allocation breakdown **Export Formats:** * **WEB** - Interactive view in browser * **PDF** - Professional formatted report * **CSV** - Spreadsheet-compatible data **Use Cases:** * Portfolio performance tracking * Net worth snapshots * Sharing with stakeholders * Record keeping * Historical portfolio analysis ### Transaction Report Create detailed reports of your transaction history with flexible filtering options. **What's Included:** * Complete transaction history * Transaction types and categories * Network information * Protocol interactions * Asset movements * Timestamps and values **Filter Options:** Filter by specific blockchain networks Select specific DeFi protocols Filter by transaction categories (swap, transfer, etc.) Filter transactions involving specific assets Filter by transaction type Select any custom date range **Export Formats:** * **WEB** - Interactive view with sorting and filtering * **CSV** - Import to Excel or Google Sheets **Additional Options:** * Hide spam transactions * Transaction count displayed before generation * Filter by multiple criteria simultaneously ### Asset Variation Report Track how your asset holdings change over time with custom variation reports. **What's Included:** * Asset balance changes over time * Beginning and ending balances * Net changes per asset * Portfolio composition shifts * Network-level breakdowns **Filter Options:** * **Networks** - Filter by blockchain * **Protocols** - Include specific protocols * **Date Range** - Compare any two periods * **Assets** - Track specific assets **Export Formats:** * **WEB** - Interactive view with charts * **CSV** - Detailed data for analysis **Use Cases:** * Month-over-month comparisons * Quarterly portfolio reviews * Track asset accumulation * Identify portfolio drift * Performance attribution **Additional Options:** * Hide spam assets * Asset count displayed before generation * Custom date range selection ## Generating Reports ### Portfolio Snapshot Report Go to the Reports page from the main navigation Select "Create" under Portfolio Snapshot Report Choose the addresses or bundles to include in your snapshot Select WEB, PDF, or CSV format Click "Generate Snapshot" to create your report ### Transaction Report Go to the Reports page from the main navigation Select "Create" under Transactions Report Choose which addresses or bundles to include Use the available filters to narrow down transactions: * Networks * Protocols * Categories * Assets * Transaction Types * Date Range * Choose export format (WEB or CSV) * Toggle "Hide spam transactions" if desired * Review transaction count Click "Generate Report for X txs" to create your report ### Asset Variation Report Go to the Reports page from the main navigation Select "Create" under Asset Variation Report Choose which addresses or bundles to include Select the time period for comparison (e.g., compare two months) Use the available filters: * Networks * Protocols * Choose export format (WEB or CSV) * Toggle "Hide spam assets" if desired * Review asset count Click "Generate Report for X assets" to create your report ## Managing Your Reports ### Your Reports Section All generated reports are saved and accessible in the "Your Reports" section. **Features:** * Search through your reports * Filter by report type (Snapshot, Transaction, Asset) * Filter by addresses or bundles * Filter by date range * View report metadata **Report Information Displayed:** * Creation date * Report name * Report type * Associated addresses/bundles * Active/inactive status * Next snapshot date * Last snapshot date * Number of addresses included ### Filtering Your Reports Use the built-in filters to quickly find reports: * **Search Bar** - Search reports by name * **Address/Bundle Filter** - Select specific addresses or bundles * **Date Picker** - Filter by creation date * **Report Type Tabs** - Toggle between Snapshot, Transaction, and Asset reports ### Scheduled Reports Create recurring reports that generate automatically on a schedule. **Scheduled Report Features:** * Set as Active or Inactive * Automatic generation at specified intervals * View next scheduled generation date * Access all historical snapshots * Manage multiple scheduled reports per bundle **Example Use Case:** Create a monthly snapshot of your "Hot Wallet 1" bundle that generates automatically on the 1st of each month. ## Report Scope Options ### Individual Addresses Generate reports for specific wallet addresses: * Single address reporting * Focused analysis * Granular tracking ### Bundles Generate reports for grouped addresses: * Multiple addresses combined * Portfolio-level view * Simplified management * Consistent reporting across related wallets ### Mixed Selection Combine individual addresses and bundles: * Maximum flexibility * Custom groupings * Ad-hoc analysis * Compare different portfolio segments ## Export Formats ### Interactive Web View **Features:** * Interactive tables * Sorting and filtering * Real-time updates * Shareable links * Responsive design **Available For:** * Portfolio Snapshot Reports * Transaction Reports * Asset Variation Reports ### Professional PDF Reports **Features:** * Print-ready format * Professional layout * Charts and tables * Offline access * Easy sharing **Available For:** * Portfolio Snapshot Reports **Best For:** * Presentations * Record keeping * Offline review * Stakeholder sharing ### Spreadsheet-Compatible Data **Features:** * Raw data export * Import to Excel/Google Sheets * Custom analysis * Data manipulation * Integration with other tools **Available For:** * Portfolio Snapshot Reports * Transaction Reports * Asset Variation Reports **Best For:** * Custom analysis * Data processing * Integration workflows * Advanced filtering ## Common Use Cases ### Monthly Portfolio Review Set up a monthly scheduled snapshot for your main portfolio Use Asset Variation Report to compare changes between months Generate Transaction Report for the month to see all activity Download PDF snapshots for your records ### Protocol-Specific Analysis Use Transaction Report filters to select specific protocols Choose the analysis period Download data for detailed analysis Use Portfolio Snapshot to see current protocol positions ### Multi-Wallet Management Group related wallets into a bundle Set up automatic snapshots for the bundle Generate transaction and variation reports for the entire bundle Create separate reports for specific addresses when needed ## Privacy & Security ### Data Handling Reports generated on-demand when requested Reports only accessible to your account All data encrypted at rest and in transit Reports are private by default ## Getting Help Need help with reports? Check out our tutorials or contact support. ### Resources Learn about transaction tracking Manage and organize your addresses Get help from our team Learn how to use Octav # Submit a Feature Source: https://docs.octav.fi/docs/submit-feature Request new features and vote on existing suggestions Help shape the future of Octav by submitting feature requests and voting on ideas from the community. *** ## Submit a Feature Request We use **Canny** to collect and track feature requests from our community. ### What You Can Do on Canny * **Submit new feature ideas** - Share your suggestions for improvements * **Vote on existing requests** - Upvote features you want to see * **Comment and discuss** - Add details or use cases to feature requests * **Track progress** - See which features are planned, in progress, or completed Before submitting, search existing requests to see if your idea has already been suggested. Upvote existing requests instead of creating duplicates! *** ## Submit via Discord You can also submit feature requests directly in our Discord community. ### Discord Submission 1. Join our Discord server using the link above 2. Navigate to the **#feature-requests** channel 3. Post your feature request with details 4. Community members can react to upvote 5. Team members will review and respond Discord is the easiest way to get quick feedback from both the team and community on your feature ideas. # Supported Blockchains Source: https://docs.octav.fi/docs/supported-blockchains Track your portfolio across 90+ blockchains with Octav Octav provides comprehensive support for tracking assets across **90+ blockchains**, covering all major ecosystems including EVM, Solana, and Hyperliquid. **Multi-Chain Portfolio Tracking** — Connect wallets from different blockchains and view them all in one unified dashboard. *** ## Major Blockchains **The foundation of DeFi** Native Token: ETH **Coinbase's L2** Native Token: ETH **Leading L2 solution** Native Token: ETH **High-speed blockchain** Native Token: SOL **Decentralized perps** Native Token: HYPE **BNB Smart Chain** Native Token: BNB **Ethereum's sidechain** Native Token: POL **OP Stack L2** Native Token: ETH **Fast & scalable** Native Token: AVAX *** ## All Supported Chains ### Layer 2 Scaling Solutions High-performance L2 Gaming & social L2 Coinbase L2 OP Stack L2 Native yield L2 Zero-knowledge L2 ConsenSys zkEVM Native zkEVM Polygon's zkEVM Cairo-based L2 Creator economy L2 DeFi-focused L2 Modular L2 Decentralized L2 Hybrid compute L2 Frax's L2 Based rollup OKX L2 BSC's L2 Uniswap's L2 Fast transactions Consumer-focused L2 Social L2 Social network L2 ApeCoin L2 Kraken's L2 Sony's L2 Worldcoin L2 Gaming L2 Privacy L2 AI-enhanced L2 Galxe's L2 Consumer L2 ### EVM-Compatible Layer 1 Blockchains Leading smart contract platform BNB Smart Chain Ethereum sidechain Subnet architecture Fast finality Payments focused Mobile-first Crypto.com chain Cronos L2 Asian market focus Polkadot parachain Kusama parachain DeFi-focused Bitcoin-aligned Bitcoin sidechain Cosmos EVM IoT blockchain SHIB L2 Gaming platform Polkadot parachain Omnichain Data blockchain Payments Gaming optimized Fast & feeless DOGE ecosystem Sports & entertainment Chinese blockchain JavaScript SDK Tezos L2 Data ownership ### Alternative & Specialized Chains High-throughput blockchain Decentralized perpetuals exchange Liquidity-focused PoL Trading-optimized Gaming & NFTs Axie Infinity chain XRP Ledger TON-based ### Bitcoin Layers & Sidechains Bitcoin smart contracts Bitcoin L2 Bitcoin L2 Build on Bitcoin Bitcoin-aligned Bitcoin L2 ### DeFi-Focused & Infrastructure Chains Privacy DeFi Restaking layer DeFi infrastructure Trading optimized Ronin DEX chain BTC restaking RWA-focused Liquid staking L2 IP blockchain Liquid staking Cross-chain clearing High performance NFT-focused Royalty-focused ### Newly Launched & Emerging Chains Entertainment & gaming DeFi layer Bitcoin-Ethereum bridge Community-driven Gaming chain *** ## What We Track Octav automatically tracks comprehensive data across all supported blockchains: **All native currencies** ETH, SOL, AVAX, MATIC, BNB, and all other native tokens across 90+ chains **All major standards** * ERC-20 (Ethereum & EVM chains) * SPL (Solana) * BEP-20 (BSC) * Automatically detected **10,000+ protocols supported** * Staking positions * Liquidity pools * Lending/Borrowing * Yield farming * Derivatives * Perpetuals **Complete token tracking** * ERC-721, ERC-1155 * Solana NFTs (Metaplex) * Compressed NFTs (cNFTs) * NFTs appear in transactions **Protocol Support** — We track positions across 10,000+ DeFi protocols including Uniswap, Aave, Compound, Curve, Lido, Rocket Pool, Jupiter, Raydium, and many more. *** ## Data Freshness & Reliability **Live transaction tracking** * New transactions appear instantly * Balance changes reflected immediately * Price updates every few seconds **Comprehensive blockchain indexing** * Historical transaction data * Complete balance history * NFT metadata caching * 99.9% uptime **On-demand updates** * Force refresh anytime * Sync specific wallets * Update individual chains *** ## Security & Compliance **Your keys stay secure** * Never asks for private keys * Only reads public blockchain data * No transaction signing **Industry-leading security** * SOC 2 Type 1 Compliant * SOC 2 Type 2 Compliant * Enterprise-grade data security *** ## Request New Chain Support **Don't see your blockchain?** We're constantly adding new chains based on community feedback! Help us prioritize which chains to add next: Join our [Discord community](https://discord.com/invite/qvcknAa73A) to discuss chain requests Submit your chain request on our [feature board](https://octav.canny.io/) Upvote existing chain requests to show community demand Submit and vote on chain requests Discuss with the community *** ## Technical Details Octav uses a combination of: * **Direct node connections** for real-time data * **Third-party indexers** for historical data * **Custom indexing** for specialized features * **Caching layers** for performance This ensures fast, reliable data across all 90+ chains. Octav handles blockchain reorgs automatically: * Monitors for chain reorganizations * Updates affected transactions * Maintains historical accuracy * Notifies users of significant changes Yes! Our API provides programmatic access to: * Multi-chain wallet data * Cross-chain transaction history * Real-time balance updates * DeFi position tracking [Explore the API documentation →](/api/introduction) # Supported Widgets Source: https://docs.octav.fi/docs/supported-widgets Complete catalog of available Octav dashboard widgets Explore all available Octav dashboard widgets. From portfolio tracking to DeFi analytics, find the perfect widgets for your needs. Core widgets available to everyone Advanced widgets with Lite and Pro licenses Add widgets to your dashboard instantly **One-Click Installation** — All widgets can be added to your dashboard instantly from the [Widget Explorer](/docs/widget-explorer). Drag, drop, and customize to build your perfect workspace. *** ## Available Widgets **Author:** Octav Labs Summary of your portfolio value and breakdown * License: Free * Category: Overview **Author:** Octav Labs Overview of the tokens in your portfolio * License: Pro * Category: Overview **Author:** Octav Labs Chart of your net worth over time * License: Lite * Category: Overview **Author:** Octav Labs A calendar widget with the variation of your net worth over time * License: Pro * Category: Overview **Author:** Octav Labs Daily timeline and changes on your portfolio over time * License: Pro * Category: Overview **Author:** Octav Labs A Bar Chart widget displaying daily net worth variations with color-coded bars * License: Pro * Category: Overview **Author:** Octav Labs Interactive treemap visualization of entire portfolio allocation, rectangles sized by USD value * License: Pro * Category: Overview **Author:** Octav Labs Overview of stablecoins in wallet and protocols * License: Pro * Category: Overview **Author:** Octav Labs Donut chart of portfolio net worth distribution across addresses with top 3 shown individually * License: Free * Category: Overview **Author:** Octav Labs Complete overview of all positions over time with name, balance, value, percentage, and distribution * License: Pro * Category: Overview **Author:** Octav Labs Track vesting and locked positions with unlock dates, countdown timers, and aggregated vesting schedules * License: Free * Category: Overview **Author:** Octav Labs Aggregate and monitor borrow positions with collateral, health rate, and liquidation risk indicators * License: Free * Category: Overview **Author:** Octav Labs Aggregate pending rewards awaiting claim across your portfolio with token, value, and source details * License: Free * Category: Overview **Author:** Octav Labs Proof of Reserve/NAV for portfolio with related on-chain events * License: Free * Category: Overview **Author:** Octav Labs Overview of daily snapshots showing date, addresses, net worth, variation, and percentage * License: Lite * Category: Overview **Author:** Octav Labs Configurable transaction data charts with multiple metrics and flexible filters including category, type, protocol, asset, and date range * License: Pro * Category: Analytics **Author:** Octav Labs Detailed portfolio views for any selected date with export options (PDF/CSV) showing wallet and protocol positions * License: Pro * Category: Analytics **Author:** Octav Labs Horizontal chain selector for wide spaces * License: Free * Category: Chain Filters **Author:** Octav Labs Vertical chain selector for narrow spaces * License: Free * Category: Chain Filters **Author:** Octav Labs Distribution of the protocols as a radar chart * License: Lite * Category: Protocols **Author:** Octav Labs List of your positions across different protocols * License: Free * Category: Protocols **Author:** Octav Labs List of your positions across different protocols using Debank style view * License: Free * Category: Protocols **Author:** Octav Labs Chart of value and yield for a specific selected position over time * License: Pro * Category: Protocols **Author:** Octav Labs Email notifications for DeFi positions such as health factor alerts, supporting Ethereum, Arbitrum, and Base * License: Pro * Category: Protocols **Author:** Octav Labs Distribution of the chains as a radar chart * License: Lite * Category: Chains **Author:** Octav Labs Distribution of the tokens in your wallet as a radar chart * License: Lite * Category: Wallet **Author:** Octav Labs Distribution of the tokens in your wallet * License: Free * Category: Wallet **Author:** Octav Labs Wallet risk and security analysis with scoring, security issues, and multi-address aggregate scoring * License: Free * Category: Wallet **Author:** Octav Labs Embedded CowSwap interface for direct token swapping from your dashboard * License: Free * Category: Wallet **Author:** Octav Labs Embed any Dune chart or dashboard with real-time data updates * License: Free * Category: Other *** ## Learn More Browse widgets by category and add them to your dashboard Create your own widgets with our developer SDK Learn about the home dashboard and widget management # Transactions Source: https://docs.octav.fi/docs/transactions View and analyze your transaction history Comprehensive transaction history and analytics across all your wallets and blockchains. Track every swap, transfer, bridge, and DeFi interaction in one unified view. Aggregate transactions across 90+ blockchains Filter by address, type, network, protocol, category, date, status, and notes Validate and categorize transactions for accounting *** ## Transaction List View The Transactions page provides a complete view of your blockchain activity with powerful search and filtering capabilities. ### Search & Filter Bar **Search transactions by:** * Transaction hash * Token name or symbol * Wallet address * ENS names * Protocol names Start typing to instantly filter your transaction list. **Filter by Wallet Address:** * Select one or multiple addresses * Search addresses by label or address * View transactions for specific wallets only * Address dropdown shows: * Wallet label * Shortened address (e.g., "0x89...473a") * Wallet icon/avatar **Transaction Types:** * **deposit** - Incoming transfers * **withdraw** - Outgoing transfers * **swap** - Token swaps on DEXs * **bridgein** - Incoming bridge transfers * **bridgeout** - Outgoing bridge transfers * And more protocol-specific types **Filter by Blockchain:** * Select specific chains (Ethereum, Base, Arbitrum, Binance, Solana, etc.) * Multi-select supported * Filter transactions to specific networks only **Filter by Protocol:** * Wallet transactions * DeFi protocols (Uniswap, Aave, Curve, etc.) * Bridge protocols (Debridge, Across V2, etc.) * Exchange protocols **Categorize Transactions:** * Custom categories for organization * Business vs personal * Multi-select categories * Custom labeling **Filter by Date:** * Pick a specific date range * Quick filters (Today, This Week, This Month, etc.) * Custom date picker for precise ranges **Octav Status:** * **To be reviewed** - Needs validation * **Validated** - Confirmed and categorized * **Flagged** - Requires attention * Filter by validation status **Filter by Notes:** * Transactions with notes * Transactions without notes * Search note content **Clear Filters** — Reset all filters with one click to return to full transaction list. *** ## Transaction Table The transaction table shows your complete transaction history with sortable columns. ### Table Columns **Transaction Timestamp:** * Full date and time (e.g., "Sep 18, 2025 2:57:11 PM") * Sortable by newest or oldest first * Timezone adjusted to your local time **Transaction Type Badge:** * Transaction type (deposit, withdraw, swap, bridgein, etc.) * Protocol icon and name (Wallet, Debridge, Across V2, etc.) * Chain icon (Base, Arbitrum, Linea, Binance, etc.) * Colored badges for quick identification **Common Types:** * **deposit** - Incoming transfers to your wallet * **withdraw** - Outgoing transfers from your wallet * **swap** - Token swaps on DEXs * **bridgein** - Incoming bridge transfers * **bridgeout** - Outgoing bridge transfers **Address Information:** * **From address** - Sender wallet (shortened format: 0x8245...4e6db0) * **To address** - Recipient wallet or labeled wallet name * Wallet labels if address is in your Address Book (e.g., "Hot Wallet 1") * Click address to copy or view details **Token Details:** * Token symbol and amount (e.g., "+1.0000" or "-0.0000") * USD value at time of transaction (e.g., "(\$0.00)") * Positive (+) for incoming, negative (-) for outgoing * Token icon displayed * **Unavailable** status if data not yet synced **Transaction Fees:** * Gas fees paid in native token * USD value of fees (e.g., "\<0.0001 (\$0.01)") * Fee icon displayed * Shows actual network fees paid **Validation Status:** * **To be reviewed** - Transaction needs validation * **Validated** - Transaction confirmed and categorized * Status badge with color coding * Click to change status **Organization:** * Custom categories applied to transaction * Personal notes attached to transaction * Tags for organization and tax purposes * Click to add or edit ### Transaction Actions Each transaction row has action buttons: Opens the transaction on the blockchain explorer (Etherscan, Basescan, etc.) **Additional Actions:** * View full transaction details * Add notes * Add categories * Mark as validated * Review later * Copy transaction hash ### Sort Options **Sort transactions by:** * **Newest** (default) - Most recent first * **Oldest** - Historical view * Date ascending/descending * Value high to low * Status **Columns Button** - Customize which columns to display in the table *** ## Transaction Details Panel Click any transaction to view comprehensive details in a side panel. ### Basic Information Full timestamp of when the transaction was confirmed (e.g., "Sep 18, 2025 at 2:57:11 PM") Blockchain where the transaction occurred with chain logo (Base, Arbitrum, etc.) Transaction type badge (DEPOSIT, WITHDRAW, SWAP, etc.) Protocol involved (Wallet, Debridge, Across V2, etc.) with protocol icon Full transaction hash with copy button (e.g., "0x9fd7...752e") Current validation status (To be reviewed, Validated, etc.) ### From and To **From Address:** * Sender wallet address (full or shortened) * Wallet label if in your Address Book * Click to copy address * View on blockchain explorer **To Address:** * Recipient wallet address * Wallet label if in your Address Book (e.g., "Hot Wallet 1") * Click to copy address * View on blockchain explorer ### Assets In/Out Table Detailed breakdown of all assets involved in the transaction: **Table Columns:** * **Asset**: Token icon, name, and symbol * **Quantity**: Amount transferred * **Price**: Price per token at time of transaction * **Total Value**: USD value of transferred amount * **From**: Source address (shortened) * **To**: Destination address (shortened) * **Categories**: Applied transaction categories * **Notes**: Personal notes field (add note button) **Assets In (X)** - Number of incoming assets shown in parentheses Each asset row shows: * Token icon and name * Positive/negative quantity indicator * Transaction flow direction (From → To) * Add note button for per-asset notes ### Transaction Actions Organize transactions with custom categories for tax and accounting purposes Add personal notes or memos to document transaction context Mark transaction for later review - appears in "To be reviewed" filter Mark transaction as validated - confirms accuracy and categorization *** ## Transaction Types ### Send, Receive, and Token Transfers **Send and Receive Transactions:** * Native currency transfers (ETH, SOL, MATIC, etc.) * Token transfers (ERC-20, SPL, BEP-20) * Multi-token transfers in single transaction * Cross-chain transfers via bridges **NFT Movement:** * ERC-721 transfers (single NFTs) * ERC-1155 transfers (multi-token) * Solana NFT transfers (Metaplex) * Batch NFT transfers ### Decentralized Exchange Transactions **DEX Swap Tracking:** * Uniswap, SushiSwap, PancakeSwap * 1inch, Paraswap, Matcha aggregators * Multi-hop swaps (token A → token B → token C) * Limit orders and advanced order types * Cross-chain swaps and bridge transactions * Slippage and price impact display **Swap Details Shown:** * Tokens in/out with amounts * Exchange rate at execution * Price impact percentage * Slippage tolerance * DEX router used * Gas fees in native token and USD ### DeFi Protocol Interactions **Staking Operations:** * Stake tokens * Unstake/withdraw * Claim rewards * Re-stake (compound) **LP Provision:** * Add liquidity * Remove liquidity * Collect fees * Rebalance positions **Lending Protocols:** * Supply assets * Borrow against collateral * Repay loans * Liquidations **Farming Activity:** * Deposit to farms * Harvest rewards * Compound earnings * Exit positions ### NFT Marketplace Interactions **Supported NFT Operations:** * **Minting** - Primary and secondary mints * **Buying** - Marketplace purchases (OpenSea, Blur, Magic Eden) * **Selling** - NFT sales with royalty tracking * **Listing/Delisting** - Marketplace listing changes * **Transferring** - Gifting and moving NFTs * **Bidding** - Offer placement and acceptance **NFT Transaction Details:** * Collection name and token ID * Rarity rank (if available) * Floor price at time of transaction * Marketplace and royalty fees * Seller and buyer addresses ### Smart Contract Calls **Advanced Interactions:** * Direct smart contract calls * Token approvals (ERC-20, ERC-721) * Multi-sig operations and signatures * Contract deployments * Governance voting * Protocol-specific functions **Contract Details:** * Contract address with verified badge * Contract name (if verified) * Function called * Input parameters * Event logs * Internal transactions *** ## Search & Filter **Combine Filters** — Use multiple filters simultaneously for precise transaction discovery. All filters work together with AND logic. ### Powerful Text Search Search transactions by any of these fields: Paste the full or partial transaction hash to find specific transactions * Token symbol (ETH, USDC, MATIC) * Token name (Ethereum, USD Coin) * Token contract address * Wallet address (full or partial) * ENS name (vitalik.eth) * Smart contract address * Address book labels * Contract name (Uniswap V3 Router) * Protocol name (Aave, Compound) * Contract address ### Advanced Filtering Options **Transaction Type Filter:** * Select one or multiple types * Filter by category (DeFi, NFT, Transfer) * Exclude specific types * Quick filters (Swaps Only, Transfers Only) **Date Range Options:** * Today, Yesterday * Last 7/30/90 days * Custom date range picker * Specific month/year * Quarter selection (Q1, Q2, Q3, Q4) **Blockchain Filter:** * Single chain selection * Multi-chain selection (hold Ctrl/Cmd) * Cross-chain view * Show only specific L1s or L2s **Wallet Selection:** * View single wallet * Multiple wallet selection * Portfolio-level view * Wallet group filtering **Value Thresholds:** * Minimum value (e.g., greater than \$100) * Maximum value (e.g., less than \$10,000) * Specific value ranges * High-value transactions only **Transaction Status:** * Confirmed ✅ * Pending ⏳ * Failed ❌ * All statuses **Token Filter:** * Filter by specific token * Multiple token selection * Token categories (stablecoins, governance, etc.) * Exclude tokens ### Sorting Options Organize your transaction list: * **Newest First** (default) - Most recent transactions * **Oldest First** - Historical view * **Highest Value** - Largest transactions first * **Lowest Value** - Smallest transactions first * **Most Gas** - Highest fees paid * **By Token** - Alphabetical by token symbol *** ## Transaction Details ### Detailed Transaction View Click any transaction to see comprehensive details: **Transaction Essentials:** * Full transaction hash (copy button) * Block number with confirmations * Timestamp (date and time) * Transaction status badge * Chain/network **Cost Analysis:** * Gas used vs gas limit * Gas price (gwei) * Total fee in native token * Total fee in USD * Fee tier indicator (low/medium/high) **Advanced Info:** * Nonce value * Position in block * Transaction type (legacy, EIP-1559) * Max priority fee * Max fee per gas **Contract Data:** * Function called * Input parameters * Decoded data (if verified) * Raw hex data ### Visual Asset Flow **Interactive Flow Diagram:** Clear visualization showing: * Tokens sent (red arrows) * Tokens received (green arrows) * Multi-step swap paths * Complex DeFi interactions * LP token movements **For Swap Transactions:** ``` 1.5 ETH → [Uniswap V3] → 2,850 USDC ``` **For Complex DeFi:** ``` 100 USDC → [Curve] → LP Token → [Convex] → Staked Position ``` ### Transaction Grouping **Grouped Related Transactions:** * Approval + Swap (grouped together) * Multi-step operations * Sequential DeFi interactions * Multi-sig transaction stages * Failed transaction attempts **Internal Transactions:** * View all internal calls * Token transfers within transaction * Multi-send operations * Contract-to-contract calls ### Blockchain Explorers Quick links to view transactions on chain-specific explorers: Etherscan, Blockscout Solscan, Solana Explorer Basescan PolygonScan Arbiscan Optimistic Etherscan *** ## Transaction Tags **Organization** — Add custom tags to transactions for better categorization and analysis. ### Tag Your Transactions **Pre-Built Tag Categories:** * Gift sent/received * Donation * Lost funds * Stolen/compromised * Trading * Long-term hold * Testing * Migration * Create unlimited custom tags * Color coding * Icon selection * Description notes ### Manage Tags **Tag Operations:** * Create custom tags with colors and icons * Tag single transactions or bulk tag * Filter transaction list by tags * Generate tag-based reports * Export transactions by tag * Share tags across team (Team plan) **Auto-Tagging Rules:** Set up automatic tagging based on: * Transaction type (auto-tag all swaps) * Counterparty address * Value threshold (tag transactions over \$10k) * Specific tokens involved * Smart contract interactions ### Reporting by Tags Use tags to generate focused reports: * Business expense summaries * Investment performance by tag * Protocol-specific analytics * Transaction categorization analysis *** ## Analytics ### Transaction Analytics Dashboard **Key Metrics:** * Total transaction count * Transaction volume (USD) * Average transaction size * Transaction frequency * Total gas spent (all chains) * Average gas per transaction * Most expensive transactions * Gas optimization score * Transactions per chain * Volume per chain * Gas costs by chain * Chain activity trends * Percentage by type * Most common interactions * DeFi vs NFT vs Transfers * Growth trends by type ### Visual Analytics **Transaction Type Distribution:** * Pie or donut chart * Bar chart view * Time-series by type * Hover for details **Multi-Chain Activity:** * Activity across all chains * Chain comparison over time * Cost analysis per chain * Network preference trends **Gas Spending Insights:** * Total gas spent over time * Gas breakdown by chain * Average gas price trends * Optimization opportunities **Activity Calendar:** * Transaction heatmap * Daily/weekly/monthly patterns * Busiest periods identification * Activity streaks ### Top Interactions See your most frequent transaction partners: **Metrics Shown:** * Most frequent addresses (by count) * Highest value counterparties (by USD) * Protocol usage statistics * Exchange deposit frequency * Smart contract interactions **Insights Provided:** * Your most-used protocols * Favorite DEXs * Regular payment recipients * DeFi position concentrations *** ## Export Transactions ### Available Export Formats **Spreadsheet Compatible:** * All transaction data included * Custom column selection * Date range filtering * Tag-based exports * Compatible with Excel, Google Sheets * UTF-8 encoding for all characters **Professional Document:** * Formatted transaction report * Summary statistics at top * Charts and visualizations * Custom branding (Pro plan) * Page numbers and table of contents * Printable format **Developer Friendly:** * Raw transaction data * API-compatible format * Bulk data export * Programmatic access * Nested structure with full details ### Customize Your Export **Export Options:** * Select date range * Choose specific wallets * Filter by transaction types * Include/exclude failed transactions * Select chains to include * Choose column fields * Set timezone for timestamps **Scheduled Exports (Pro/Team):** * Daily transaction exports * Weekly summaries * Monthly reports * Automatic email delivery * Cloud storage integration *** ## Real-Time Updates **Live Monitoring** — Octav uses WebSocket connections for instant transaction notifications. New transactions appear automatically without refreshing. ### Automatic Transaction Updates **Real-Time Features:** * New transactions appear instantly * WebSocket connection for zero-latency * Pending transaction monitoring * Confirmation count updates * Status changes reflected immediately **Visual Indicators:** * 🟢 New transaction highlight * ⏳ Pending spinner animation * ✅ Confirmed checkmark * ❌ Failed red badge ### Transaction Alerts Get notified about important transactions: Alert when you receive tokens or NFTs Warning for transactions above your threshold Immediate notification of failed transactions Track confirmations for high-value transactions Alert on swaps with high slippage or price impact ### Configure Notifications **Notification Preferences:** * Enable/disable real-time updates * Set notification thresholds * Choose notification methods (email, push, browser) * Configure quiet hours * Per-wallet notification settings * Alert sound options *** ## Transaction Management **Perform Actions on Multiple Transactions:** Select multiple transactions and: * Apply tags in bulk * Export selected transactions * Mark as reviewed * Hide from view * Create transaction groups * Bulk categorization **Selection Tools:** * Select all on page * Select all matching filter * Range selection (Shift+Click) * Invert selection **Add Context to Transactions:** * Custom notes per transaction * Attach receipts or documents * Link related transactions * Internal memo field * Markdown formatting support * Team-shared notes (Team plan) **Use Cases:** * Document business purposes * Trading strategy notes * Error documentation * Transaction context notes **Transaction Privacy:** * Hide specific transactions from view * Archive old transactions * Bulk hide operations * Unhide anytime * Hidden transactions excluded from reports * Hidden count badge *** ## Privacy & Security **Read-Only Access** — Octav never stores your private keys. We only read public blockchain data. You cannot sign transactions through Octav. **Your Transaction Data is Secure:** * View-only access to blockchain data * No transaction signing capability * Encrypted storage of tags and notes * Private notes never shared publicly * Optional local-only note storage * GDPR and CCPA compliant **Control Your Privacy:** * Label addresses privately * Hide sensitive transactions * Archive old activity * Bulk privacy operations * Address book labels remain private * Optional public profile (coming soon) *** ## Advanced Features **Aggregate or Separate:** * Combined view across all wallets * Per-wallet breakdown available * Wallet comparison mode * Portfolio-level analysis * Wallet group filtering **Bridge and Cross-Chain Transactions:** * Automatic bridge transaction detection * Cross-chain swap tracking * Wrapped token movements * Multi-chain protocol tracking * Bridge fee analysis **Identify MEV Activity:** * Sandwich attack detection * Front-running identification * Liquidation tracking * Arbitrage opportunity analysis * MEV impact on your transactions **Historical Analysis:** * Replay historical transactions * What-if analysis * Price impact at different times * Alternative routing comparison * Historical gas price context *** ## Performance **Optimized for Large Datasets** — Octav efficiently handles wallets with thousands of transactions using virtual scrolling and smart caching. **Performance Features:** * Paginated loading for large transaction sets * Infinite scroll for seamless browsing * Virtual scrolling for smooth performance * Background data syncing * Smart caching to reduce load times * Progressive loading of transaction details **Sync Status:** * Last sync timestamp displayed * Manual refresh button available * Auto-sync settings configurable * Sync progress indicator * Per-chain sync status *** ## Learn More Create financial reports and analytics Programmatic access to transactions Label and organize addresses # DeFi Dashboard Widgets Source: https://docs.octav.fi/docs/widget-explorer Browse and customise widgets to build a DeFi dashboard with real-time data on wallet balances, protocol positions and portfolio performance. Explore powerful widgets built for your needs. Discover and customize widgets to build your perfect DeFi dashboard with real-time data and insights. Core widgets available to everyone Advanced widgets with Lite and Pro licenses Add widgets to your dashboard instantly *** ## How Widgets & Licenses Work **Two Ways to Access Widgets** — Buy individual widgets or purchase a license package that includes multiple widgets. ### Access Options **Buy Widgets One by One** Purchase only the widgets you need according to your specific requirements: * Pay for each widget separately * Choose exactly what you want * No commitment required * Add more widgets anytime **Pricing:** * **Free** - \$0 (Core portfolio widgets) * **Lite** - \$10-50 per widget * **Pro** - \$25-50 per widget **Purchase a License Bundle** Get access to a set of widgets with a single license: **\$0** Access to all free widgets **\$149 / year** Includes all Lite-tier widgets per address **\$499 / year** 🔥 Full access to all Pro widgets per address **Licenses Are Cheaper** — A license package is always cheaper than buying all widgets individually. **Important License Terms:** * **Per address** - Each license applies to one wallet address * **Per year** - Annual billing * Renewal required after expiration * Includes all widgets in that tier *** ## Widget Categories **Organized by Purpose** — Widgets are categorized to help you find exactly what you need for your dashboard. **Portfolio Summary & Analytics** High-level portfolio metrics and visualizations: * Portfolio summary (net worth, breakdown) * Token overview * Networth charts (line, calendar, bar chart) * Portfolio timeline * Daily variations **Use Cases:** * Track total portfolio value * Monitor portfolio performance over time * Visualize net worth trends * Daily variation tracking **Blockchain Selectors** Filter and display data by specific chains: * Horizontal chain selector (wide layouts) * Vertical chain selector (narrow spaces) **Use Cases:** * Filter dashboard by blockchain * Focus on specific chains * Quick chain switching * Multi-chain organization **DeFi Protocol Tracking** Monitor your DeFi positions and protocol exposure: * Protocol radar chart * Protocol grid view * Debank-style protocol list **Use Cases:** * Track DeFi positions * Monitor protocol exposure * View position values * Protocol distribution **Chain Distribution** Visualize blockchain allocation: * Chains radar chart **Use Cases:** * See which chains hold most value * Track cross-chain exposure * Visualize chain distribution **Wallet-Level Data** Token holdings and wallet-specific information: * Tokens radar chart * Wallet tokens chart **Use Cases:** * Token distribution visualization * Wallet balance breakdown * Asset allocation analysis *** ## Adding Widgets to Your Dashboard From your dashboard, click the board menu and select **Add Widget** Search or filter widgets by: * **Search bar** - Find widgets by name * **Category** - Filter by Overview, Chains, Protocols, Wallet, Chain Filters * **License** - Filter by Free, Lite, or Pro Click on a widget to see its details: * Widget name and description * Author (Octav Labs) * Price * License requirement * Category Click **Add to Dashboard** to install the widget * Free widgets add instantly * Paid widgets require purchase or valid license Drag to position the widget and resize as needed **Try Before You Buy** — Preview all widgets to see if they fit your needs before purchasing. *** ## Widget Search & Filters **Search Widgets by Name** Use the search bar at the top of Widget Manager: * Type widget name or keywords * Results filter instantly * Case-insensitive search * Partial matching supported **Example Searches:** * "portfolio" - Shows Portfolio Summary, Portfolio Timeline, etc. * "chart" - Shows all chart-type widgets * "chain" - Shows chain-related widgets * "networth" - Shows net worth widgets **Filter by Category** Select from available categories: * **All** - Show all widgets * **Overview** - Portfolio summaries and analytics * **Chain Filters** - Chain selection widgets * **Protocols** - DeFi protocol widgets * **Chains** - Chain distribution widgets * **Wallet** - Wallet-specific widgets **Filter by License Tier** Filter widgets by license requirement: * **All** - Show all widgets regardless of license * **Free** - Show only free widgets * **Lite** - Show Lite-tier widgets (\$10-50) * **Pro** - Show Pro-tier widgets (\$25-50) **Check Your License** — Your active licenses are displayed in the Widget Manager. *** ## Managing Active Widgets **See All Installed Widgets** The **Active Widgets** tab in Widget Manager shows: * All widgets currently on your dashboard * Widget name and category * Current board location * Quick access to settings **Actions:** * Remove widgets you no longer need * Jump to widget location on dashboard * View widget settings * Reorder widgets in list **Widget Settings** Each widget has configurable options: * Display preferences * Data filters * Time ranges * Color schemes * Update frequency Access settings by clicking the gear icon on any widget. **Delete from Dashboard** Remove widgets you don't use: * Click remove in Widget Manager * Or click X button on widget itself * Widgets can be re-added anytime * Removal doesn't affect license *** ## License Management **Per-Address Licenses** — Each license is valid for 1 wallet address for 1 year. ### License Tiers **Free Access** Available to all users: * Portfolio Summary * Chain Selectors * Wallet Tokens Chart * Protocol Grid & List * No time limit * No credit card required **Lite License** **\$149 / year** per address Unlock enhanced widgets: * Everything in Free * Chain Radar Chart * Protocol Radar Chart * Net Worth Over Time * Token Radar Chart * Token Overview * Daily Snapshot (1/day, 1 year) **Terms:** * Valid for 1 address * Billed yearly * Auto-renewal optional **Pro License** 🔥 Most Popular **\$499 / year** per address Full access to all features: * Transactions (TXs) * Everything in Lite * Portfolio Timeline * Net Worth Calendar Variation * Net Worth Bar Chart Variation * Daily Snapshot (unlimited) * Reports * CSV Export * PDF Export **Terms:** * Valid for 1 address * Billed yearly * Auto-renewal optional **Enterprise** **Custom Pricing** Tailored plan and support: * Everything in Pro * Custom limits and features * SLA & support * Dedicated account manager * Custom integrations [Contact Sales →](/docs/contact-us) ### Purchasing Licenses Select Lite or Pro license based on widgets you need Choose which wallet address the license applies to Complete purchase with credits or payment method Licensed widgets are now available in Widget Manager **Compare Costs** — Calculate if individual widget purchases or a license package is better for your needs. *** ## Widget Customization **Adjust Widget Size** * Hover over widget corners * Drag to resize * Snap to grid * Minimum/maximum sizes enforced **Reposition on Dashboard** * Click and hold widget header * Drag to new position * Other widgets adjust automatically * Layout saves automatically **Widget-Specific Options** Each widget has unique settings: * Chart time ranges * Display currencies * Chain filters * Color preferences * Data thresholds *** ## Need Help? Detailed documentation for each widget Learn about dashboard boards and layouts Get help from our community # Examples Source: https://docs.octav.fi/mcp/examples Real-world AI assistant conversations powered by the Octav MCP server See what happens when you connect an AI assistant to the Octav MCP server. Each example shows a human prompt and the rich analysis the AI produces after calling tools behind the scenes. These examples use Claude Desktop with the Octav MCP server configured. The same conversations work in Cursor, VS Code Copilot, or any MCP-compatible client. *** ## 1. Portfolio Health Check Ask your AI to analyze a wallet's diversification and flag risks. ```text theme={null} Analyze the portfolio health for 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68. Give me asset allocation, diversification assessment, and any red flags. ``` ```text theme={null} Portfolio Health Report — 0x742d...2bD68 Total Value: $184,230.41 Asset Allocation ┌──────────────┬──────────┬────────┐ │ Asset │ Value │ Share │ ├──────────────┼──────────┼────────┤ │ ETH │ $82,903 │ 45.0% │ │ USDC │ $38,088 │ 20.7% │ │ WBTC │ $27,634 │ 15.0% │ │ ARB │ $14,738 │ 8.0% │ │ AAVE (staked)│ $11,053 │ 6.0% │ │ Other (7) │ $9,814 │ 5.3% │ └──────────────┴──────────┴────────┘ DeFi Positions • Aave V3 (Ethereum): $27,400 supplied (USDC + ETH), $8,200 borrowed (USDC) Health factor: 2.14 — healthy • Uniswap V3 (Arbitrum): ETH/USDC LP — $12,300, in range Diversification Assessment: MODERATE ✓ Good stablecoin buffer (20.7%) ✓ Blue-chip heavy (ETH + WBTC = 60%) ✓ Active DeFi yield generation ⚠ 45% concentration in ETH — consider if this aligns with your risk tolerance ⚠ 8% in ARB — single L2 token exposure Red Flags ⚠ No Solana or non-EVM diversification ⚠ Aave borrow position has no stop-loss — a 50% ETH drop would bring health factor to ~1.07 (liquidation risk) Recommendation: Consider reducing ETH concentration to <35% and setting up Aave alerts for health factor below 1.5. ``` *** ## 2. Whale Wallet Research Research an unfamiliar wallet's recent activity to understand their strategy. ```text theme={null} Research wallet 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B. Show me their last 30 days of activity — what are they doing? ``` ```text theme={null} Wallet Research — 0xAb58...aeC9B (30-Day Activity) Overview • Current value: $2,847,102 • Transactions (last 30d): 47 total • Most active chains: Ethereum (31), Arbitrum (12), Base (4) Notable Activity Swaps (18 transactions) • Sold 400 ETH → USDC across 5 transactions ($1.34M total) • Bought 12.5 WBTC using USDC ($812K) • Pattern: Systematic ETH → BTC rotation over 2 weeks DeFi (14 transactions) • Deposited $500K USDC into Aave V3 (Ethereum) — earning 4.2% APY • Added liquidity to Uniswap V3 WBTC/ETH pool on Arbitrum ($340K) • Claimed $8,400 in ARB rewards Transfers (15 transactions) • 3 large transfers to a secondary wallet (total $420K in stablecoins) • Regular small transfers from Coinbase ($2K-5K range) Pattern Analysis This wallet is executing a deliberate rotation from ETH to BTC while parking profits in stablecoin yield. The systematic selling (not panic dumps) and DeFi deployment suggest an experienced operator de-risking ETH exposure while maintaining market participation through BTC and LP positions. ``` *** ## 3. Historical Performance Analysis Compare portfolio value across multiple time points to track performance. ```text theme={null} Compare my portfolio at 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 across three dates: 2024-01-01, 2024-07-01, and 2025-01-01. Show me the performance timeline and what drove the changes. ``` ```text theme={null} Performance Timeline — 0x742d...2bD68 ┌────────────┬────────────┬──────────┬────────────┐ │ Date │ NAV │ Change │ Cumulative │ ├────────────┼────────────┼──────────┼────────────┤ │ 2024-01-01 │ $98,420 │ — │ — │ │ 2024-07-01 │ $142,870 │ +$44,450 │ +45.2% │ │ 2025-01-01 │ $184,230 │ +$41,360 │ +87.2% │ └────────────┴────────────┴──────────┴────────────┘ Full-year return: +87.2% ($85,810) Key Drivers H1 2024 (+45.2%) • ETH rally from $2,350 → $3,450 drove bulk of gains • WBTC position appreciated ~40% with BTC's ETF momentum • DeFi yields contributed ~$3,200 H2 2024 (+29.0%) • ETH continued to $3,800, but gains moderated • ARB position added in Q3 — up 65% by year-end • Aave lending income: ~$4,100 vs. Benchmarks (2024 full year) • ETH: +62% — you outperformed by 25.2pp • BTC: +121% — you underperformed by 33.8pp • 60/40 Crypto Index: +78% — you outperformed by 9.2pp The outperformance vs ETH came from DeFi yield and the well-timed ARB entry. Underperformance vs BTC reflects lower BTC allocation (15% vs pure BTC hold). ``` *** ## 4. DeFi Position Deep Dive Get a detailed breakdown of token distribution across protocols. ```text theme={null} Give me a detailed token distribution for 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68. Break it down by protocol and include recommendations. ``` ```text theme={null} Token Distribution Deep Dive — 0x742d...2bD68 Snapshot date: 2025-01-15 By Token (Top Holdings) ┌──────────┬────────────┬────────┬──────────────────────────────┐ │ Token │ Value │ Share │ Location │ ├──────────┼────────────┼────────┼──────────────────────────────┤ │ ETH │ $82,903 │ 45.0% │ Wallet: $43,200 │ │ │ │ │ Aave V3: $27,400 (supplied) │ │ │ │ │ Uniswap V3: $12,303 (LP) │ ├──────────┼────────────┼────────┼──────────────────────────────┤ │ USDC │ $38,088 │ 20.7% │ Wallet: $10,088 │ │ │ │ │ Aave V3: $28,000 (supplied) │ ├──────────┼────────────┼────────┼──────────────────────────────┤ │ WBTC │ $27,634 │ 15.0% │ Wallet: $27,634 │ ├──────────┼────────────┼────────┼──────────────────────────────┤ │ ARB │ $14,738 │ 8.0% │ Wallet: $14,738 │ ├──────────┼────────────┼────────┼──────────────────────────────┤ │ AAVE │ $11,053 │ 6.0% │ Staked (stkAAVE): $11,053 │ ├──────────┼────────────┼────────┼──────────────────────────────┤ │ Other │ $9,814 │ 5.3% │ Various (7 tokens) │ └──────────┴────────────┴────────┴──────────────────────────────┘ By Protocol ┌────────────────────┬────────────┬────────┐ │ Protocol │ Value │ Share │ ├────────────────────┼────────────┼────────┤ │ Wallet (no proto.) │ $95,660 │ 51.9% │ │ Aave V3 │ $55,400 │ 30.1% │ │ Uniswap V3 │ $12,303 │ 6.7% │ │ Aave Staking │ $11,053 │ 6.0% │ │ Other DeFi │ $9,814 │ 5.3% │ └────────────────────┴────────────┴────────┘ Recommendations 1. ETH is spread across 3 locations — consider consolidating if you don't need the Aave collateral 2. WBTC is 100% idle in wallet — could earn 0.5-2% APY in lending 3. ARB is also idle — Arbitrum has native staking options worth exploring 4. Strong Aave presence (36.1% in Aave ecosystem) — monitor protocol risk concentration ``` *** ## 5. Airdrop & Polymarket Check Use two specialized tools in one conversation. ```text theme={null} Two things: 1. Check if my Solana wallet 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU is eligible for any airdrops 2. Show my Polymarket positions for 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 ``` ```text theme={null} 1. Airdrop Eligibility — 7xKXtg...gAsU (Solana) ┌──────────────────┬────────────┬──────────────────────────┐ │ Program │ Status │ Details │ ├──────────────────┼────────────┼──────────────────────────┤ │ Jupiter (JUP) │ Eligible │ 847 JUP (~$512) │ │ Tensor (TNSR) │ Claimed │ 1,200 TNSR (claimed Apr) │ │ Parcl (PRCL) │ Eligible │ 340 PRCL (~$89) │ │ Drift (DRIFT) │ Not elig. │ Min volume not met │ └──────────────────┴────────────┴──────────────────────────┘ Action needed: 2 unclaimed airdrops worth ~$601 total. --- 2. Polymarket Positions — 0x742d...2bD68 ┌────────────────────────────┬────────┬──────────┬──────────┐ │ Market │ Side │ Shares │ P&L │ ├────────────────────────────┼────────┼──────────┼──────────┤ │ ETH above $4k by March? │ Yes │ 500 │ +$142 │ │ Fed rate cut in Q1? │ No │ 1,200 │ -$86 │ │ Solana flips BNB by mcap? │ Yes │ 300 │ +$67 │ └────────────────────────────┴────────┴──────────┴──────────┘ Polymarket P&L: +$123 net (unrealized) Summary: You have $601 in unclaimed airdrops on Solana and $123 in unrealized Polymarket gains. The Jupiter airdrop is the largest — I'd recommend claiming that first. ``` *** ## 6. Multi-Wallet Treasury Overview Aggregate multiple wallets across chains into one consolidated view. ```text theme={null} Give me a consolidated treasury report for these 3 wallets: - 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68 (operations) - 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B (cold storage) - 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU (solana treasury) Include chain exposure, top holdings, protocol distribution, and risk assessment. ``` ```text theme={null} Consolidated Treasury Report 3 wallets | 4 chains | Updated 2025-01-15 Total Treasury Value: $3,194,562 Wallet Breakdown ┌──────────────┬──────────────┬────────┐ │ Wallet │ Value │ Share │ ├──────────────┼──────────────┼────────┤ │ Cold Storage │ $2,847,102 │ 89.1% │ │ Operations │ $184,230 │ 5.8% │ │ Solana │ $163,230 │ 5.1% │ └──────────────┴──────────────┴────────┘ Chain Exposure ┌──────────────┬──────────────┬────────┐ │ Chain │ Value │ Share │ ├──────────────┼──────────────┼────────┤ │ Ethereum │ $2,640,800 │ 82.7% │ │ Arbitrum │ $247,432 │ 7.7% │ │ Solana │ $163,230 │ 5.1% │ │ Base │ $143,100 │ 4.5% │ └──────────────┴──────────────┴────────┘ Top 5 Holdings (Aggregated) ┌──────────┬──────────────┬────────┐ │ Token │ Value │ Share │ ├──────────┼──────────────┼────────┤ │ ETH │ $1,482,300 │ 46.4% │ │ USDC │ $738,200 │ 23.1% │ │ WBTC │ $512,430 │ 16.0% │ │ SOL │ $141,800 │ 4.4% │ │ ARB │ $98,200 │ 3.1% │ └──────────┴──────────────┴────────┘ Protocol Distribution ┌──────────────────┬──────────────┬────────┐ │ Protocol │ Value │ Share │ ├──────────────────┼──────────────┼────────┤ │ Wallet (idle) │ $2,206,400 │ 69.1% │ │ Aave V3 │ $582,800 │ 18.2% │ │ Uniswap V3 │ $253,200 │ 7.9% │ │ Marinade │ $98,400 │ 3.1% │ │ Other │ $53,762 │ 1.7% │ └──────────────────┴──────────────┴────────┘ Risk Assessment ✓ Strong stablecoin position (23.1%) — good liquidity buffer ✓ Blue-chip dominated (ETH + BTC + SOL = 66.8%) ✓ DeFi positions in battle-tested protocols ⚠ HIGH: 89.1% of treasury in a single wallet (cold storage) → Consider distributing across additional cold wallets ⚠ MEDIUM: 82.7% on Ethereum — high chain concentration → Evaluate L2 migration for operational funds ⚠ MEDIUM: 69.1% of assets are idle (not earning yield) → $2.2M could be deployed in low-risk lending ⚠ LOW: Solana treasury is 5.1% — acceptable for diversification ``` *** ## What's Happening Behind the Scenes When you ask a question, the AI assistant automatically selects and calls the right Octav MCP tools. You never need to specify tool names or parameters — just ask in natural language. For example, the **Multi-Wallet Treasury** example above triggered these tool calls: 1. `octav_get_portfolio` — fetched full holdings for all 3 wallets in one call 2. `octav_get_nav` — got the total NAV for each wallet 3. `octav_get_token_overview` — pulled aggregated token distribution The AI then synthesized these raw data responses into the formatted treasury report with risk analysis. Each tool call costs 1 credit per address — so this full report used \~9 credits total. *** ## Next Steps Get set up in under a minute All 14 tools with parameters Sign up at the developer portal # MCP Server Source: https://docs.octav.fi/mcp/overview Connect AI assistants like Claude Desktop, Cursor, and VS Code to the Octav API through the Model Context Protocol The official Octav MCP server lets AI assistants query portfolio data, transaction history, net worth, and historical snapshots across 20+ blockchains — directly from your conversation. **Quick Install** — Run the MCP server with no installation required: ```bash theme={null} npx octav-api-mcp ``` *** ## Features Multi-chain portfolio aggregation * Wallet balances and DeFi positions * Net worth in multiple currencies * Token distribution overview Complete transaction records * Filter by chain, type, and date range * Manual sync triggers * Up to 250 results per query Time-series portfolio data * Point-in-time snapshots * Automatic daily subscriptions * Performance tracking Additional capabilities * Solana airdrop eligibility * Polymarket positions * x402 agent payment support *** ## Installation No installation required — run directly: ```bash theme={null} npx octav-api-mcp ``` Install globally: ```bash theme={null} npm install -g octav-api-mcp ``` Then run: ```bash theme={null} octav-api-mcp ``` Install globally: ```bash theme={null} pnpm add -g octav-api-mcp ``` Then run: ```bash theme={null} octav-api-mcp ``` *** ## Configuration You'll need an Octav API key. Get one at [data.octav.fi](https://data.octav.fi/). ### Claude Desktop Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` Edit `%APPDATA%\Claude\claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` ### Cursor Go to **Cursor Settings > MCP** and add a new global MCP server: ```json theme={null} { "mcpServers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } ``` ### VS Code (Copilot) Add to your VS Code `settings.json`: ```json theme={null} { "mcp": { "servers": { "octav": { "command": "npx", "args": ["-y", "octav-api-mcp"], "env": { "OCTAV_API_KEY": "your-api-key-here" } } } } } ``` ### Claude Code Add the MCP server to your project with the Claude Code CLI: ```bash theme={null} claude mcp add octav -- npx -y octav-api-mcp ``` Then set the API key as an environment variable or pass it via your shell profile. *** ## Available Tools The MCP server exposes 14 tools organized by category. Most tools cost **1 credit per address**. ### Portfolio & Holdings **Complete portfolio including wallet holdings and DeFi positions across 20+ blockchains.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | -------------------------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses (EVM `0x...` or Solana base58). Max 10. | **Cost:** 1 credit per address ``` "Show me the full portfolio for 0x123..." ``` **Wallet token balances only (excludes DeFi protocol positions).** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** 1 credit per address ``` "What tokens does 0x123... hold?" ``` **Total net worth in a specified currency.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `currency` | string | No | Fiat: `USD`, `EUR`, `CAD`, `AED`, `CHF`, `SGD`. Crypto: `ETH`, `SOL`, `cbBTC`, `EURC`, `BNB`. Defaults to `USD`. | **Cost:** 1 credit per address ``` "What's the total net worth of 0x123... in EUR?" ``` **Aggregated token distribution across all chains.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `date` | string | Yes | Snapshot date in `YYYY-MM-DD` format. | **Cost:** 1 credit per address ``` "Show me the token breakdown for 0x123... as of 2025-01-15" ``` ### Transactions **Query transaction history with filtering and pagination.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | --------------------------------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `chain` | string | No | Filter by chain (e.g., `ethereum`, `solana`). | | `type` | string | No | Filter by type (e.g., `transfer`, `swap`, `stake`). | | `startDate` | string | No | Start date (`YYYY-MM-DD`). | | `endDate` | string | No | End date (`YYYY-MM-DD`). | | `limit` | number | No | Results per request (1–250, default 50). | | `offset` | number | No | Pagination offset (default 0). | **Cost:** 1 credit per address ``` "Show me the last 20 swaps on Ethereum for 0x123..." ``` **Trigger manual transaction synchronization for immediate indexing.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** 1 credit per address ``` "Sync the latest transactions for 0x123..." ``` ### Historical & Snapshots **Portfolio snapshot for a specific past date.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ---------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `date` | string | Yes | Date in `YYYY-MM-DD` format. | **Cost:** 1 credit per address ``` "What was my portfolio worth on 2024-12-31?" ``` **Subscribe to automatic daily portfolio snapshots.** | Parameter | Type | Required | Description | | ------------- | --------- | -------- | ------------------------------------ | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | | `description` | string | No | Optional label for the subscription. | **Cost:** 1,200 credits ``` "Subscribe 0x123... to daily snapshots" ``` ### Metadata **Check sync status of addresses across all chains.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** Free ``` "Is 0x123... fully synced?" ``` **Check API credit balance and usage.** No parameters required. **Cost:** Free ``` "How many API credits do I have left?" ``` ### Specialized **Check airdrop eligibility for a Solana address.** | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------- | | `address` | string | Yes | Solana wallet address (base58). | **Cost:** 1 credit ``` "Check airdrop eligibility for my Solana wallet" ``` **Get Polymarket prediction market positions.** | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------------------- | | `address` | string | Yes | Ethereum wallet address (`0x...`). | **Cost:** 1 credit ``` "Show my Polymarket positions for 0x123..." ``` **Wallet holdings via x402 payment protocol for AI agents with automatic payment.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** Paid via HTTP 402 payment protocol **Full portfolio via x402 payment protocol for AI agents with automatic payment.** | Parameter | Type | Required | Description | | ----------- | --------- | -------- | ------------------------- | | `addresses` | string\[] | Yes | Wallet addresses. Max 10. | **Cost:** Paid via HTTP 402 payment protocol Prefer the API-key tools above. Reach for `octav_agent_*` only when the user explicitly asks for x402, or when the agent has a funded wallet and no API key. They cover only wallet, portfolio, nav, status, and chains — **transaction history is not available over x402**. See [Agent Endpoints (x402)](/api/endpoints/agent-x402). *** ## Example Prompts Once the MCP server is connected, you can ask your AI assistant questions like: ``` "What's the total value of my portfolio at 0xABC...?" ``` ``` "Show me all swap transactions on Arbitrum for the past month" ``` ``` "Compare my portfolio value today vs. 30 days ago" ``` ``` "Check if my Solana wallet is eligible for any airdrops" ``` ``` "How many API credits do I have left?" ``` *** ## Error Handling The MCP server returns clear error messages for common issues: | Error | Cause | Solution | | ---------------------- | ------------------------------------------ | -------------------------------------------------------- | | Invalid address format | Address doesn't match EVM or Solana format | Use `0x...` (40 hex chars) for EVM or base58 for Solana | | Authentication failure | Missing or invalid API key | Check your `OCTAV_API_KEY` environment variable | | Insufficient credits | Not enough credits for the request | Purchase more at [data.octav.fi](https://data.octav.fi/) | | Rate limit exceeded | Too many requests per minute | Wait and retry — the server includes retry guidance | *** ## Resources Source code and issues Full REST API reference Credit packages and costs