diff --git a/src/adaptors/growihf/index.js b/src/adaptors/growihf/index.js index 3d7903d16a..9cb0f9c7c9 100644 --- a/src/adaptors/growihf/index.js +++ b/src/adaptors/growihf/index.js @@ -2,9 +2,13 @@ const https = require('https'); const VAULT_ADDRESS = '0x1e37a337ed460039d1b15bd3bc489de789768d5e'; const API_URL = 'https://api.hyperliquid.xyz/info'; -const USDC_ADDRESS = '0xb88339CB7199b77E23DB6E890353E22632Ba630f'; +const ARBITRUM_NATIVE_USDC_ADDRESS = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; -function fetchVaultDetails() { +const GROWI_ALPHA_VAULT_ID = 2; +const HIBACHI_DATA_API = 'https://data-api.hibachi.xyz'; +const USDT_ADDRESS_ARBITRUM = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9'; + +function fetchHyperliquidVaultDetails() { return new Promise((resolve, reject) => { const data = JSON.stringify({ type: 'vaultDetails', @@ -38,7 +42,7 @@ function fetchVaultDetails() { }); } -function computeAPYInception(vaultDetails) { +function computeHyperliquidAPYInception(vaultDetails) { const portfolio = vaultDetails.portfolio.find((p) => p[0] === 'allTime'); if (!portfolio) throw new Error('Missing allTime portfolio'); @@ -78,8 +82,8 @@ function computeAPYInception(vaultDetails) { } } -function computeAPY7Day(vaultDetails) { - let alltimeData = computeAPYInception(vaultDetails); +function computeHyperliquidAPY7Day(vaultDetails) { + let alltimeData = computeHyperliquidAPYInception(vaultDetails); const portfolio = vaultDetails.portfolio.find((p) => p[0] === 'week'); if (!portfolio) throw new Error('Missing weekly portfolio'); @@ -121,16 +125,115 @@ function computeAPY7Day(vaultDetails) { tvlUsd: alltimeData['tvlUsd'], apy: ann_yield * 100, apyBaseInception: alltimeData['apyBaseInception'] * 100, - underlyingTokens: [USDC_ADDRESS], + underlyingTokens: [ARBITRUM_NATIVE_USDC_ADDRESS], poolMeta: 'Hyperliquid Vault', url: 'https://app.hf.growi.fi/', }; } +function fetchHibachiDataAPI(url) { + return new Promise((resolve, reject) => { + https + .get(url, (res) => { + let body = ''; + res.on('data', (chunk) => (body += chunk)); + res.on('end', () => { + try { + resolve(JSON.parse(body)); + } catch (err) { + reject(err); + } + }); + }) + .on('error', reject); + }); +} + +function groupHibachiIntervalsByDay(intervals) { + const map = new Map(); + for (const iv of intervals) { + const day = Math.floor(iv.timestamp / 86400); + const existing = map.get(day); + if (!existing || iv.timestamp > existing.timestamp) { + map.set(day, iv); + } + } + return [...map.values()].sort((a, b) => a.timestamp - b.timestamp); +} + +function computeHibachiAPYInterval(intervals) { + if (intervals.length < 2) throw new Error('Not enough data points'); + const first = intervals[0]; + const last = intervals[intervals.length - 1]; + const days = (last.timestamp - first.timestamp) / 86400; + if (days <= 0) throw new Error('Non-positive time span'); + const priceRatio = + parseFloat(last.perSharePrice) / parseFloat(first.perSharePrice); + if (!(priceRatio > 0)) throw new Error('Invalid share price'); + return Math.pow(priceRatio, 365 / days) - 1; +} + +async function computeHibachiAPY7Day() { + const infoData = await fetchHibachiDataAPI(`${HIBACHI_DATA_API}/vault/info`); + const info = (infoData || []).find((v) => v.vaultId === GROWI_ALPHA_VAULT_ID); + if (!info) throw new Error('Growi Alpha Vault info not found'); + + const performanceData = await fetchHibachiDataAPI(`${HIBACHI_DATA_API}/vault/performance?vaultId=${GROWI_ALPHA_VAULT_ID}&timeRange=All`); + const intervals = performanceData.vaultPerformanceIntervals || []; + if (intervals.length < 3) throw new Error('Not enough data points'); + + const apyBaseInception = computeHibachiAPYInterval(intervals); + + const dailyBuckets = groupHibachiIntervalsByDay(intervals); + const week = dailyBuckets.slice(-7); + const apy7d = + week.length >= 2 ? computeHibachiAPYInterval(week) : apyBaseInception; + + const tvlUsdLatest = + parseFloat(performanceData.vaultPerformanceIntervals.at(-1).totalValueLocked); + + return { + pool: `growihf-alpha-vault-hibachi`, + chain: 'hibachi', + project: 'growihf', + symbol: 'USDT', + tvlUsd: tvlUsdLatest, + apy: apy7d * 100, + apyBaseInception: apyBaseInception * 100, + underlyingTokens: [USDT_ADDRESS_ARBITRUM], + poolMeta: 'Hibachi Vault', + url: 'https://app.hf.growi.fi/', + }; +} + module.exports = { timetravel: false, apy: async () => { - const vaultDetails = await fetchVaultDetails(); - return [computeAPY7Day(vaultDetails)]; + const pools = []; + + const sources = [ + { + name: 'hyperliquid', + build: async () => { + const details = await fetchHyperliquidVaultDetails(); + return computeHyperliquidAPY7Day(details); + }, + }, + { + name: 'hibachi', + build: computeHibachiAPY7Day, + }, + ]; + + for (const { name, build } of sources) { + try { + pools.push(await build()); + } catch (err) { + console.error(`growihf: skipping ${name}: ${err.message}`); + continue; + } + } + + return pools; }, };