diff --git a/projects/helper/bitcoin-book/fetchers.js b/projects/helper/bitcoin-book/fetchers.js index ba76a844146..b72da76de99 100644 --- a/projects/helper/bitcoin-book/fetchers.js +++ b/projects/helper/bitcoin-book/fetchers.js @@ -242,4 +242,68 @@ module.exports = { }) return Array.from(new Set(staticAddresses)) }, + zenrock: async () => { + const ZRCHAIN_WALLETS_API = 'https://api.diamond.zenrocklabs.io/zrchain/treasury/zenbtc_wallets'; + const ZENBTC_PARAMS_API = 'https://api.diamond.zenrocklabs.io/zenbtc/params'; + const ZRCHAIN_KEY_BY_ID_API = 'https://api.diamond.zenrocklabs.io/zrchain/treasury/key_by_id'; + + return getConfig('zenrock/addresses', undefined, { + fetcher: async () => { + async function getBitcoinAddresses() { + const btcAddresses = []; + let nextKey = null; + + while (true) { + let url = ZRCHAIN_WALLETS_API; + if (nextKey) { + url += `?pagination.key=${encodeURIComponent(nextKey)}`; + } + const { data } = await axios.get(url); + if (data.zenbtc_wallets && Array.isArray(data.zenbtc_wallets)) { + for (const walletGroup of data.zenbtc_wallets) { + if (walletGroup.wallets && Array.isArray(walletGroup.wallets)) { + for (const wallet of walletGroup.wallets) { + if (wallet.type === 'WALLET_TYPE_BTC_MAINNET' && wallet.address) { + btcAddresses.push(wallet.address); + } + } + } + } + } + if (data.pagination && data.pagination.next_key) { + nextKey = data.pagination.next_key; + } else { + break; + } + } + return btcAddresses; + } + + async function getChangeAddresses() { + const changeAddresses = []; + + const { data: paramsData } = await axios.get(ZENBTC_PARAMS_API); + const changeAddressKeyIDs = paramsData.params?.changeAddressKeyIDs || []; + for (const keyID of changeAddressKeyIDs) { + const { data: keyData } = await axios.get(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_BTC_MAINNET/`); + if (keyData.wallets && Array.isArray(keyData.wallets)) { + for (const wallet of keyData.wallets) { + if (wallet.type === 'WALLET_TYPE_BTC_MAINNET' && wallet.address) { + changeAddresses.push(wallet.address); + } + } + } + } + return changeAddresses; + } + + const [btcAddresses, changeAddresses] = await Promise.all([ + getBitcoinAddresses(), + getChangeAddresses(), + ]); + const allAddresses = [...btcAddresses, ...changeAddresses]; + return allAddresses; + } + }); + }, } \ No newline at end of file diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js new file mode 100644 index 00000000000..256a7a2ccc9 --- /dev/null +++ b/projects/zenrock/index.js @@ -0,0 +1,72 @@ +const { sumTokens: sumBitcoinTokens } = require('../helper/chain/bitcoin'); +const { zenrock } = require('../helper/bitcoin-book/fetchers'); + +/** + * Queries Bitcoin balances for all zrchain treasury and change addresses + * Returns balances object with Bitcoin TVL + */ +async function tvl() { + // Fetch all protocol addresses (treasury + change) from the bitcoin-book fetcher + const allAddresses = await zenrock(); + + if (allAddresses.length === 0) { + return { bitcoin: '0' }; + } + + // Use Bitcoin helper to sum balances for all addresses + const balances = {}; + await sumBitcoinTokens({ balances, owners: allAddresses }); + + return balances; +} + +/** + * Queries Zcash balances for all zrchain treasury and change addresses + * Returns balances object with Zcash TVL + * + * NOTE: Currently using supply-based approach as a test implementation. + * The address-based approach is commented out below for future use. + */ +async function zcashTvl() { + // Fetch custodied amount from DCT supply endpoint + const { get } = require('../helper/http'); + const { getConfig } = require('../helper/cache'); + const sdk = require('@defillama/sdk'); + + const DCT_SUPPLY_API = 'https://api.diamond.zenrocklabs.io/dct/supply'; + + const supplyData = await getConfig('zenrock/dct_supply', DCT_SUPPLY_API, { + fetcher: async () => { + const response = await get(DCT_SUPPLY_API); + return response; + } + }); + + const balances = {}; + + // Find ASSET_ZENZEC in supplies array + const zenZecSupply = supplyData.supplies?.find( + item => item.supply?.asset === 'ASSET_ZENZEC' + ); + + if (zenZecSupply && zenZecSupply.supply?.custodied_amount) { + // custodied_amount is in Zatoshi (smallest unit, like satoshis for BTC) + // Convert to ZEC by dividing by 1e8 + const custodiedAmount = Number(zenZecSupply.supply.custodied_amount); + const custodiedZEC = custodiedAmount / 1e8; + + sdk.util.sumSingleBalance(balances, 'zcash', custodiedZEC); + } + + return balances; +} + +module.exports = { + methodology: 'zrchain locks native assets through its decentralized MPC network. zenBTC, Zenrock\'s flagship product, is a yield-bearing wrapped Bitcoin issued on Solana and EVM chains. TVL represents the total Bitcoin locked in zrchain treasury addresses. All zenBTC is fully backed by native Bitcoin, with the price of zenBTC anticipated to increase as yield payments are made continuously.', + bitcoin: { + tvl, + }, + zcash: { + tvl: zcashTvl, + }, +};