From 455b54b885949a656d7404248c20a5477f6a951e Mon Sep 17 00:00:00 2001 From: alan Date: Wed, 10 Sep 2025 18:03:03 +0900 Subject: [PATCH 1/3] add: yield info for affluent multiply vault --- src/adaptors/affluent/index.js | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/adaptors/affluent/index.js diff --git a/src/adaptors/affluent/index.js b/src/adaptors/affluent/index.js new file mode 100644 index 0000000000..b40053bbd2 --- /dev/null +++ b/src/adaptors/affluent/index.js @@ -0,0 +1,57 @@ +const utils = require('../utils'); + +const AFFLUENT_API_URL = "https://api.affluent.org/v2/api/strategyvaults/"; + +const getAPY = async () => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 15_000); + + try { + const res = await fetch(AFFLUENT_API_URL, { + method: "GET", + headers: { Accept: "application/json" }, + signal: controller.signal, + }); + + if (!res.ok) { + throw new Error(`API error: HTTP ${res.status} ${res.statusText}`); + } + + const data = await res.json(); + + if (!Array.isArray(data)) { + throw new Error("Unexpected response shape: not an array"); + } + + const vaults = data.map((v) => { + const pool = `${v.address}-TON`; + const project = "affluent"; + const apyBase = v.netApy; + const tvl = + typeof v?.tvl === "number" + ? v.tvl + : typeof v?.tvl === "string" + ? Number(v.tvl) + : 0; + + return { + pool, + chain: "TON", + project, + symbol: String(v?.symbol ?? ""), + tvlUsd: tvl, + ...(apyBase !== undefined ? { apyBase } : {}), + }; + }); + + return vaults; + } catch (err) { + console.error("Failed to fetch and map strategy vaults:", err); + } +} + +module.exports = { + timetravel: false, + url: 'https://affluent.org', + apy: getAPY, +}; From b5ced4466736ff2cd2b5724746756ab47d177e3f Mon Sep 17 00:00:00 2001 From: alan Date: Mon, 15 Sep 2025 13:35:37 +0900 Subject: [PATCH 2/3] add: yield info for affluent lending vault (share vault) --- src/adaptors/affluent/index.js | 160 +++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 45 deletions(-) diff --git a/src/adaptors/affluent/index.js b/src/adaptors/affluent/index.js index b40053bbd2..6fa9e8f09f 100644 --- a/src/adaptors/affluent/index.js +++ b/src/adaptors/affluent/index.js @@ -1,57 +1,127 @@ -const utils = require('../utils'); +const utils = require("../utils"); -const AFFLUENT_API_URL = "https://api.affluent.org/v2/api/strategyvaults/"; +const AFFLUENT_MULTIPLY_VAULT_API_URL = "https://api.affluent.org/v2/api/strategyvaults"; +const AFFLUENT_LENDING_VAULT_API_URL = "https://api.affluent.org/v2/api/sharevaults"; -const getAPY = async () => { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 15_000); +const nowSec = () => Math.floor(Date.now() / 1000); +const getAPY = async () => { try { - const res = await fetch(AFFLUENT_API_URL, { - method: "GET", - headers: { Accept: "application/json" }, - signal: controller.signal, - }); - - if (!res.ok) { - throw new Error(`API error: HTTP ${res.status} ${res.statusText}`); - } - - const data = await res.json(); - - if (!Array.isArray(data)) { - throw new Error("Unexpected response shape: not an array"); - } - - const vaults = data.map((v) => { - const pool = `${v.address}-TON`; - const project = "affluent"; - const apyBase = v.netApy; - const tvl = - typeof v?.tvl === "number" - ? v.tvl - : typeof v?.tvl === "string" - ? Number(v.tvl) - : 0; - - return { - pool, - chain: "TON", - project, - symbol: String(v?.symbol ?? ""), - tvlUsd: tvl, - ...(apyBase !== undefined ? { apyBase } : {}), - }; - }); + const [strategy, share] = await Promise.all([ + getStrategyVaultsMapped(), + getShareVaultsMapped(), + ]); - return vaults; + const merged = [...strategy, ...share]; + return merged; } catch (err) { - console.error("Failed to fetch and map strategy vaults:", err); + console.error("getAPY failed:", err); + return []; } +}; + +async function getStrategyVaultsMapped() { + const res = await fetch(AFFLUENT_MULTIPLY_VAULT_API_URL); + if (!res.ok) { + throw new Error(`Strategy API error: HTTP ${res.status} ${res.statusText}`); + } + + const data = await res.json(); + if (!Array.isArray(data)) { + throw new Error("Strategy: Unexpected response shape (not an array)"); + } + + return data.map((v) => + mapToOutput({ + address: v.address, + symbol: v?.symbol, + tvl: v?.tvl, + netApy: v?.netApy, + }) + ); +} + +async function getShareVaultList() { + const res = await fetch(AFFLUENT_LENDING_VAULT_API_URL); + if (!res.ok) { + throw new Error(`Share list API error: HTTP ${res.status} ${res.statusText}`); + } + + const data = await res.json(); + if (!Array.isArray(data)) { + throw new Error("Share list: Unexpected response shape (not an array)"); + } + return data; +} + +async function getShareVaultPoint(address, ts = nowSec()) { + const url = `${AFFLUENT_LENDING_VAULT_API_URL}/${address}?from=${ts}&to=${ts}&unit=3600`; + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Share point API error(${address}): HTTP ${res.status} ${res.statusText}`); + } + + const arr = await res.json(); + return Array.isArray(arr) && arr[0] ? arr[0] : null; +} + +async function getShareVaultsMapped() { + const list = await getShareVaultList(); + const ts = nowSec(); + + const results = await Promise.allSettled( + list.map(async (v) => { + const p = await getShareVaultPoint(v.address, ts); + const netApy = typeof p?.apy === "number" ? p.apy : undefined; + const tvl = p?.tvl; + + return mapToOutput({ + address: v.address, + symbol: v?.symbol, + tvl, + netApy, + }); + }) + ); + + return results.map((r, i) => { + if (r.status === "fulfilled") return r.value; + const sv = list[i]; + return mapToOutput({ + address: sv.address, + symbol: sv?.symbol, + tvl: 0, + netApy: undefined, + }); + }); } module.exports = { - timetravel: false, - url: 'https://affluent.org', apy: getAPY, + timetravel: false, + url: "https://affluent.org", }; + +function toNumberOr0(v) { + if (typeof v === "number") return v; + if (typeof v === "string") { + const n = Number(v); + return Number.isFinite(n) ? n : 0; + } + return 0; +} + +function mapToOutput({ address, symbol, tvl, netApy }) { + const pool = `${address}-TON`; + const project = "affluent"; + const apyBase = netApy; + + return { + pool, + chain: "TON", + project, + symbol: String(symbol ?? ""), + tvlUsd: toNumberOr0(tvl), + ...(apyBase !== undefined ? { apyBase } : {}), + }; +} \ No newline at end of file From df51be81c226fbd6a4afa6d9ce740cbbe8dd9b80 Mon Sep 17 00:00:00 2001 From: alan Date: Fri, 10 Oct 2025 13:30:26 +0900 Subject: [PATCH 3/3] fix: set symbol information based on tokens composing the vault --- src/adaptors/affluent/index.js | 60 +++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/adaptors/affluent/index.js b/src/adaptors/affluent/index.js index 6fa9e8f09f..4e63dad460 100644 --- a/src/adaptors/affluent/index.js +++ b/src/adaptors/affluent/index.js @@ -2,14 +2,17 @@ const utils = require("../utils"); const AFFLUENT_MULTIPLY_VAULT_API_URL = "https://api.affluent.org/v2/api/strategyvaults"; const AFFLUENT_LENDING_VAULT_API_URL = "https://api.affluent.org/v2/api/sharevaults"; +const AFFLUENT_ASSETS_API_URL = "https://api.affluent.org/v2/api/assets"; const nowSec = () => Math.floor(Date.now() / 1000); const getAPY = async () => { try { + const assetMap = await getAssetMap(); + const [strategy, share] = await Promise.all([ - getStrategyVaultsMapped(), - getShareVaultsMapped(), + getStrategyVaultsMapped(assetMap), + getShareVaultsMapped(assetMap), ]); const merged = [...strategy, ...share]; @@ -20,7 +23,7 @@ const getAPY = async () => { } }; -async function getStrategyVaultsMapped() { +async function getStrategyVaultsMapped(assetMap) { const res = await fetch(AFFLUENT_MULTIPLY_VAULT_API_URL); if (!res.ok) { throw new Error(`Strategy API error: HTTP ${res.status} ${res.statusText}`); @@ -31,14 +34,20 @@ async function getStrategyVaultsMapped() { throw new Error("Strategy: Unexpected response shape (not an array)"); } - return data.map((v) => - mapToOutput({ + return data.map((v) => { + const assetKeys = Object.keys(v.assets || {}); + const assetSymbols = assetKeys + .map((addr) => assetMap[addr] || addr) + .sort((a, b) => a.localeCompare(b)); + const assetSymbolString = assetSymbols.join("-"); + + return mapToOutput({ address: v.address, - symbol: v?.symbol, - tvl: v?.tvl, - netApy: v?.netApy, - }) - ); + symbol: assetSymbolString, + tvl: v.tvl, + netApy: v.netApy, + }); + }); } async function getShareVaultList() { @@ -65,7 +74,7 @@ async function getShareVaultPoint(address, ts = nowSec()) { return Array.isArray(arr) && arr[0] ? arr[0] : null; } -async function getShareVaultsMapped() { +async function getShareVaultsMapped(assetMap) { const list = await getShareVaultList(); const ts = nowSec(); @@ -75,9 +84,11 @@ async function getShareVaultsMapped() { const netApy = typeof p?.apy === "number" ? p.apy : undefined; const tvl = p?.tvl; + const underlyingSymbol = v?.underlying ? (assetMap[v.underlying] || v.underlying) : undefined; + return mapToOutput({ address: v.address, - symbol: v?.symbol, + symbol: underlyingSymbol, tvl, netApy, }); @@ -96,6 +107,31 @@ async function getShareVaultsMapped() { }); } +async function getAssetMap() { + const res = await fetch(AFFLUENT_ASSETS_API_URL); + if (!res.ok) { + throw new Error(`Asset API error: HTTP ${res.status} ${res.statusText}`); + } + + const data = await res.json(); + if (!Array.isArray(data)) { + throw new Error("Asset: Unexpected response shape (not an array)"); + } + + const map = {}; + for (const item of data) { + let symbol = item.symbol; + + if (symbol === "FactorialTON") { + symbol = "TON"; + } + + map[item.address] = symbol; + } + + return map; +} + module.exports = { apy: getAPY, timetravel: false,