UNPKG

@cryptodevops/n8n-nodes-defillama

Version:

Custom n8n node for DefiLlama API - Access DeFi protocols data, TVL, yields, and chain analytics

1,018 lines (1,017 loc) 67.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefiLlama = void 0; const n8n_workflow_1 = require("n8n-workflow"); const axios_1 = __importDefault(require("axios")); class DefiLlama { constructor() { this.description = { displayName: 'DefiLlama', name: 'defiLlama', icon: 'file:defillama.svg', group: ['input'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Access DeFi protocols data using the DefiLlama API', defaults: { name: 'DefiLlama', }, inputs: ['main'], outputs: ['main'], usableAsTool: true, credentials: [ { name: 'defiLlamaApi', required: false, }, ], requestDefaults: { baseURL: 'https://api.llama.fi', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }, properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Bridge', value: 'bridges', }, { name: 'Category', value: 'categories', }, { name: 'Chain', value: 'chain', }, { name: 'DEXs & Volume', value: 'dexs', }, { name: 'Fees & Revenue', value: 'fees', }, { name: 'Hack', value: 'hacks', }, { name: 'Oracle', value: 'oracles', }, { name: 'Protocol', value: 'protocol', }, { name: 'Raise', value: 'raises', }, { name: 'Stablecoin', value: 'stablecoins', }, { name: 'TVL', value: 'tvl', }, { name: 'Yield', value: 'yield', }, ], default: 'protocol', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['protocol'], }, }, options: [ { name: 'List All Protocols', value: 'listProtocols', description: 'Get all DeFi protocols with their TVL data', action: 'Get all de fi protocols with their tvl data', }, { name: 'Get Protocol Details', value: 'getProtocol', description: 'Get detailed information about a specific protocol', action: 'Get detailed information about a specific protocol', }, { name: 'Get Protocol TVL', value: 'getProtocolTvl', description: 'Get current TVL of a specific protocol', action: 'Get current TVL of a specific protocol', }, ], default: 'listProtocols', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['chain'], }, }, options: [ { name: 'List All Chains', value: 'listChains', description: 'Get all blockchain chains with their TVL data', action: 'Get all blockchain chains with their TVL data', }, { name: 'Get Chain TVL', value: 'getChainTvl', description: 'Get historical TVL data for a specific chain', action: 'Get historical TVL data for a specific chain', }, ], default: 'listChains', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['tvl'], }, }, options: [ { name: 'Get Historical TVL', value: 'getHistoricalTvl', description: 'Get historical TVL data for all chains combined', action: 'Get historical TVL data for all chains combined', }, ], default: 'getHistoricalTvl', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['yield'], }, }, options: [ { name: 'Get Yield Pools', value: 'getYieldPools', description: 'Get yield farming opportunities across protocols', action: 'Get yield farming opportunities across protocols', }, ], default: 'getYieldPools', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['fees'], }, }, options: [ { name: 'Get Fees Overview', value: 'getFeesOverview', description: 'Get fees and revenue overview across all protocols', action: 'Get fees and revenue overview across all protocols', }, { name: 'Get Protocol Fees', value: 'getProtocolFees', description: 'Get fees and revenue data for a specific protocol', action: 'Get fees and revenue data for a specific protocol', }, ], default: 'getFeesOverview', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['stablecoins'], }, }, options: [ { name: 'List Stablecoins', value: 'listStablecoins', description: 'Get all stablecoins with market cap and other data', action: 'Get all stablecoins with market cap and other data', }, { name: 'Get Stablecoin Charts', value: 'getStablecoinCharts', description: 'Get historical market cap charts for a specific stablecoin', action: 'Get historical market cap charts for a specific stablecoin', }, { name: 'Get Stablecoin Prices', value: 'getStablecoinPrices', description: 'Get current prices for all stablecoins', action: 'Get current prices for all stablecoins', }, ], default: 'listStablecoins', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['dexs'], }, }, options: [ { name: 'Get DEXs Overview', value: 'getDexsOverview', description: 'Get trading volume overview across all DEXs', action: 'Get trading volume overview across all de xs', }, { name: 'Get DEX Volume', value: 'getDexVolume', description: 'Get trading volume data for a specific DEX', action: 'Get trading volume data for a specific DEX', }, ], default: 'getDexsOverview', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['bridges'], }, }, options: [ { name: 'List Bridges', value: 'listBridges', description: 'Get all cross-chain bridges with volume data', action: 'Get all cross chain bridges with volume data', }, { name: 'Get Bridge Volume', value: 'getBridgeVolume', description: 'Get volume data for a specific bridge', action: 'Get volume data for a specific bridge', }, ], default: 'listBridges', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['oracles'], }, }, options: [ { name: 'List Oracles', value: 'listOracles', description: 'Get all oracle protocols with TVS (Total Value Secured)', action: 'Get all oracle protocols with tvs total value secured', }, ], default: 'listOracles', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['categories'], }, }, options: [ { name: 'List Categories', value: 'listCategories', description: 'Get all DeFi protocol categories', action: 'Get all de fi protocol categories', }, ], default: 'listCategories', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['raises'], }, }, options: [ { name: 'List Raises', value: 'listRaises', description: 'Get funding rounds and investment data', action: 'Get funding rounds and investment data', }, ], default: 'listRaises', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['hacks'], }, }, options: [ { name: 'List Hacks', value: 'listHacks', description: 'Get DeFi security incidents and hack data', action: 'Get de fi security incidents and hack data', }, ], default: 'listHacks', }, { displayName: 'Protocol Slug', name: 'protocolSlug', type: 'string', displayOptions: { show: { operation: ['getProtocol', 'getProtocolTvl', 'getProtocolFees'], resource: ['protocol', 'fees'], }, }, default: '', placeholder: 'aave', description: 'The protocol slug (e.g., aave, uniswap, curve)', required: true, }, { displayName: 'Chain Slug', name: 'chainSlug', type: 'string', displayOptions: { show: { operation: ['getChainTvl'], resource: ['chain'], }, }, default: '', placeholder: 'ethereum', description: 'The chain slug (e.g., ethereum, polygon, arbitrum)', required: true, }, { displayName: 'Stablecoin ID', name: 'stablecoinId', type: 'string', displayOptions: { show: { operation: ['getStablecoinCharts'], resource: ['stablecoins'], }, }, default: '', placeholder: '1', description: 'The stablecoin ID (e.g., 1 for Tether)', required: true, }, { displayName: 'DEX Slug', name: 'dexSlug', type: 'string', displayOptions: { show: { operation: ['getDexVolume'], resource: ['dexs'], }, }, default: '', placeholder: 'uniswap', description: 'The DEX slug (e.g., uniswap, sushiswap, curve)', required: true, }, { displayName: 'Bridge ID', name: 'bridgeId', type: 'string', displayOptions: { show: { operation: ['getBridgeVolume'], resource: ['bridges'], }, }, default: '', placeholder: '1', description: 'The bridge ID (e.g., 1 for Polygon PoS Bridge)', required: true, }, { displayName: 'Additional Fields', name: 'additionalFields', type: 'collection', placeholder: 'Add Field', default: {}, options: [ { displayName: 'Max Results', name: 'maxResults', type: 'number', typeOptions: { minValue: 1, maxValue: 100, }, default: 20, description: 'Maximum number of results to return (1-100)', }, { displayName: 'Chain Filter', name: 'chainFilter', type: 'string', displayOptions: { show: { '/resource': ['yield'], }, }, default: '', placeholder: 'ethereum,polygon', description: 'Filter by specific chains (comma-separated)', }, { displayName: 'Min TVL', name: 'minTvl', type: 'number', displayOptions: { show: { '/resource': ['protocol'], '/operation': ['listProtocols'], }, }, default: 0, description: 'Minimum TVL threshold in USD', }, { displayName: 'Min APY', name: 'minApy', type: 'number', displayOptions: { show: { '/resource': ['yield'], }, }, default: 0, description: 'Minimum APY percentage', }, { displayName: 'Stablecoin Only', name: 'stablecoinOnly', type: 'boolean', displayOptions: { show: { '/resource': ['yield'], }, }, default: false, description: 'Whether to filter for stablecoin pools only', }, { displayName: 'AI Agent Mode', name: 'aiMode', type: 'boolean', default: false, description: 'Whether to optimize output format for AI agents (simplified and structured)', }, { displayName: 'Include Summary', name: 'includeSummary', type: 'boolean', displayOptions: { show: { aiMode: [true], }, }, default: true, description: 'Whether to include a human-readable summary for AI agents', }, { displayName: 'Min Market Cap', name: 'minMarketCap', type: 'number', displayOptions: { show: { '/resource': ['stablecoins'], }, }, default: 0, description: 'Minimum market cap threshold in USD for stablecoins', }, { displayName: 'Min Volume', name: 'minVolume', type: 'number', displayOptions: { show: { '/resource': ['dexs', 'bridges'], }, }, default: 0, description: 'Minimum volume threshold in USD', }, { displayName: 'Min TVS', name: 'minTvs', type: 'number', displayOptions: { show: { '/resource': ['oracles'], }, }, default: 0, description: 'Minimum Total Value Secured (TVS) threshold in USD for oracles', }, { displayName: 'Date Range', name: 'dateRange', type: 'options', displayOptions: { show: { '/resource': ['fees', 'dexs', 'bridges'], }, }, options: [ { name: '24 Hours', value: '24h', }, { name: '7 Days', value: '7d', }, { name: '30 Days', value: '30d', }, ], default: '24h', description: 'Time range for data aggregation', }, { displayName: 'Include Pegged Assets', name: 'includePegged', type: 'boolean', displayOptions: { show: { '/resource': ['stablecoins'], }, }, default: true, description: 'Whether to include pegged assets in stablecoin results', }, ], }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; const length = items.length; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); for (let i = 0; i < length; i++) { try { const additionalFields = this.getNodeParameter('additionalFields', i); // Get credentials if available (optional for DefiLlama) let apiKey = ''; try { const credentials = await this.getCredentials('defiLlamaApi'); if (credentials && credentials.apiKey) { apiKey = credentials.apiKey; } } catch (error) { // API key is optional for most endpoints } let endpoint = ''; let responseData; const headers = { Accept: 'application/json', 'Content-Type': 'application/json', }; // Add API key to headers if available if (apiKey) { headers['Authorization'] = `Bearer ${apiKey}`; } if (resource === 'protocol') { if (operation === 'listProtocols') { endpoint = 'protocols'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; // Apply filters if (additionalFields.minTvl && additionalFields.minTvl > 0) { responseData = responseData.filter((protocol) => protocol.tvl >= additionalFields.minTvl); } // Limit results const maxResults = additionalFields.maxResults || 20; responseData = responseData.slice(0, maxResults); } else if (operation === 'getProtocol') { const protocolSlug = this.getNodeParameter('protocolSlug', i); endpoint = `protocol/${protocolSlug}`; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; } else if (operation === 'getProtocolTvl') { const protocolSlug = this.getNodeParameter('protocolSlug', i); endpoint = `tvl/${protocolSlug}`; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = { tvl: response.data, protocol: protocolSlug }; } } else if (resource === 'chain') { if (operation === 'listChains') { endpoint = 'chains'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; // Limit results const maxResults = additionalFields.maxResults || 20; responseData = responseData.slice(0, maxResults); } else if (operation === 'getChainTvl') { const chainSlug = this.getNodeParameter('chainSlug', i); endpoint = `v2/historicalChainTvl/${chainSlug}`; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; } } else if (resource === 'tvl') { if (operation === 'getHistoricalTvl') { endpoint = 'v2/historicalChainTvl'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; } } else if (resource === 'yield') { if (operation === 'getYieldPools') { endpoint = 'pools'; const response = await axios_1.default.get(`https://yields.llama.fi/${endpoint}`, { headers }); responseData = response.data.data || response.data; // Apply filters if (additionalFields.chainFilter) { const chains = additionalFields.chainFilter .split(',') .map((c) => c.trim().toLowerCase()); responseData = responseData.filter((pool) => { var _a; return chains.includes((_a = pool.chain) === null || _a === void 0 ? void 0 : _a.toLowerCase()); }); } if (additionalFields.minApy && additionalFields.minApy > 0) { responseData = responseData.filter((pool) => pool.apy >= additionalFields.minApy); } if (additionalFields.stablecoinOnly) { responseData = responseData.filter((pool) => pool.stablecoin === true || (pool.symbol && pool.symbol.toLowerCase().includes('usd'))); } // Limit results const maxResults = additionalFields.maxResults || 20; responseData = responseData.slice(0, maxResults); } } else if (resource === 'fees') { if (operation === 'getFeesOverview') { endpoint = 'overview/fees?excludeTotalDataChart=true'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data.protocols || response.data; // Apply date range filter const dateRange = additionalFields.dateRange; if (dateRange && Array.isArray(responseData)) { responseData = responseData.map((protocol) => { const filteredProtocol = { ...protocol }; // Keep only the data for the selected date range switch (dateRange) { case '24h': filteredProtocol.total = protocol.total24h; filteredProtocol.revenue = protocol.revenue24h; filteredProtocol.fees = protocol.fees24h; break; case '7d': filteredProtocol.total = protocol.total7d; filteredProtocol.revenue = protocol.revenue7d; filteredProtocol.fees = protocol.fees7d; break; case '30d': filteredProtocol.total = protocol.total30d; filteredProtocol.revenue = protocol.revenue30d; filteredProtocol.fees = protocol.fees30d; break; default: // Keep all data if no specific range selected break; } filteredProtocol.dateRange = dateRange; return filteredProtocol; }); } // Apply filters if (additionalFields.minVolume && additionalFields.minVolume > 0) { responseData = responseData.filter((protocol) => (protocol.total || protocol.total24h) >= additionalFields.minVolume); } // Limit results const maxResults = additionalFields.maxResults || 20; if (Array.isArray(responseData)) { responseData = responseData.slice(0, maxResults); } } else if (operation === 'getProtocolFees') { const protocolSlug = this.getNodeParameter('protocolSlug', i); endpoint = `summary/fees/${protocolSlug}`; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; // Apply date range filter for protocol fees const dateRange = additionalFields.dateRange; if (dateRange && responseData) { const filteredData = { ...responseData }; switch (dateRange) { case '24h': filteredData.total = responseData.total24h; filteredData.revenue = responseData.revenue24h; filteredData.fees = responseData.fees24h; break; case '7d': filteredData.total = responseData.total7d; filteredData.revenue = responseData.revenue7d; filteredData.fees = responseData.fees7d; break; case '30d': filteredData.total = responseData.total30d; filteredData.revenue = responseData.revenue30d; filteredData.fees = responseData.fees30d; break; } filteredData.dateRange = dateRange; responseData = filteredData; } } } else if (resource === 'stablecoins') { if (operation === 'listStablecoins') { endpoint = 'stablecoins'; const response = await axios_1.default.get(`https://stablecoins.llama.fi/${endpoint}`, { headers, }); responseData = response.data.peggedAssets || response.data; // Apply filters if (additionalFields.minMarketCap && additionalFields.minMarketCap > 0) { responseData = responseData.filter((stablecoin) => stablecoin.circulating && stablecoin.circulating.peggedUSD >= additionalFields.minMarketCap); } if (!additionalFields.includePegged) { responseData = responseData.filter((stablecoin) => stablecoin.pegType !== 'peggedVAR'); } // Limit results const maxResults = additionalFields.maxResults || 20; responseData = responseData.slice(0, maxResults); } else if (operation === 'getStablecoinCharts') { const stablecoinId = this.getNodeParameter('stablecoinId', i); endpoint = `stablecoincharts/all?stablecoin=${stablecoinId}`; const response = await axios_1.default.get(`https://stablecoins.llama.fi/${endpoint}`, { headers, }); responseData = response.data; } else if (operation === 'getStablecoinPrices') { endpoint = 'stablecoinprices'; const response = await axios_1.default.get(`https://stablecoins.llama.fi/${endpoint}`, { headers, }); responseData = response.data; // Limit results const maxResults = additionalFields.maxResults || 20; if (Array.isArray(responseData)) { responseData = responseData.slice(0, maxResults); } } } else if (resource === 'dexs') { if (operation === 'getDexsOverview') { endpoint = 'overview/dexs?excludeTotalDataChart=true'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data.protocols || response.data; // Apply date range filter const dateRange = additionalFields.dateRange; if (dateRange && Array.isArray(responseData)) { responseData = responseData.map((dex) => { const filteredDex = { ...dex }; // Keep only the data for the selected date range switch (dateRange) { case '24h': filteredDex.totalVolume = dex.total24h; filteredDex.change = dex.change_1d; break; case '7d': filteredDex.totalVolume = dex.total7d; filteredDex.change = dex.change_7d; break; case '30d': filteredDex.totalVolume = dex.total30d; filteredDex.change = dex.change_30d; break; default: // Keep all data if no specific range selected break; } filteredDex.dateRange = dateRange; return filteredDex; }); } // Apply filters if (additionalFields.minVolume && additionalFields.minVolume > 0) { responseData = responseData.filter((dex) => (dex.totalVolume || dex.total24h) >= additionalFields.minVolume); } // Limit results const maxResults = additionalFields.maxResults || 20; if (Array.isArray(responseData)) { responseData = responseData.slice(0, maxResults); } } else if (operation === 'getDexVolume') { const dexSlug = this.getNodeParameter('dexSlug', i); endpoint = `summary/dexs/${dexSlug}`; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; // Apply date range filter for DEX volume const dateRange = additionalFields.dateRange; if (dateRange && responseData) { const filteredData = { ...responseData }; switch (dateRange) { case '24h': filteredData.totalVolume = responseData.total24h; filteredData.change = responseData.change_1d; break; case '7d': filteredData.totalVolume = responseData.total7d; filteredData.change = responseData.change_7d; break; case '30d': filteredData.totalVolume = responseData.total30d; filteredData.change = responseData.change_30d; break; } filteredData.dateRange = dateRange; responseData = filteredData; } } } else if (resource === 'bridges') { if (operation === 'listBridges') { endpoint = 'bridges'; const response = await axios_1.default.get(`https://bridges.llama.fi/${endpoint}`, { headers }); responseData = response.data.bridges || response.data; // Apply date range filter const dateRange = additionalFields.dateRange; if (dateRange && Array.isArray(responseData)) { responseData = responseData.map((bridge) => { const filteredBridge = { ...bridge }; // Keep only the data for the selected date range switch (dateRange) { case '24h': filteredBridge.volume = bridge.volumePrevDay; filteredBridge.volumeChange = bridge.volumePrev2Day ? ((bridge.volumePrevDay - bridge.volumePrev2Day) / bridge.volumePrev2Day * 100) : 0; break; case '7d': filteredBridge.volume = bridge.volumePrevWeek; filteredBridge.volumeChange = bridge.volumePrev2Week ? ((bridge.volumePrevWeek - bridge.volumePrev2Week) / bridge.volumePrev2Week * 100) : 0; break; case '30d': filteredBridge.volume = bridge.volumePrevMonth; filteredBridge.volumeChange = bridge.volumePrev2Month ? ((bridge.volumePrevMonth - bridge.volumePrev2Month) / bridge.volumePrev2Month * 100) : 0; break; default: // Keep all data if no specific range selected break; } filteredBridge.dateRange = dateRange; return filteredBridge; }); } // Apply filters if (additionalFields.minVolume && additionalFields.minVolume > 0) { responseData = responseData.filter((bridge) => (bridge.volume || bridge.volumePrevDay) >= additionalFields.minVolume); } // Limit results const maxResults = additionalFields.maxResults || 20; responseData = responseData.slice(0, maxResults); } else if (operation === 'getBridgeVolume') { const bridgeId = this.getNodeParameter('bridgeId', i); endpoint = `bridgevolume/${bridgeId}`; const response = await axios_1.default.get(`https://bridges.llama.fi/${endpoint}`, { headers }); responseData = response.data; // Apply date range filter for bridge volume const dateRange = additionalFields.dateRange; if (dateRange && responseData) { const filteredData = { ...responseData }; // For bridge volume, we might need to filter the historical data if (responseData.volumeHistory && Array.isArray(responseData.volumeHistory)) { const now = Date.now() / 1000; let cutoffTime = now; switch (dateRange) { case '24h': cutoffTime = now - (24 * 60 * 60); break; case '7d': cutoffTime = now - (7 * 24 * 60 * 60); break; case '30d': cutoffTime = now - (30 * 24 * 60 * 60); break; } filteredData.volumeHistory = responseData.volumeHistory.filter((entry) => entry.date >= cutoffTime); } filteredData.dateRange = dateRange; responseData = filteredData; } } } else if (resource === 'oracles') { if (operation === 'listOracles') { endpoint = 'oracles'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; // Apply filters if (additionalFields.minTvs && additionalFields.minTvs > 0) { responseData = responseData.filter((oracle) => oracle.tvs >= additionalFields.minTvs); } // Limit results const maxResults = additionalFields.maxResults || 20; responseData = responseData.slice(0, maxResults); } } else if (resource === 'categories') { if (operation === 'listCategories') { endpoint = 'categories'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers }); responseData = response.data; // Limit results const maxResults = additionalFields.maxResults || 20; if (Array.isArray(responseData)) { responseData = responseData.slice(0, maxResults); } } } else if (resource === 'raises') { if (operation === 'listRaises') { endpoint = 'raises'; const response = await axios_1.default.get(`https://api.llama.fi/${endpoint}`, { headers });