Skip to content

Commit bff61fb

Browse files
authored
Zeebu Protocol Yield Adapter (#1706)
* Zeebu Yield Adapter This PR adds a new yield server adapter for the Zeebu protocol, supporting staking rewards across three chains: Ethereum, Base, and BSC. The adapter: - Fetches total staked ZBU tokens for each supported chain - Retrieves token prices using the DefiLlama price API - Calculates TVL and APY based on reward distribution logs - Supports multiple reward tokens (stablecoins and ZBU) - Provides a standardized pool object for each chain's staking rewards Key features: - Multi-chain support - Dynamic APY calculation - Handles different token decimals - Uses blockchain event logs for reward tracking * Changed apr and apy base to aprReward and apyReward respectively
1 parent 66612f0 commit bff61fb

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

src/adaptors/zeebu/index.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
const sdk = require('@defillama/sdk');
2+
const axios = require('axios');
3+
const utils = require('../utils');
4+
5+
// Reward tokens per chain (1st = stablecoin, 2nd = ZBU)
6+
const REWARD_TOKENS = {
7+
ethereum: ['0xdAC17F958D2ee523a2206206994597C13D831ec7', '0xe77f6aCD24185e149e329C1C0F479201b9Ec2f4B'],
8+
base: ['0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', '0x2C8C89C442436CC6C0A77943E09C8DAF49DA3161'],
9+
bsc: ['0x55d398326f99059ff775485246999027b3197955', '0x4D3dc895a9EDb234DfA3e303A196c009dC918f84'],
10+
};
11+
12+
// ZBU token addresses per chain
13+
const ZBU = {
14+
ethereum: '0xe77f6aCD24185e149e329C1C0F479201b9Ec2f4B',
15+
base: '0x2C8C89C442436CC6C0A77943E09C8DAF49DA3161',
16+
bsc: '0x4D3dc895a9EDb234DfA3e303A196c009dC918f84',
17+
};
18+
19+
// Reward Distributor contracts
20+
const REWARD_DISTRIBUTORS = {
21+
ethereum: '0xE843115fF0Dc2b20f5b07b6E7Ba5fED064468AC6',
22+
base: '0x24a4f5afc6a87005f00770e7e66d4a3d134f9923',
23+
bsc: '0x8ae3D193a7Dfeb4c8e36211d21E729feCcfa738A',
24+
};
25+
26+
// Voting Escrow contracts
27+
const VOTING_ESCROW_ADDRESSES = {
28+
ethereum: '0x8e76Cdf3b14c540aB54aFa7f8492AC1d16Ecfb35',
29+
base: '0xcf08D1EC5d8e566D95299399307F75f98D6AEa03',
30+
bsc: '0xd3e8cD2eDbf252860E02ffb245fD654b1ab30f30',
31+
};
32+
33+
// Chain-specific block numbers (for fetching logs)
34+
const FROM_BLOCKS = {
35+
ethereum: 21324707,
36+
base: 23235100,
37+
bsc: 44561619,
38+
};
39+
40+
// Decimals for stablecoins & ZBU
41+
const DECIMALS = {
42+
ethereum: [6, 18], // USDT (6), ZBU (18)
43+
base: [6, 18], // USDC (6), ZBU (18)
44+
bsc: [18, 18], // BSC-USD (18), ZBU (18)
45+
};
46+
47+
// Event topic for AddedRewardDistribution
48+
const ADDED_REWARD_DISTRIBUTION_TOPIC = '0xf00943d3f835d7ca6986bc0202fbb734d3be564db0bad44bafccb5d41149302e';
49+
50+
const apy = async () => {
51+
console.log("Starting APY calculation...");
52+
const results = [];
53+
54+
for (const chain of Object.keys(REWARD_DISTRIBUTORS)) {
55+
console.log(`Processing ${chain}...`);
56+
57+
// Fetch total staked ZBU
58+
const totalStakedZBU = (
59+
await sdk.api.abi.call({
60+
target: ZBU[chain],
61+
abi: 'erc20:balanceOf',
62+
params: [VOTING_ESCROW_ADDRESSES[chain]],
63+
chain,
64+
})
65+
).output / 1e18; // ZBU has 18 decimals
66+
console.log(`Total Staked ZBU on ${chain}: `, totalStakedZBU, ` ZBU`);
67+
68+
// Fetch prices for both reward tokens (stablecoin + ZBU)
69+
const tokenPrices = {};
70+
for (const token of REWARD_TOKENS[chain]) {
71+
const priceKey = `${chain}:${token}`;
72+
tokenPrices[token] = (
73+
await axios.get(`https://coins.llama.fi/prices/current/${priceKey}`)
74+
).data.coins[priceKey]?.price;
75+
}
76+
console.log(`Token Prices for ${chain}: `, tokenPrices);
77+
78+
// Calculate TVL in USD (using stablecoin price)
79+
const tvlUsd = totalStakedZBU * tokenPrices[REWARD_TOKENS[chain][1]];
80+
console.log(`TVL (USD) for ${chain}: $ `, tvlUsd);
81+
const currentBlock = await sdk.api.util.getLatestBlock(chain);
82+
const toBlock = currentBlock.number;
83+
// Fetch logs from RewardDistributor contract
84+
const logs = (
85+
await sdk.api.util.getLogs({
86+
target: REWARD_DISTRIBUTORS[chain],
87+
topic: '',
88+
toBlock,
89+
fromBlock: FROM_BLOCKS[chain],
90+
keys: [],
91+
topics: [ADDED_REWARD_DISTRIBUTION_TOPIC],
92+
chain,
93+
})
94+
).output.sort((a, b) => b.blockNumber - a.blockNumber);
95+
// console.log(`Fetched Logs for ${chain}: `, logs);
96+
97+
if (logs.length === 0) {
98+
console.log(`No reward distribution events found for ${chain}.`);
99+
continue;
100+
}
101+
102+
// Decode and sum up rewards
103+
let totalRewardsUsd = 0;
104+
105+
for (const log of logs) {
106+
const dataHex = log.data;
107+
const amountHex = dataHex.slice(0, 66); // First 32 bytes -> Amount
108+
const timestampHex = dataHex.slice(66, 130); // Second 32 bytes -> Reward Timestamp
109+
110+
const tokenAddress = `0x${log.topics[2].slice(-40)}`; // Extract token address
111+
console.log("Token Address: ", tokenAddress);
112+
const decimals = REWARD_TOKENS[chain][0].toLowerCase() === tokenAddress.toLowerCase()
113+
? DECIMALS[chain][0] // First token (Stablecoin)
114+
: DECIMALS[chain][1]; // Second token (ZBU)
115+
116+
const amount = Number(BigInt(amountHex).toString()) / 10 ** decimals;
117+
const rewardTimestamp = parseInt(timestampHex, 16);
118+
119+
// Fetch token price
120+
let tokenPrice;
121+
if (REWARD_TOKENS[chain][0].toLowerCase() === tokenAddress.toLowerCase()) {
122+
// If it's a usd-pegged stablecoin, use $1
123+
tokenPrice = 1;
124+
} else {
125+
// If it's ZBU, use its actual price
126+
tokenPrice = tokenPrices[Object.keys(tokenPrices).find(
127+
key => key.toLowerCase() === tokenAddress.toLowerCase()
128+
)];
129+
}
130+
131+
const rewardValueUsd = Number(amount) * tokenPrice;
132+
totalRewardsUsd += rewardValueUsd;
133+
134+
console.log(`Token: ${tokenAddress} - Amount: ${amount} - Reward Value (in USD): ${rewardValueUsd} - Timestamp: ${rewardTimestamp}`);
135+
}
136+
137+
console.log(`Total Rewards in USD for ${chain}: `, totalRewardsUsd);
138+
139+
// APY Calculation
140+
const now = Math.floor(Date.now() / 1000);
141+
const firstLogTimestamp = parseInt(logs[logs.length - 1].data.slice(66, 130), 16);
142+
const daysSinceStart = (now - firstLogTimestamp) / 86400;
143+
144+
if (daysSinceStart <= 0) {
145+
console.log(`Invalid daysSinceStart for ${chain}. Skipping APY calculation.`);
146+
continue;
147+
}
148+
149+
const aprReward = (totalRewardsUsd * 365) / (tvlUsd * daysSinceStart) * 100;
150+
const apyReward = utils.aprToApy(aprReward, 52);
151+
152+
console.log(`APR (Reward) for ${chain}: `, aprReward);
153+
console.log(`APY (Reward) for ${chain}: `, apyReward);
154+
155+
results.push({
156+
pool: VOTING_ESCROW_ADDRESSES[chain],
157+
chain: chain.charAt(0).toUpperCase() + chain.slice(1),
158+
project: 'zeebu',
159+
symbol: 'ZBU',
160+
tvlUsd,
161+
apyReward,
162+
rewardTokens: REWARD_TOKENS[chain],
163+
underlyingTokens: [ZBU[chain]],
164+
poolMeta: 'Zeebu Staking Rewards',
165+
url: 'https://zeebu.fi/',
166+
});
167+
}
168+
169+
return results;
170+
};
171+
172+
module.exports = {
173+
apy
174+
};

0 commit comments

Comments
 (0)