Skip to content

Commit 1750a60

Browse files
committed
Enhance Accountable integration by adding Merkl rewards fetching and APY breakdowns for vaults
1 parent ada3243 commit 1750a60

File tree

1 file changed

+71
-22
lines changed

1 file changed

+71
-22
lines changed

src/adaptors/accountable/index.js

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const sdk = require('@defillama/sdk');
2+
const axios = require('axios');
23
const utils = require('../utils');
34

45
const API_URL = 'https://yield.accountable.capital/api/loan';
6+
const MERKL_API_URL = 'https://api.merkl.xyz/v4/opportunities?explorerAddress=';
57
const chainIdToName = { 143: 'monad' };
68
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
79

@@ -11,7 +13,7 @@ const abis = {
1113
};
1214

1315
const basisPointsToPercent = (value) => Number(value) / 1e4;
14-
const formatAmountWithDecimlas = (value, decimals) => value / 10 ** decimals;
16+
const formatAmount = (value, decimals = 18) => (value == null ? null : Number(value) / 10 ** decimals);
1517

1618
const fetchVaultsByLoanIds = async(loanIds) => {
1719
const results = await Promise.allSettled(
@@ -80,17 +82,45 @@ const getVaultStats = async(vaults, chain = 'monad') => {
8082

8183
const totalAssets = totalAssetsRes.output.map((o) => o.output);
8284
const liquidity = liquidityRes.output.map((o) => o.output);
83-
console.log(totalAssets, liquidity);
8485
return vaults.reduce((acc, address, i) => {
8586
acc[address] = {
8687
totalSupplied: supplies[i],
87-
totalBorrowed: totalAssets[i] - liquidity[i],
88+
totalBorrowed: Number(totalAssets[i]) - Number(liquidity[i] || 0),
8889
tvl: liquidity[i],
8990
};
9091
return acc;
9192
}, {});
9293
};
9394

95+
const fetchMerklRewards = async(vaultAddress) => {
96+
try {
97+
const { data } = await axios.get(`${MERKL_API_URL}${vaultAddress}`);
98+
const opp =
99+
Array.isArray(data) &&
100+
(data.find((item) => item?.explorerAddress?.toLowerCase() === vaultAddress.toLowerCase()) || data[0]);
101+
if (!opp) return [];
102+
return (
103+
opp.rewardsRecord?.breakdowns
104+
?.map((b) => b?.token?.address?.toLowerCase())
105+
.filter(Boolean) || []
106+
);
107+
} catch (e) {
108+
return [];
109+
}
110+
};
111+
112+
const fetchBreakdowns = async(loanIds) => {
113+
const results = await Promise.allSettled(
114+
loanIds.map((id) => utils.getData(`${API_URL}/${id}/apy/breakdown`))
115+
);
116+
117+
return results.reduce((acc, res, idx) => {
118+
if (res.status !== 'fulfilled') return acc;
119+
acc[loanIds[idx]] = res.value || {};
120+
return acc;
121+
}, {});
122+
};
123+
94124
const apy = async() => {
95125
const { items } = await utils.getData(API_URL);
96126
const activeLoans = items.filter((item) => item.loan_state === 3);
@@ -99,27 +129,46 @@ const apy = async() => {
99129
const loanVaultMap = await fetchVaultsByLoanIds(loanIds);
100130
const vaultAddresses = Object.values(loanVaultMap);
101131
const vaultStats = await getVaultStats(vaultAddresses);
102-
103-
return activeLoans.map((item) => {
104-
const chainName = chainIdToName[item.chain_id] || 'unknown';
105-
const vaultAddress = loanVaultMap[item.id];
106-
const stats = vaultAddress ? vaultStats[vaultAddress] || {} : {};
107-
108-
return {
109-
pool: `${item.loan_address}-${chainName}`.toLowerCase(),
110-
chain: chainName,
111-
project: 'accountable',
112-
symbol: utils.formatSymbol(item.asset_symbol),
113-
tvlUsd: formatAmountWithDecimlas(stats.tvl, 6),
114-
apyBase: basisPointsToPercent(item.net_apy),
115-
url: `https://yield.accountable.capital/vaults/${item.loan_address}`,
116-
totalSupplyUsd: formatAmountWithDecimlas(stats.totalSupplied, 6),
117-
totalBorrowUsd: formatAmountWithDecimlas(stats.totalBorrowed, 6),
118-
};
119-
});
132+
const breakdowns = await fetchBreakdowns(loanIds);
133+
134+
return Promise.all(
135+
activeLoans.map(async(item) => {
136+
const chainName = chainIdToName[item.chain_id] || 'unknown';
137+
const vaultAddress = loanVaultMap[item.id];
138+
const stats = vaultAddress ? vaultStats[vaultAddress] || {} : {};
139+
const pointBoosts = item?.all_points_apy_boost?.boosts_by_points || [];
140+
const pointRewardApy = pointBoosts.reduce((sum, b) => sum + Number(b?.apy_boost_percent || 0), 0);
141+
const pointRewardTokens = pointBoosts.map((b) => b?.point_name).filter(Boolean);
142+
143+
const breakdown = breakdowns[item.id] || {};
144+
const merklApy = breakdown?.merkle_apy ?? 0;
145+
const nativeApy = breakdown?.native_apy ?? basisPointsToPercent(item.apy);
146+
const perfFee = breakdown?.performance_fee ?? 0;
147+
148+
const totalApyReward = (merklApy ?? 0) + pointRewardApy || null;
149+
const merklTokens = vaultAddress ? await fetchMerklRewards(vaultAddress) : [];
150+
const combinedRewardTokens = Array.from(
151+
new Set([...(merklTokens || []), ...pointRewardTokens])
152+
);
153+
154+
return {
155+
pool: `${item.loan_address}-${chainName}`.toLowerCase(),
156+
chain: utils.formatChain(chainName),
157+
project: 'accountable',
158+
symbol: utils.formatSymbol(item.asset_symbol),
159+
tvlUsd: formatAmount(stats.tvl, 6),
160+
apyBase: nativeApy + perfFee,
161+
apyReward: totalApyReward,
162+
rewardTokens: combinedRewardTokens,
163+
url: `https://yield.accountable.capital/vaults/${item.loan_address}`,
164+
totalSupplyUsd: formatAmount(stats.totalSupplied, 6),
165+
totalBorrowUsd: formatAmount(stats.totalBorrowed, 6),
166+
};
167+
})
168+
);
120169
};
121170

122171
module.exports = {
123172
timetravel: false,
124173
apy,
125-
};
174+
};

0 commit comments

Comments
 (0)