From cd6d26d94a11708bfe25e64f7b96b6aace2b36b9 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 27 Oct 2025 12:30:06 -0400 Subject: [PATCH 01/12] Add zenrock DefiLlama TVL adapter - Tracks native Bitcoin locked by zrchain protocol across Ethereum and Solana - Reports proportional Bitcoin distribution per chain from zrchain API - Uses DefiLlama SDK helpers for both chains (Solana getTokenSupplies, Ethereum api.call) - Implements promise-based caching to prevent redundant fetches with concurrent calls - Derives Solana mint address from program ID using PDA with "wrapped_mint" seed - Tested with live mainnet data: ~74 BTC across chains Methodology: TVL represents total native Bitcoin locked by the protocol, with zenBTC as wrapped representations on each chain. Each zenBTC is backed by at least 1 BTC in custody, with yield mechanisms coming soon that will increase the backing ratio above 1:1. --- projects/zenrock/index.js | 132 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 projects/zenrock/index.js diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js new file mode 100644 index 00000000000..506d4b7d890 --- /dev/null +++ b/projects/zenrock/index.js @@ -0,0 +1,132 @@ +const { getTokenSupplies } = require('../helper/solana'); +const { PublicKey } = require('@solana/web3.js'); + +// Ethereum zenBTC configuration +const ZENBTC_ETHEREUM = '0x2fE9754d5D28bac0ea8971C0Ca59428b8644C776'; + +// Solana zenBTC configuration +const ZENBTC_PROGRAM_ID = '9t9RfpterTs95eXbKQWeAriZqET13TbjwDa6VW6LJHFb'; + +// zrchain API endpoint for actual custodied Bitcoin +const ZRCHAIN_API = 'https://api.diamond.zenrocklabs.io/zenbtc/supply'; + +// Cache for supplies to avoid redundant fetches +let suppliesPromise = null; + +/** + * Derives the zenBTC mint address from the program ID using the "wrapped_mint" seed + */ +function getMintAddress() { + const seeds = [Buffer.from('wrapped_mint')]; + const [address] = PublicKey.findProgramAddressSync(seeds, new PublicKey(ZENBTC_PROGRAM_ID)); + return address.toString(); +} + +/** + * Fetches the actual custodied Bitcoin amount from zrchain API + * This represents the real BTC locked by the protocol + */ +async function getCustodiedBTC() { + try { + const response = await fetch(ZRCHAIN_API); + const data = await response.json(); + // custodiedBTC is in satoshis (8 decimals) + return BigInt(data.custodiedBTC); + } catch (error) { + console.error(`Error fetching custodied BTC from zrchain API: ${error.message}`); + return 0n; + } +} + +/** + * Queries Ethereum zenBTC supply using the DefiLlama API + */ +async function getEthereumSupply(api) { + try { + const supply = await api.call({ + abi: 'erc20:totalSupply', + target: ZENBTC_ETHEREUM, + chain: 'ethereum', + }); + return BigInt(supply); + } catch (error) { + console.error(`Error querying Ethereum zenBTC supply: ${error.message}`); + return 0n; + } +} + +/** + * Main TVL function - handles both Ethereum and Solana + * Reports each chain's proportional share of the custodied Bitcoin + */ +async function fetchSupplies(api) { + // Fetch all data in parallel + const [custodiedBTC, ethSupply] = await Promise.all([ + getCustodiedBTC(), + getEthereumSupply(api), + ]); + + // Query Solana supply using the DefiLlama helper + const zenbtcMint = getMintAddress(); + const solanaSupplies = await getTokenSupplies([zenbtcMint], { api }); + const solSupply = solanaSupplies[zenbtcMint] ? BigInt(solanaSupplies[zenbtcMint]) : 0n; + + return { + custodiedBTC, + ethSupply, + solSupply, + }; +} + +async function tvl(api) { + const { chain } = api; + + // Store api globally for use in async functions (like ACRED does) + global.api = api; + + // Use a single promise to ensure only one fetch happens even if called simultaneously + if (!suppliesPromise) { + suppliesPromise = fetchSupplies(api); + } + + const { custodiedBTC, ethSupply, solSupply } = await suppliesPromise; + + console.log(`[${chain}] ethSupply: ${ethSupply}, solSupply: ${solSupply}, custodiedBTC: ${custodiedBTC}`); + + const totalZenBTC = ethSupply + solSupply; + + // Avoid division by zero + if (totalZenBTC === 0n) { + const balances = {}; + balances['coingecko:bitcoin'] = '0'; + return balances; + } + + // Calculate this chain's proportional share of custodied Bitcoin + let chainSupply = 0n; + if (chain === 'ethereum') { + chainSupply = ethSupply; + } else if (chain === 'solana') { + chainSupply = solSupply; + } + + // (chainSupply / totalZenBTC) * custodiedBTC, then convert satoshis to BTC + const chainBTCAmount = Number((chainSupply * custodiedBTC) / totalZenBTC) / 1e8; + + const balances = {}; + balances['coingecko:bitcoin'] = chainBTCAmount; + + return balances; +} + +module.exports = { + methodology: 'zrchain locks native Bitcoin through its decentralized MPC network. zenBTC representations are issued on Ethereum and Solana mainnet, where they serve as wrapped claims on the native Bitcoin collateral held by the protocol. TVL represents the total Bitcoin locked, calculated as the sum of zenBTC supplies across all chains. Each zenBTC token is fully backed 1:1 by Bitcoin in custody.', + ethereum: { + tvl, + }, + solana: { + tvl, + }, +}; + +// node test.js projects/zrchain/index.js From c0d15bc96455237d94c81a8e93bdd47072d74004 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 27 Oct 2025 12:44:54 -0400 Subject: [PATCH 02/12] Update methodology to reflect yield-bearing nature of zenBTC --- projects/zenrock/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index 506d4b7d890..ebff01eca7e 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -120,7 +120,7 @@ async function tvl(api) { } module.exports = { - methodology: 'zrchain locks native Bitcoin through its decentralized MPC network. zenBTC representations are issued on Ethereum and Solana mainnet, where they serve as wrapped claims on the native Bitcoin collateral held by the protocol. TVL represents the total Bitcoin locked, calculated as the sum of zenBTC supplies across all chains. Each zenBTC token is fully backed 1:1 by Bitcoin in custody.', + 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. zenBTC TVL represents the total Bitcoin locked, calculated as the sum of zenBTC supplies across all chains. All zenBTC is fully backed 1:1 by Bitcoin in custody, but the price of zenBTC is anticipated to increase as yield payments are made on a continuous basis.', ethereum: { tvl, }, From 6780600b6cc7d8515fc0e9069d32713eeb605888 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 27 Oct 2025 15:05:03 -0400 Subject: [PATCH 03/12] feat(zenrock): replace API-based TVL with actual Bitcoin address queries - Implement paginated fetching of Bitcoin mainnet addresses from zrchain treasury (297 addresses total) - Replace custodied BTC API endpoint with actual on-chain balance queries using Bitcoin helper - Add change address (bc1qngthd4lgz6pjkf24d2cesltlnd7nd0pjguuvqu) to treasury wallet list - TVL now calculated from verified Bitcoin address balances (~74.05 BTC) - Maintains proportional distribution across Ethereum and Solana zenBTC supplies This provides transparent, on-chain verified TVL instead of relying on API-reported custodied amounts. --- projects/zenrock/index.js | 93 +++++++++++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 13 deletions(-) diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index ebff01eca7e..1aa86a2aca1 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -1,5 +1,6 @@ const { getTokenSupplies } = require('../helper/solana'); const { PublicKey } = require('@solana/web3.js'); +const { sumTokens: sumBitcoinTokens } = require('../helper/chain/bitcoin'); // Ethereum zenBTC configuration const ZENBTC_ETHEREUM = '0x2fE9754d5D28bac0ea8971C0Ca59428b8644C776'; @@ -7,8 +8,8 @@ const ZENBTC_ETHEREUM = '0x2fE9754d5D28bac0ea8971C0Ca59428b8644C776'; // Solana zenBTC configuration const ZENBTC_PROGRAM_ID = '9t9RfpterTs95eXbKQWeAriZqET13TbjwDa6VW6LJHFb'; -// zrchain API endpoint for actual custodied Bitcoin -const ZRCHAIN_API = 'https://api.diamond.zenrocklabs.io/zenbtc/supply'; +// zrchain treasury wallet endpoint for Bitcoin addresses +const ZRCHAIN_WALLETS_API = 'https://api.diamond.zenrocklabs.io/zrchain/treasury/zenbtc_wallets'; // Cache for supplies to avoid redundant fetches let suppliesPromise = null; @@ -23,17 +24,83 @@ function getMintAddress() { } /** - * Fetches the actual custodied Bitcoin amount from zrchain API - * This represents the real BTC locked by the protocol + * Fetches all Bitcoin mainnet addresses from zrchain treasury with pagination + * Filters for WALLET_TYPE_BTC_MAINNET type only */ -async function getCustodiedBTC() { +async function getBitcoinAddresses() { + const btcAddresses = []; + let nextKey = null; + + try { + while (true) { + let url = ZRCHAIN_WALLETS_API; + if (nextKey) { + url += `?pagination.key=${encodeURIComponent(nextKey)}`; + } + + const response = await fetch(url); + const data = await response.json(); + + // Extract Bitcoin mainnet addresses from zenbtc_wallets array + 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) { + // Filter for Bitcoin mainnet addresses only + if (wallet.type === 'WALLET_TYPE_BTC_MAINNET' && wallet.address) { + btcAddresses.push(wallet.address); + } + } + } + } + } + + // Check for next page + if (data.pagination && data.pagination.next_key) { + nextKey = data.pagination.next_key; + } else { + // No more pages, exit loop + break; + } + } + + console.log(`Fetched ${btcAddresses.length} Bitcoin mainnet addresses from zrchain treasury`); + return btcAddresses; + } catch (error) { + console.error(`Error fetching Bitcoin addresses from zrchain API: ${error.message}`); + return []; + } +} + +/** + * Queries Bitcoin balances for all treasury addresses + * Returns total BTC balance in satoshis + */ +async function getBitcoinTVL() { try { - const response = await fetch(ZRCHAIN_API); - const data = await response.json(); - // custodiedBTC is in satoshis (8 decimals) - return BigInt(data.custodiedBTC); + const btcAddresses = await getBitcoinAddresses(); + + if (btcAddresses.length === 0) { + console.warn('No Bitcoin addresses found in treasury'); + return 0n; + } + + // Add change address + const changeAddress = 'bc1qngthd4lgz6pjkf24d2cesltlnd7nd0pjguuvqu'; + btcAddresses.push(changeAddress); + + // Use Bitcoin helper to sum balances for all addresses + const balances = {}; + await sumBitcoinTokens({ balances, owners: btcAddresses }); + + // Extract Bitcoin balance and convert to satoshis (from BTC) + const btcAmount = balances.bitcoin || 0; + const satoshis = BigInt(Math.round(btcAmount * 1e8)); + + console.log(`Bitcoin TVL from ${btcAddresses.length} addresses: ${satoshis.toString()} satoshis (${btcAmount} BTC)`); + return satoshis; } catch (error) { - console.error(`Error fetching custodied BTC from zrchain API: ${error.message}`); + console.error(`Error calculating Bitcoin TVL: ${error.message}`); return 0n; } } @@ -57,12 +124,12 @@ async function getEthereumSupply(api) { /** * Main TVL function - handles both Ethereum and Solana - * Reports each chain's proportional share of the custodied Bitcoin + * Reports each chain's proportional share of the actual Bitcoin in treasury */ async function fetchSupplies(api) { // Fetch all data in parallel const [custodiedBTC, ethSupply] = await Promise.all([ - getCustodiedBTC(), + getBitcoinTVL(), getEthereumSupply(api), ]); @@ -129,4 +196,4 @@ module.exports = { }, }; -// node test.js projects/zrchain/index.js +// node test.js projects/zenrock/index.js From 81434be59df7d51c4ee07c0e09492ca6e1435ac6 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 27 Oct 2025 15:11:59 -0400 Subject: [PATCH 04/12] feat(zenrock): dynamically fetch change addresses from zenbtc params - Replace hardcoded change address with dynamic fetching from zenbtc params API - Implement getChangeAddresses() function that: - Fetches changeAddressKeyIDs from /zenbtc/params - Queries /zrchain/treasury/key_by_id/{keyID}/WALLET_TYPE_BTC_MAINNET/ for each key - Extracts addresses from wallet data - Fetch treasury and change addresses in parallel for efficiency - Update logging to show breakdown (296 treasury + 1 change = 297 total addresses) This makes the adapter dynamic and resilient to future changes in the zenbtc configuration. --- projects/zenrock/index.js | 71 +++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index 1aa86a2aca1..1b136ef7640 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -8,8 +8,10 @@ const ZENBTC_ETHEREUM = '0x2fE9754d5D28bac0ea8971C0Ca59428b8644C776'; // Solana zenBTC configuration const ZENBTC_PROGRAM_ID = '9t9RfpterTs95eXbKQWeAriZqET13TbjwDa6VW6LJHFb'; -// zrchain treasury wallet endpoint for Bitcoin addresses +// zrchain API endpoints 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'; // Cache for supplies to avoid redundant fetches let suppliesPromise = null; @@ -23,6 +25,50 @@ function getMintAddress() { return address.toString(); } +/** + * Fetches change addresses from zenbtc params + * Queries each change address key ID to get the actual Bitcoin addresses + */ +async function getChangeAddresses() { + const changeAddresses = []; + + try { + // Fetch zenbtc params to get change address key IDs + const paramsResponse = await fetch(ZENBTC_PARAMS_API); + const paramsData = await paramsResponse.json(); + + const changeAddressKeyIDs = paramsData.params?.changeAddressKeyIDs || []; + + // Fetch each change address + for (const keyID of changeAddressKeyIDs) { + try { + const keyResponse = await fetch(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_BTC_MAINNET/`); + const keyData = await keyResponse.json(); + + // Extract addresses from wallets array + if (keyData.wallets && Array.isArray(keyData.wallets)) { + for (const wallet of keyData.wallets) { + if (wallet.address) { + changeAddresses.push(wallet.address); + } + } + } + } catch (error) { + console.warn(`Error fetching change address for key ID ${keyID}: ${error.message}`); + } + } + + if (changeAddresses.length > 0) { + console.log(`Fetched ${changeAddresses.length} change address(es)`); + } + + return changeAddresses; + } catch (error) { + console.error(`Error fetching change addresses from zenbtc params: ${error.message}`); + return []; + } +} + /** * Fetches all Bitcoin mainnet addresses from zrchain treasury with pagination * Filters for WALLET_TYPE_BTC_MAINNET type only @@ -73,31 +119,34 @@ async function getBitcoinAddresses() { } /** - * Queries Bitcoin balances for all treasury addresses + * Queries Bitcoin balances for all treasury addresses and change addresses * Returns total BTC balance in satoshis */ async function getBitcoinTVL() { try { - const btcAddresses = await getBitcoinAddresses(); + // Fetch treasury addresses and change addresses in parallel + const [btcAddresses, changeAddresses] = await Promise.all([ + getBitcoinAddresses(), + getChangeAddresses(), + ]); - if (btcAddresses.length === 0) { - console.warn('No Bitcoin addresses found in treasury'); + // Combine all addresses + const allAddresses = [...btcAddresses, ...changeAddresses]; + + if (allAddresses.length === 0) { + console.warn('No Bitcoin addresses found in treasury or change addresses'); return 0n; } - // Add change address - const changeAddress = 'bc1qngthd4lgz6pjkf24d2cesltlnd7nd0pjguuvqu'; - btcAddresses.push(changeAddress); - // Use Bitcoin helper to sum balances for all addresses const balances = {}; - await sumBitcoinTokens({ balances, owners: btcAddresses }); + await sumBitcoinTokens({ balances, owners: allAddresses }); // Extract Bitcoin balance and convert to satoshis (from BTC) const btcAmount = balances.bitcoin || 0; const satoshis = BigInt(Math.round(btcAmount * 1e8)); - console.log(`Bitcoin TVL from ${btcAddresses.length} addresses: ${satoshis.toString()} satoshis (${btcAmount} BTC)`); + console.log(`Bitcoin TVL from ${allAddresses.length} addresses (${btcAddresses.length} treasury + ${changeAddresses.length} change): ${satoshis.toString()} satoshis (${btcAmount} BTC)`); return satoshis; } catch (error) { console.error(`Error calculating Bitcoin TVL: ${error.message}`); From 0d22f6764972c212cab0cd65a8519e2d9154db22 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Tue, 28 Oct 2025 12:48:51 -0400 Subject: [PATCH 05/12] Fix: Include both treasury and change addresses in Bitcoin TVL calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tvl() function now fetches both treasury and change addresses in parallel, combining them to calculate the total Bitcoin TVL. This ensures accurate accounting of all custodied Bitcoin across both wallet types. Test results: 297 total addresses (296 treasury + 1 change) = 74.05 BTC TVL 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- projects/zenrock/index.js | 216 ++++++++++---------------------------- 1 file changed, 53 insertions(+), 163 deletions(-) diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index 1b136ef7640..e442666503b 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -1,74 +1,10 @@ -const { getTokenSupplies } = require('../helper/solana'); -const { PublicKey } = require('@solana/web3.js'); const { sumTokens: sumBitcoinTokens } = require('../helper/chain/bitcoin'); -// Ethereum zenBTC configuration -const ZENBTC_ETHEREUM = '0x2fE9754d5D28bac0ea8971C0Ca59428b8644C776'; - -// Solana zenBTC configuration -const ZENBTC_PROGRAM_ID = '9t9RfpterTs95eXbKQWeAriZqET13TbjwDa6VW6LJHFb'; - // zrchain API endpoints 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'; -// Cache for supplies to avoid redundant fetches -let suppliesPromise = null; - -/** - * Derives the zenBTC mint address from the program ID using the "wrapped_mint" seed - */ -function getMintAddress() { - const seeds = [Buffer.from('wrapped_mint')]; - const [address] = PublicKey.findProgramAddressSync(seeds, new PublicKey(ZENBTC_PROGRAM_ID)); - return address.toString(); -} - -/** - * Fetches change addresses from zenbtc params - * Queries each change address key ID to get the actual Bitcoin addresses - */ -async function getChangeAddresses() { - const changeAddresses = []; - - try { - // Fetch zenbtc params to get change address key IDs - const paramsResponse = await fetch(ZENBTC_PARAMS_API); - const paramsData = await paramsResponse.json(); - - const changeAddressKeyIDs = paramsData.params?.changeAddressKeyIDs || []; - - // Fetch each change address - for (const keyID of changeAddressKeyIDs) { - try { - const keyResponse = await fetch(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_BTC_MAINNET/`); - const keyData = await keyResponse.json(); - - // Extract addresses from wallets array - if (keyData.wallets && Array.isArray(keyData.wallets)) { - for (const wallet of keyData.wallets) { - if (wallet.address) { - changeAddresses.push(wallet.address); - } - } - } - } catch (error) { - console.warn(`Error fetching change address for key ID ${keyID}: ${error.message}`); - } - } - - if (changeAddresses.length > 0) { - console.log(`Fetched ${changeAddresses.length} change address(es)`); - } - - return changeAddresses; - } catch (error) { - console.error(`Error fetching change addresses from zenbtc params: ${error.message}`); - return []; - } -} - /** * Fetches all Bitcoin mainnet addresses from zrchain treasury with pagination * Filters for WALLET_TYPE_BTC_MAINNET type only @@ -119,128 +55,82 @@ async function getBitcoinAddresses() { } /** - * Queries Bitcoin balances for all treasury addresses and change addresses - * Returns total BTC balance in satoshis + * Fetches change addresses from zenbtc params + * Queries each change address key ID to get the actual Bitcoin mainnet addresses */ -async function getBitcoinTVL() { +async function getChangeAddresses() { + const changeAddresses = []; + try { - // Fetch treasury addresses and change addresses in parallel - const [btcAddresses, changeAddresses] = await Promise.all([ - getBitcoinAddresses(), - getChangeAddresses(), - ]); + // Fetch zenbtc params to get change address key IDs + const paramsResponse = await fetch(ZENBTC_PARAMS_API); + const paramsData = await paramsResponse.json(); - // Combine all addresses - const allAddresses = [...btcAddresses, ...changeAddresses]; + const changeAddressKeyIDs = paramsData.params?.changeAddressKeyIDs || []; - if (allAddresses.length === 0) { - console.warn('No Bitcoin addresses found in treasury or change addresses'); - return 0n; - } + // Fetch each change address + for (const keyID of changeAddressKeyIDs) { + try { + const keyResponse = await fetch(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_BTC_MAINNET/`); + const keyData = await keyResponse.json(); - // Use Bitcoin helper to sum balances for all addresses - const balances = {}; - await sumBitcoinTokens({ balances, owners: allAddresses }); + // Extract Bitcoin mainnet addresses from wallets array + 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); + } + } + } + } catch (error) { + console.warn(`Error fetching change address for key ID ${keyID}: ${error.message}`); + } + } - // Extract Bitcoin balance and convert to satoshis (from BTC) - const btcAmount = balances.bitcoin || 0; - const satoshis = BigInt(Math.round(btcAmount * 1e8)); + if (changeAddresses.length > 0) { + console.log(`Fetched ${changeAddresses.length} Bitcoin change address(es)`); + } - console.log(`Bitcoin TVL from ${allAddresses.length} addresses (${btcAddresses.length} treasury + ${changeAddresses.length} change): ${satoshis.toString()} satoshis (${btcAmount} BTC)`); - return satoshis; + return changeAddresses; } catch (error) { - console.error(`Error calculating Bitcoin TVL: ${error.message}`); - return 0n; + console.error(`Error fetching change addresses from zenbtc params: ${error.message}`); + return []; } } /** - * Queries Ethereum zenBTC supply using the DefiLlama API + * Queries Bitcoin balances for all treasury and change addresses + * Returns balances object with Bitcoin TVL */ -async function getEthereumSupply(api) { +async function tvl() { try { - const supply = await api.call({ - abi: 'erc20:totalSupply', - target: ZENBTC_ETHEREUM, - chain: 'ethereum', - }); - return BigInt(supply); - } catch (error) { - console.error(`Error querying Ethereum zenBTC supply: ${error.message}`); - return 0n; - } -} - -/** - * Main TVL function - handles both Ethereum and Solana - * Reports each chain's proportional share of the actual Bitcoin in treasury - */ -async function fetchSupplies(api) { - // Fetch all data in parallel - const [custodiedBTC, ethSupply] = await Promise.all([ - getBitcoinTVL(), - getEthereumSupply(api), - ]); - - // Query Solana supply using the DefiLlama helper - const zenbtcMint = getMintAddress(); - const solanaSupplies = await getTokenSupplies([zenbtcMint], { api }); - const solSupply = solanaSupplies[zenbtcMint] ? BigInt(solanaSupplies[zenbtcMint]) : 0n; - - return { - custodiedBTC, - ethSupply, - solSupply, - }; -} - -async function tvl(api) { - const { chain } = api; - - // Store api globally for use in async functions (like ACRED does) - global.api = api; - - // Use a single promise to ensure only one fetch happens even if called simultaneously - if (!suppliesPromise) { - suppliesPromise = fetchSupplies(api); - } - - const { custodiedBTC, ethSupply, solSupply } = await suppliesPromise; + const [btcAddresses, changeAddresses] = await Promise.all([ + getBitcoinAddresses(), + getChangeAddresses(), + ]); - console.log(`[${chain}] ethSupply: ${ethSupply}, solSupply: ${solSupply}, custodiedBTC: ${custodiedBTC}`); + const allAddresses = [...btcAddresses, ...changeAddresses]; - const totalZenBTC = ethSupply + solSupply; + if (allAddresses.length === 0) { + console.warn('No Bitcoin addresses found in treasury or change addresses'); + return { bitcoin: '0' }; + } - // Avoid division by zero - if (totalZenBTC === 0n) { + // Use Bitcoin helper to sum balances for all addresses const balances = {}; - balances['coingecko:bitcoin'] = '0'; - return balances; - } + await sumBitcoinTokens({ balances, owners: allAddresses }); - // Calculate this chain's proportional share of custodied Bitcoin - let chainSupply = 0n; - if (chain === 'ethereum') { - chainSupply = ethSupply; - } else if (chain === 'solana') { - chainSupply = solSupply; + console.log(`Bitcoin TVL from ${allAddresses.length} addresses (${btcAddresses.length} treasury + ${changeAddresses.length} change): ${balances.bitcoin} BTC`); + return balances; + } catch (error) { + console.error(`Error calculating Bitcoin TVL: ${error.message}`); + return { bitcoin: '0' }; } - - // (chainSupply / totalZenBTC) * custodiedBTC, then convert satoshis to BTC - const chainBTCAmount = Number((chainSupply * custodiedBTC) / totalZenBTC) / 1e8; - - const balances = {}; - balances['coingecko:bitcoin'] = chainBTCAmount; - - 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. zenBTC TVL represents the total Bitcoin locked, calculated as the sum of zenBTC supplies across all chains. All zenBTC is fully backed 1:1 by Bitcoin in custody, but the price of zenBTC is anticipated to increase as yield payments are made on a continuous basis.', - ethereum: { - tvl, - }, - solana: { + methodology: 'zrchain locks native Bitcoin 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, }, }; From a7b481534ba82c4e62a59d2fb5bb796cfa6e2ae1 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Wed, 29 Oct 2025 10:00:46 -0400 Subject: [PATCH 06/12] Refactor: Move zenrock address fetching to bitcoin-book fetchers helper Move treasury and change address fetching logic into the centralized bitcoin-book/fetchers.js module. This allows code reuse across Bitcoin adapters and better follows DefiLlama's organization patterns. The zenrock fetcher uses getConfig for caching and returns all 297 addresses (296 treasury + 1 change) for the adapter to use with the Bitcoin sumTokens helper. Changes: - Added zenrock fetcher to bitcoin-book/fetchers.js with getConfig caching - Updated zenrock/index.js to use the new zenrock fetcher - Simplified adapter to single tvl function that calls the fetcher --- projects/helper/bitcoin-book/fetchers.js | 77 +++++++++++++++ projects/zenrock/index.js | 113 ++--------------------- 2 files changed, 83 insertions(+), 107 deletions(-) diff --git a/projects/helper/bitcoin-book/fetchers.js b/projects/helper/bitcoin-book/fetchers.js index ba76a844146..d791af61ee2 100644 --- a/projects/helper/bitcoin-book/fetchers.js +++ b/projects/helper/bitcoin-book/fetchers.js @@ -242,4 +242,81 @@ 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; + try { + 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; + } catch (error) { + sdk.log(`Error fetching Bitcoin addresses from zrchain API: ${error.message}`); + return []; + } + } + + async function getChangeAddresses() { + const changeAddresses = []; + try { + const { data: paramsData } = await axios.get(ZENBTC_PARAMS_API); + const changeAddressKeyIDs = paramsData.params?.changeAddressKeyIDs || []; + for (const keyID of changeAddressKeyIDs) { + try { + 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); + } + } + } + } catch (error) { + sdk.log(`Error fetching change address for key ID ${keyID}: ${error.message}`); + } + } + return changeAddresses; + } catch (error) { + sdk.log(`Error fetching change addresses from zenbtc params: ${error.message}`); + return []; + } + } + + const [btcAddresses, changeAddresses] = await Promise.all([ + getBitcoinAddresses(), + getChangeAddresses(), + ]); + const allAddresses = [...btcAddresses, ...changeAddresses]; + sdk.log(`Zenrock: Fetched ${btcAddresses.length} treasury addresses + ${changeAddresses.length} change addresses = ${allAddresses.length} total`); + return allAddresses; + } + }); + }, } \ No newline at end of file diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index e442666503b..784f3640ad9 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -1,118 +1,17 @@ const { sumTokens: sumBitcoinTokens } = require('../helper/chain/bitcoin'); - -// zrchain API endpoints -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'; - -/** - * Fetches all Bitcoin mainnet addresses from zrchain treasury with pagination - * Filters for WALLET_TYPE_BTC_MAINNET type only - */ -async function getBitcoinAddresses() { - const btcAddresses = []; - let nextKey = null; - - try { - while (true) { - let url = ZRCHAIN_WALLETS_API; - if (nextKey) { - url += `?pagination.key=${encodeURIComponent(nextKey)}`; - } - - const response = await fetch(url); - const data = await response.json(); - - // Extract Bitcoin mainnet addresses from zenbtc_wallets array - 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) { - // Filter for Bitcoin mainnet addresses only - if (wallet.type === 'WALLET_TYPE_BTC_MAINNET' && wallet.address) { - btcAddresses.push(wallet.address); - } - } - } - } - } - - // Check for next page - if (data.pagination && data.pagination.next_key) { - nextKey = data.pagination.next_key; - } else { - // No more pages, exit loop - break; - } - } - - console.log(`Fetched ${btcAddresses.length} Bitcoin mainnet addresses from zrchain treasury`); - return btcAddresses; - } catch (error) { - console.error(`Error fetching Bitcoin addresses from zrchain API: ${error.message}`); - return []; - } -} - -/** - * Fetches change addresses from zenbtc params - * Queries each change address key ID to get the actual Bitcoin mainnet addresses - */ -async function getChangeAddresses() { - const changeAddresses = []; - - try { - // Fetch zenbtc params to get change address key IDs - const paramsResponse = await fetch(ZENBTC_PARAMS_API); - const paramsData = await paramsResponse.json(); - - const changeAddressKeyIDs = paramsData.params?.changeAddressKeyIDs || []; - - // Fetch each change address - for (const keyID of changeAddressKeyIDs) { - try { - const keyResponse = await fetch(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_BTC_MAINNET/`); - const keyData = await keyResponse.json(); - - // Extract Bitcoin mainnet addresses from wallets array - 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); - } - } - } - } catch (error) { - console.warn(`Error fetching change address for key ID ${keyID}: ${error.message}`); - } - } - - if (changeAddresses.length > 0) { - console.log(`Fetched ${changeAddresses.length} Bitcoin change address(es)`); - } - - return changeAddresses; - } catch (error) { - console.error(`Error fetching change addresses from zenbtc params: ${error.message}`); - return []; - } -} +const { zenrock } = require('../helper/bitcoin-book/fetchers'); /** - * Queries Bitcoin balances for all treasury and change addresses + * Queries Bitcoin balances for all zrchain treasury and change addresses * Returns balances object with Bitcoin TVL */ async function tvl() { try { - const [btcAddresses, changeAddresses] = await Promise.all([ - getBitcoinAddresses(), - getChangeAddresses(), - ]); - - const allAddresses = [...btcAddresses, ...changeAddresses]; + // Fetch all protocol addresses (treasury + change) from the bitcoin-book fetcher + const allAddresses = await zenrock(); if (allAddresses.length === 0) { - console.warn('No Bitcoin addresses found in treasury or change addresses'); + console.warn('No Bitcoin addresses found for zrchain protocol'); return { bitcoin: '0' }; } @@ -120,7 +19,7 @@ async function tvl() { const balances = {}; await sumBitcoinTokens({ balances, owners: allAddresses }); - console.log(`Bitcoin TVL from ${allAddresses.length} addresses (${btcAddresses.length} treasury + ${changeAddresses.length} change): ${balances.bitcoin} BTC`); + console.log(`Bitcoin TVL from ${allAddresses.length} zrchain addresses: ${balances.bitcoin} BTC`); return balances; } catch (error) { console.error(`Error calculating Bitcoin TVL: ${error.message}`); From ff3993d19d6878665830a37d253019090448a09e Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Fri, 31 Oct 2025 17:45:47 -0400 Subject: [PATCH 07/12] feat: add zenZEC TVL (MVP, need to fetch zcash addresses) --- projects/helper/bitcoin-book/fetchers.js | 77 ++++++++++ projects/helper/chain/zcash.js | 174 +++++++++++++++++++++++ projects/helper/env.js | 1 + projects/helper/sumTokens.js | 1 + projects/helper/tokenMapping.js | 2 +- projects/zenrock/index.js | 82 ++++++++++- 6 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 projects/helper/chain/zcash.js diff --git a/projects/helper/bitcoin-book/fetchers.js b/projects/helper/bitcoin-book/fetchers.js index d791af61ee2..7930b23b888 100644 --- a/projects/helper/bitcoin-book/fetchers.js +++ b/projects/helper/bitcoin-book/fetchers.js @@ -319,4 +319,81 @@ module.exports = { } }); }, + zenrockDCT: async () => { + const ZRCHAIN_WALLETS_API = 'https://api.diamond.zenrocklabs.io/zrchain/treasury/zenbtc_wallets'; + const DCT_PARAMS_API = 'https://api.diamond.zenrocklabs.io/dct/params'; + const ZRCHAIN_KEY_BY_ID_API = 'https://api.diamond.zenrocklabs.io/zrchain/treasury/key_by_id'; + + return getConfig('zenrock/dct_addresses', undefined, { + fetcher: async () => { + async function getZcashAddresses() { + const zecAddresses = []; + let nextKey = null; + try { + 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_ZCASH_MAINNET' && wallet.address) { + zecAddresses.push(wallet.address); + } + } + } + } + } + if (data.pagination && data.pagination.next_key) { + nextKey = data.pagination.next_key; + } else { + break; + } + } + return zecAddresses; + } catch (error) { + sdk.log(`Error fetching Zcash addresses from zrchain API: ${error.message}`); + return []; + } + } + + async function getChangeAddresses() { + const changeAddresses = []; + try { + const { data: paramsData } = await axios.get(DCT_PARAMS_API); + const changeAddressKeyIDs = paramsData.params?.assets?.[0]?.change_address_key_ids || []; + for (const keyID of changeAddressKeyIDs) { + try { + const { data: keyData } = await axios.get(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_ZCASH_MAINNET/`); + if (keyData.wallets && Array.isArray(keyData.wallets)) { + for (const wallet of keyData.wallets) { + if (wallet.type === 'WALLET_TYPE_ZCASH_MAINNET' && wallet.address) { + changeAddresses.push(wallet.address); + } + } + } + } catch (error) { + sdk.log(`Error fetching change address for key ID ${keyID}: ${error.message}`); + } + } + return changeAddresses; + } catch (error) { + sdk.log(`Error fetching change addresses from dct params: ${error.message}`); + return []; + } + } + + const [zecAddresses, changeAddresses] = await Promise.all([ + getZcashAddresses(), + getChangeAddresses(), + ]); + const allAddresses = [...zecAddresses, ...changeAddresses]; + sdk.log(`Zenrock DCT: Fetched ${zecAddresses.length} treasury addresses + ${changeAddresses.length} change addresses = ${allAddresses.length} total`); + return allAddresses; + } + }); + }, } \ No newline at end of file diff --git a/projects/helper/chain/zcash.js b/projects/helper/chain/zcash.js new file mode 100644 index 00000000000..97cd60c89b4 --- /dev/null +++ b/projects/helper/chain/zcash.js @@ -0,0 +1,174 @@ +const sdk = require('@defillama/sdk') +const { get, post } = require('../http') +const { getEnv } = require('../env') +const { getUniqueAddresses } = require('../tokenMapping') +const { RateLimiter } = require("limiter"); +const { sliceIntoChunks, sleep } = require('../utils'); + +// Zcash block explorer API endpoints +const url = addr => 'https://api.zcha.in/v2/mainnet/accounts/' + addr +const url2 = addr => 'https://api.zcashnetwork.io/api/v1/addresses/' + addr + '/balance' + +const delay = 3 * 60 * 60 // 3 hours +const balancesNow = {} + +const zcashCacheEnv = getEnv('ZCASH_CACHE_API') + +const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 10_000 }); + +async function cachedZECBalCall(owners, retriesLeft = 2) { + try { + const res = await post(zcashCacheEnv, { addresses: owners, network: 'ZEC' }) + return res + } catch (e) { + console.error('cachedZECBalCall error', e.toString()) + if (retriesLeft > 0) { + return await cachedZECBalCall(owners, retriesLeft - 1) + } + throw e + } +} + +async function getCachedZcashBalances(owners) { + const chunks = sliceIntoChunks(owners, 700) + sdk.log('zcash cache api call: ', owners.length, chunks.length) + let sum = 0 + let i = 0 + for (const chunk of chunks) { + const res = await cachedZECBalCall(chunk) + sdk.log(i++, sum / 1e8, res / 1e8, chunk.length) + sum += +res + } + return sum +} + +async function _sumTokensBlockchain({ balances = {}, owners = [], forceCacheUse, }) { + if (zcashCacheEnv && owners.length > 51) { + if (owners.length > 1000) forceCacheUse = true + try { + const res = await getCachedZcashBalances(owners) + sdk.util.sumSingleBalance(balances, 'zcash', res / 1e8) + return balances + + } catch (e) { + if (forceCacheUse) throw e + sdk.log('zcash cache error', e.toString()) + } + } + console.time('zcash' + owners.length + '___' + owners[0]) + const STEP = 10 // Smaller batches for Zcash API + for (let i = 0; i < owners.length; i += STEP) { + const chunk = owners.slice(i, i + STEP) + // Query addresses individually since batch APIs aren't reliable + for (const addr of chunk) { + try { + const balance = await getBalanceNow(addr) + sdk.util.sumSingleBalance(balances, 'zcash', balance) + } catch (err) { + sdk.log('zcash balance error', addr, err.toString()) + } + } + await sleep(2000) // Rate limiting + } + + console.timeEnd('zcash' + owners.length + '___' + owners[0]) + return balances +} + +const withLimiter = (fn, tokensToRemove = 1) => async (...args) => { + await limiter.removeTokens(tokensToRemove); + return fn(...args); +} + +const sumTokensBlockchain = withLimiter(_sumTokensBlockchain) + +async function getBalanceNow(addr) { + if (balancesNow[addr]) return balancesNow[addr] + try { + // Try zcha.in API first - returns balance in Zatoshi (smallest unit, like satoshis) + const response = await get(url(addr)) + if (response && (response.balance !== undefined || response.balance !== null)) { + balancesNow[addr] = (response.balance || 0) / 1e8 + return balancesNow[addr] + } + } catch (e) { + sdk.log('zcha.in zcash balance error', addr, e.toString()) + } + + try { + // Fallback to zcashnetwork.io API + const response = await get(url2(addr)) + if (response && (response.balance !== undefined || response.balance !== null)) { + balancesNow[addr] = (response.balance || 0) / 1e8 + return balancesNow[addr] + } + } catch (e) { + sdk.log('zcashnetwork.io balance error', addr, e.toString()) + } + + // Default to 0 if no balance found + balancesNow[addr] = 0 + return balancesNow[addr] +} + +async function sumTokens({ balances = {}, owners = [], timestamp, forceCacheUse, }) { + if (typeof timestamp === "object" && timestamp.timestamp) timestamp = timestamp.timestamp + owners = getUniqueAddresses(owners, 'zcash') + const now = Date.now() / 1e3 + + if (!timestamp || (now - timestamp) < delay) { + try { + await sumTokensBlockchain({ balances, owners, forceCacheUse }) + return balances + } catch (e) { + sdk.log('zcash sumTokens error', e.toString()) + } + } + if (forceCacheUse) throw new Error('timestamp is too old, cant pull with forceCacheUse flag set') + + for (const addr of owners) + sdk.util.sumSingleBalance(balances, 'zcash', await getBalance(addr, timestamp)) + return balances +} + +// get archive ZEC balance +async function getBalance(addr, timestamp) { + try { + const endpoint = url(addr) + '/transactions' + const response = await get(endpoint) + const txs = response.data?.[addr]?.transactions || [] + + let balance = 0 + for (const tx of txs) { + if (tx.time && tx.time <= timestamp) { + // Process outputs (received) + if (tx.outputs) { + for (const output of tx.outputs) { + if (output.recipient === addr) { + balance += (output.value || 0) / 1e8 + } + } + } + // Process inputs (spent) + if (tx.inputs) { + for (const input of tx.inputs) { + if (input.recipient === addr) { + balance -= (input.value || 0) / 1e8 + } + } + } + } + } + + return balance + } catch (e) { + sdk.log('zcash getBalance error', addr, e.toString()) + // Fallback to current balance if historical lookup fails + return await getBalanceNow(addr) + } +} + +module.exports = { + sumTokens +} + diff --git a/projects/helper/env.js b/projects/helper/env.js index 407a509802d..82a83e9b2c1 100644 --- a/projects/helper/env.js +++ b/projects/helper/env.js @@ -66,6 +66,7 @@ const ENV_KEYS = [ 'RPC_PROXY_URL', 'BLACKSAIL_API_KEY', 'BITCOIN_CACHE_API', + 'ZCASH_CACHE_API', 'DEBANK_API_KEY', 'SMARDEX_SUBGRAPH_API_KEY', 'PROXY_AUTH', diff --git a/projects/helper/sumTokens.js b/projects/helper/sumTokens.js index 7320cb0b34a..dacfadbac1e 100644 --- a/projects/helper/sumTokens.js +++ b/projects/helper/sumTokens.js @@ -19,6 +19,7 @@ const helpers = { "near": require("./chain/near"), "bitcoin": require("./chain/bitcoin"), "litecoin": require("./chain/litecoin"), + "zcash": require("./chain/zcash"), "polkadot": require("./chain/polkadot"), "acala": require("./chain/acala"), "bifrost": require("./chain/bifrost"), diff --git a/projects/helper/tokenMapping.js b/projects/helper/tokenMapping.js index 7430b1db30d..95afce209da 100644 --- a/projects/helper/tokenMapping.js +++ b/projects/helper/tokenMapping.js @@ -22,7 +22,7 @@ const ibcChains = ['ibc', 'terra', 'terra2', 'crescent', 'osmosis', 'kujira', 's 'kopi', 'elys', "pryzm", "mantra", 'agoric', 'band', 'celestia', 'dydx', 'carbon', 'milkyway', 'regen', 'sommelier', 'stride', 'prom', 'babylon', 'xion' ] -const caseSensitiveChains = [...ibcChains, ...svmChains, 'tezos', 'ton', 'algorand', 'aptos', 'near', 'bitcoin', 'waves', 'tron', 'litecoin', 'polkadot', 'ripple', 'elrond', 'cardano', 'stacks', 'sui', 'ergo', 'mvc', 'renec', 'doge', 'stellar', 'massa', +const caseSensitiveChains = [...ibcChains, ...svmChains, 'tezos', 'ton', 'algorand', 'aptos', 'near', 'bitcoin', 'waves', 'tron', 'litecoin', 'polkadot', 'ripple', 'elrond', 'cardano', 'stacks', 'sui', 'ergo', 'mvc', 'renec', 'doge', 'stellar', 'massa', 'zcash', 'eclipse', 'acala', 'aelf', 'aeternity', 'alephium', 'bifrost', 'bittensor', 'verus', ] diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index 784f3640ad9..ae3c3b8ed1b 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -1,5 +1,6 @@ const { sumTokens: sumBitcoinTokens } = require('../helper/chain/bitcoin'); -const { zenrock } = require('../helper/bitcoin-book/fetchers'); +const { sumTokens: sumZcashTokens } = require('../helper/chain/zcash'); +const { zenrock, zenrockDCT } = require('../helper/bitcoin-book/fetchers'); /** * Queries Bitcoin balances for all zrchain treasury and change addresses @@ -27,11 +28,90 @@ async function tvl() { } } +/** + * 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() { + try { + // 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); + + console.log(`Zcash TVL from custodied amount: ${custodiedZEC} ZEC`); + } else { + console.warn('No ASSET_ZENZEC custodied_amount found in DCT supply data'); + balances.zcash = '0'; + } + + return balances; + } catch (error) { + console.error(`Error calculating Zcash TVL: ${error.message}`); + return { zcash: '0' }; + } +} + +// NOTE: Address-based implementation commented out for now - will be used when address queries are working +/* +async function zcashTvlAddressBased() { + try { + // Fetch all protocol addresses (treasury + change) from the bitcoin-book fetcher + const allAddresses = await zenrockDCT(); + + if (allAddresses.length === 0) { + console.warn('No Zcash addresses found for zrchain protocol'); + return { zcash: '0' }; + } + + // Use Zcash helper to sum balances for all addresses + const balances = {}; + await sumZcashTokens({ balances, owners: allAddresses }); + + console.log(`Zcash TVL from ${allAddresses.length} zrchain addresses: ${balances.zcash} ZEC`); + return balances; + } catch (error) { + console.error(`Error calculating Zcash TVL: ${error.message}`); + return { zcash: '0' }; + } +} +*/ + module.exports = { methodology: 'zrchain locks native Bitcoin 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, + }, }; // node test.js projects/zenrock/index.js From e30cefce32305d780cca472ab49604045dca4586 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 3 Nov 2025 09:58:04 -0500 Subject: [PATCH 08/12] Refactor: Remove try-catch blocks and clean up unused code Address PR review feedback: 1. Removed all try-catch blocks that swallow errors - allow hard failures for monitoring 2. Removed console.log/console.error statements, rely on sdk.log 3. Cleaned up zcash.js: - Removed unused address-based balance functions - Removed fallback API endpoints (zcha.in, zcashnetwork.io) - Removed getBalanceNow and getBalance functions - Kept only cache API call functions - Changed cache delay from 3 hours to 1 hour 4. Removed zenrockDCT import from zenrock adapter (not currently used) 5. Removed commented-out address-based zcash TVL function 6. Simplified adapter code Test results: Adapter exports 303 total addresses (bitcoin + zcash): - Bitcoin: 8.00 M from 296 treasury + 1 change address - Zcash: 188.07k (supply-based approach) --- projects/helper/bitcoin-book/fetchers.js | 140 ++++++++++------------- projects/helper/chain/zcash.js | 129 ++------------------- projects/zenrock/index.js | 113 ++++++------------ 3 files changed, 103 insertions(+), 279 deletions(-) diff --git a/projects/helper/bitcoin-book/fetchers.js b/projects/helper/bitcoin-book/fetchers.js index 7930b23b888..36f538483c3 100644 --- a/projects/helper/bitcoin-book/fetchers.js +++ b/projects/helper/bitcoin-book/fetchers.js @@ -252,61 +252,49 @@ module.exports = { async function getBitcoinAddresses() { const btcAddresses = []; let nextKey = null; - try { - 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); - } + + 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; - } catch (error) { - sdk.log(`Error fetching Bitcoin addresses from zrchain API: ${error.message}`); - return []; + if (data.pagination && data.pagination.next_key) { + nextKey = data.pagination.next_key; + } else { + break; + } } + return btcAddresses; } async function getChangeAddresses() { const changeAddresses = []; - try { - const { data: paramsData } = await axios.get(ZENBTC_PARAMS_API); - const changeAddressKeyIDs = paramsData.params?.changeAddressKeyIDs || []; - for (const keyID of changeAddressKeyIDs) { - try { - 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); - } - } + + 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); } - } catch (error) { - sdk.log(`Error fetching change address for key ID ${keyID}: ${error.message}`); } } - return changeAddresses; - } catch (error) { - sdk.log(`Error fetching change addresses from zenbtc params: ${error.message}`); - return []; } + return changeAddresses; } const [btcAddresses, changeAddresses] = await Promise.all([ @@ -329,61 +317,49 @@ module.exports = { async function getZcashAddresses() { const zecAddresses = []; let nextKey = null; - try { - 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_ZCASH_MAINNET' && wallet.address) { - zecAddresses.push(wallet.address); - } + + 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_ZCASH_MAINNET' && wallet.address) { + zecAddresses.push(wallet.address); } } } } - if (data.pagination && data.pagination.next_key) { - nextKey = data.pagination.next_key; - } else { - break; - } } - return zecAddresses; - } catch (error) { - sdk.log(`Error fetching Zcash addresses from zrchain API: ${error.message}`); - return []; + if (data.pagination && data.pagination.next_key) { + nextKey = data.pagination.next_key; + } else { + break; + } } + return zecAddresses; } async function getChangeAddresses() { const changeAddresses = []; - try { - const { data: paramsData } = await axios.get(DCT_PARAMS_API); - const changeAddressKeyIDs = paramsData.params?.assets?.[0]?.change_address_key_ids || []; - for (const keyID of changeAddressKeyIDs) { - try { - const { data: keyData } = await axios.get(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_ZCASH_MAINNET/`); - if (keyData.wallets && Array.isArray(keyData.wallets)) { - for (const wallet of keyData.wallets) { - if (wallet.type === 'WALLET_TYPE_ZCASH_MAINNET' && wallet.address) { - changeAddresses.push(wallet.address); - } - } + + const { data: paramsData } = await axios.get(DCT_PARAMS_API); + const changeAddressKeyIDs = paramsData.params?.assets?.[0]?.change_address_key_ids || []; + for (const keyID of changeAddressKeyIDs) { + const { data: keyData } = await axios.get(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_ZCASH_MAINNET/`); + if (keyData.wallets && Array.isArray(keyData.wallets)) { + for (const wallet of keyData.wallets) { + if (wallet.type === 'WALLET_TYPE_ZCASH_MAINNET' && wallet.address) { + changeAddresses.push(wallet.address); } - } catch (error) { - sdk.log(`Error fetching change address for key ID ${keyID}: ${error.message}`); } } - return changeAddresses; - } catch (error) { - sdk.log(`Error fetching change addresses from dct params: ${error.message}`); - return []; } + return changeAddresses; } const [zecAddresses, changeAddresses] = await Promise.all([ diff --git a/projects/helper/chain/zcash.js b/projects/helper/chain/zcash.js index 97cd60c89b4..63434a8dfb6 100644 --- a/projects/helper/chain/zcash.js +++ b/projects/helper/chain/zcash.js @@ -1,15 +1,11 @@ const sdk = require('@defillama/sdk') -const { get, post } = require('../http') +const { post } = require('../http') const { getEnv } = require('../env') const { getUniqueAddresses } = require('../tokenMapping') const { RateLimiter } = require("limiter"); const { sliceIntoChunks, sleep } = require('../utils'); -// Zcash block explorer API endpoints -const url = addr => 'https://api.zcha.in/v2/mainnet/accounts/' + addr -const url2 = addr => 'https://api.zcashnetwork.io/api/v1/addresses/' + addr + '/balance' - -const delay = 3 * 60 * 60 // 3 hours +const delay = 60 * 60 // 1 hour const balancesNow = {} const zcashCacheEnv = getEnv('ZCASH_CACHE_API') @@ -17,16 +13,8 @@ const zcashCacheEnv = getEnv('ZCASH_CACHE_API') const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 10_000 }); async function cachedZECBalCall(owners, retriesLeft = 2) { - try { - const res = await post(zcashCacheEnv, { addresses: owners, network: 'ZEC' }) - return res - } catch (e) { - console.error('cachedZECBalCall error', e.toString()) - if (retriesLeft > 0) { - return await cachedZECBalCall(owners, retriesLeft - 1) - } - throw e - } + const res = await post(zcashCacheEnv, { addresses: owners, network: 'ZEC' }) + return res } async function getCachedZcashBalances(owners) { @@ -45,34 +33,12 @@ async function getCachedZcashBalances(owners) { async function _sumTokensBlockchain({ balances = {}, owners = [], forceCacheUse, }) { if (zcashCacheEnv && owners.length > 51) { if (owners.length > 1000) forceCacheUse = true - try { - const res = await getCachedZcashBalances(owners) - sdk.util.sumSingleBalance(balances, 'zcash', res / 1e8) - return balances - - } catch (e) { - if (forceCacheUse) throw e - sdk.log('zcash cache error', e.toString()) - } - } - console.time('zcash' + owners.length + '___' + owners[0]) - const STEP = 10 // Smaller batches for Zcash API - for (let i = 0; i < owners.length; i += STEP) { - const chunk = owners.slice(i, i + STEP) - // Query addresses individually since batch APIs aren't reliable - for (const addr of chunk) { - try { - const balance = await getBalanceNow(addr) - sdk.util.sumSingleBalance(balances, 'zcash', balance) - } catch (err) { - sdk.log('zcash balance error', addr, err.toString()) - } - } - await sleep(2000) // Rate limiting + const res = await getCachedZcashBalances(owners) + sdk.util.sumSingleBalance(balances, 'zcash', res / 1e8) + return balances } - console.timeEnd('zcash' + owners.length + '___' + owners[0]) - return balances + throw new Error('ZCASH_CACHE_API environment variable not configured') } const withLimiter = (fn, tokensToRemove = 1) => async (...args) => { @@ -82,90 +48,17 @@ const withLimiter = (fn, tokensToRemove = 1) => async (...args) => { const sumTokensBlockchain = withLimiter(_sumTokensBlockchain) -async function getBalanceNow(addr) { - if (balancesNow[addr]) return balancesNow[addr] - try { - // Try zcha.in API first - returns balance in Zatoshi (smallest unit, like satoshis) - const response = await get(url(addr)) - if (response && (response.balance !== undefined || response.balance !== null)) { - balancesNow[addr] = (response.balance || 0) / 1e8 - return balancesNow[addr] - } - } catch (e) { - sdk.log('zcha.in zcash balance error', addr, e.toString()) - } - - try { - // Fallback to zcashnetwork.io API - const response = await get(url2(addr)) - if (response && (response.balance !== undefined || response.balance !== null)) { - balancesNow[addr] = (response.balance || 0) / 1e8 - return balancesNow[addr] - } - } catch (e) { - sdk.log('zcashnetwork.io balance error', addr, e.toString()) - } - - // Default to 0 if no balance found - balancesNow[addr] = 0 - return balancesNow[addr] -} - async function sumTokens({ balances = {}, owners = [], timestamp, forceCacheUse, }) { if (typeof timestamp === "object" && timestamp.timestamp) timestamp = timestamp.timestamp owners = getUniqueAddresses(owners, 'zcash') const now = Date.now() / 1e3 if (!timestamp || (now - timestamp) < delay) { - try { - await sumTokensBlockchain({ balances, owners, forceCacheUse }) - return balances - } catch (e) { - sdk.log('zcash sumTokens error', e.toString()) - } + await sumTokensBlockchain({ balances, owners, forceCacheUse }) + return balances } - if (forceCacheUse) throw new Error('timestamp is too old, cant pull with forceCacheUse flag set') - - for (const addr of owners) - sdk.util.sumSingleBalance(balances, 'zcash', await getBalance(addr, timestamp)) - return balances -} -// get archive ZEC balance -async function getBalance(addr, timestamp) { - try { - const endpoint = url(addr) + '/transactions' - const response = await get(endpoint) - const txs = response.data?.[addr]?.transactions || [] - - let balance = 0 - for (const tx of txs) { - if (tx.time && tx.time <= timestamp) { - // Process outputs (received) - if (tx.outputs) { - for (const output of tx.outputs) { - if (output.recipient === addr) { - balance += (output.value || 0) / 1e8 - } - } - } - // Process inputs (spent) - if (tx.inputs) { - for (const input of tx.inputs) { - if (input.recipient === addr) { - balance -= (input.value || 0) / 1e8 - } - } - } - } - } - - return balance - } catch (e) { - sdk.log('zcash getBalance error', addr, e.toString()) - // Fallback to current balance if historical lookup fails - return await getBalanceNow(addr) - } + throw new Error('timestamp is too old, cant pull with forceCacheUse flag set') } module.exports = { diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index ae3c3b8ed1b..d152391026f 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -1,108 +1,65 @@ const { sumTokens: sumBitcoinTokens } = require('../helper/chain/bitcoin'); -const { sumTokens: sumZcashTokens } = require('../helper/chain/zcash'); -const { zenrock, zenrockDCT } = require('../helper/bitcoin-book/fetchers'); +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() { - try { - // Fetch all protocol addresses (treasury + change) from the bitcoin-book fetcher - const allAddresses = await zenrock(); + // Fetch all protocol addresses (treasury + change) from the bitcoin-book fetcher + const allAddresses = await zenrock(); - if (allAddresses.length === 0) { - console.warn('No Bitcoin addresses found for zrchain protocol'); - return { bitcoin: '0' }; - } - - // Use Bitcoin helper to sum balances for all addresses - const balances = {}; - await sumBitcoinTokens({ balances, owners: allAddresses }); - - console.log(`Bitcoin TVL from ${allAddresses.length} zrchain addresses: ${balances.bitcoin} BTC`); - return balances; - } catch (error) { - console.error(`Error calculating Bitcoin TVL: ${error.message}`); + 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() { - try { - // 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 = {}; + // Fetch custodied amount from DCT supply endpoint + const { get } = require('../helper/http'); + const { getConfig } = require('../helper/cache'); + const sdk = require('@defillama/sdk'); - // Find ASSET_ZENZEC in supplies array - const zenZecSupply = supplyData.supplies?.find( - item => item.supply?.asset === 'ASSET_ZENZEC' - ); + const DCT_SUPPLY_API = 'https://api.diamond.zenrocklabs.io/dct/supply'; - 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); - - console.log(`Zcash TVL from custodied amount: ${custodiedZEC} ZEC`); - } else { - console.warn('No ASSET_ZENZEC custodied_amount found in DCT supply data'); - balances.zcash = '0'; + const supplyData = await getConfig('zenrock/dct_supply', DCT_SUPPLY_API, { + fetcher: async () => { + const response = await get(DCT_SUPPLY_API); + return response; } + }); - return balances; - } catch (error) { - console.error(`Error calculating Zcash TVL: ${error.message}`); - return { zcash: '0' }; - } -} - -// NOTE: Address-based implementation commented out for now - will be used when address queries are working -/* -async function zcashTvlAddressBased() { - try { - // Fetch all protocol addresses (treasury + change) from the bitcoin-book fetcher - const allAddresses = await zenrockDCT(); + const balances = {}; - if (allAddresses.length === 0) { - console.warn('No Zcash addresses found for zrchain protocol'); - return { zcash: '0' }; - } + // Find ASSET_ZENZEC in supplies array + const zenZecSupply = supplyData.supplies?.find( + item => item.supply?.asset === 'ASSET_ZENZEC' + ); - // Use Zcash helper to sum balances for all addresses - const balances = {}; - await sumZcashTokens({ balances, owners: allAddresses }); + 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; - console.log(`Zcash TVL from ${allAddresses.length} zrchain addresses: ${balances.zcash} ZEC`); - return balances; - } catch (error) { - console.error(`Error calculating Zcash TVL: ${error.message}`); - return { zcash: '0' }; + sdk.util.sumSingleBalance(balances, 'zcash', custodiedZEC); } + + return balances; } -*/ module.exports = { methodology: 'zrchain locks native Bitcoin 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.', @@ -113,5 +70,3 @@ module.exports = { tvl: zcashTvl, }, }; - -// node test.js projects/zenrock/index.js From f6c276209260b277ef3e23a0a97e2727796aa165 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 3 Nov 2025 15:38:27 -0500 Subject: [PATCH 09/12] clean up unused code --- projects/helper/bitcoin-book/fetchers.js | 2 - projects/helper/chain/zcash.js | 67 ------------------------ projects/helper/env.js | 5 +- projects/helper/sumTokens.js | 1 - projects/helper/tokenMapping.js | 2 +- 5 files changed, 3 insertions(+), 74 deletions(-) delete mode 100644 projects/helper/chain/zcash.js diff --git a/projects/helper/bitcoin-book/fetchers.js b/projects/helper/bitcoin-book/fetchers.js index 36f538483c3..cfd16f353ad 100644 --- a/projects/helper/bitcoin-book/fetchers.js +++ b/projects/helper/bitcoin-book/fetchers.js @@ -302,7 +302,6 @@ module.exports = { getChangeAddresses(), ]); const allAddresses = [...btcAddresses, ...changeAddresses]; - sdk.log(`Zenrock: Fetched ${btcAddresses.length} treasury addresses + ${changeAddresses.length} change addresses = ${allAddresses.length} total`); return allAddresses; } }); @@ -367,7 +366,6 @@ module.exports = { getChangeAddresses(), ]); const allAddresses = [...zecAddresses, ...changeAddresses]; - sdk.log(`Zenrock DCT: Fetched ${zecAddresses.length} treasury addresses + ${changeAddresses.length} change addresses = ${allAddresses.length} total`); return allAddresses; } }); diff --git a/projects/helper/chain/zcash.js b/projects/helper/chain/zcash.js deleted file mode 100644 index 63434a8dfb6..00000000000 --- a/projects/helper/chain/zcash.js +++ /dev/null @@ -1,67 +0,0 @@ -const sdk = require('@defillama/sdk') -const { post } = require('../http') -const { getEnv } = require('../env') -const { getUniqueAddresses } = require('../tokenMapping') -const { RateLimiter } = require("limiter"); -const { sliceIntoChunks, sleep } = require('../utils'); - -const delay = 60 * 60 // 1 hour -const balancesNow = {} - -const zcashCacheEnv = getEnv('ZCASH_CACHE_API') - -const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 10_000 }); - -async function cachedZECBalCall(owners, retriesLeft = 2) { - const res = await post(zcashCacheEnv, { addresses: owners, network: 'ZEC' }) - return res -} - -async function getCachedZcashBalances(owners) { - const chunks = sliceIntoChunks(owners, 700) - sdk.log('zcash cache api call: ', owners.length, chunks.length) - let sum = 0 - let i = 0 - for (const chunk of chunks) { - const res = await cachedZECBalCall(chunk) - sdk.log(i++, sum / 1e8, res / 1e8, chunk.length) - sum += +res - } - return sum -} - -async function _sumTokensBlockchain({ balances = {}, owners = [], forceCacheUse, }) { - if (zcashCacheEnv && owners.length > 51) { - if (owners.length > 1000) forceCacheUse = true - const res = await getCachedZcashBalances(owners) - sdk.util.sumSingleBalance(balances, 'zcash', res / 1e8) - return balances - } - - throw new Error('ZCASH_CACHE_API environment variable not configured') -} - -const withLimiter = (fn, tokensToRemove = 1) => async (...args) => { - await limiter.removeTokens(tokensToRemove); - return fn(...args); -} - -const sumTokensBlockchain = withLimiter(_sumTokensBlockchain) - -async function sumTokens({ balances = {}, owners = [], timestamp, forceCacheUse, }) { - if (typeof timestamp === "object" && timestamp.timestamp) timestamp = timestamp.timestamp - owners = getUniqueAddresses(owners, 'zcash') - const now = Date.now() / 1e3 - - if (!timestamp || (now - timestamp) < delay) { - await sumTokensBlockchain({ balances, owners, forceCacheUse }) - return balances - } - - throw new Error('timestamp is too old, cant pull with forceCacheUse flag set') -} - -module.exports = { - sumTokens -} - diff --git a/projects/helper/env.js b/projects/helper/env.js index 82a83e9b2c1..e776beaf733 100644 --- a/projects/helper/env.js +++ b/projects/helper/env.js @@ -38,8 +38,8 @@ const DEFAULTS = { NIBIRU_RPC: "https://evm-rpc.archive.nibiru.fi/", IOTA_RPC: "https://api.mainnet.iota.cafe", KAVA_ARCHIVAL_RPC: "https://evm.kava.io", - BIFROST_P_RPC: "wss://api-bifrost-polkadot.n.dwellir.com/"+_yek, - BIFROST_K_RPC: "wss://api-bifrost-kusama.n.dwellir.com/"+_yek, + BIFROST_P_RPC: "wss://api-bifrost-polkadot.n.dwellir.com/" + _yek, + BIFROST_K_RPC: "wss://api-bifrost-kusama.n.dwellir.com/" + _yek, HYDRAGON_RPC: "https://rpc-mainnet.hydrachain.org", TAC_RPC: "https://rpc.tac.build", FRAXTAL_RPC: "https://rpc.frax.com", @@ -66,7 +66,6 @@ const ENV_KEYS = [ 'RPC_PROXY_URL', 'BLACKSAIL_API_KEY', 'BITCOIN_CACHE_API', - 'ZCASH_CACHE_API', 'DEBANK_API_KEY', 'SMARDEX_SUBGRAPH_API_KEY', 'PROXY_AUTH', diff --git a/projects/helper/sumTokens.js b/projects/helper/sumTokens.js index dacfadbac1e..7320cb0b34a 100644 --- a/projects/helper/sumTokens.js +++ b/projects/helper/sumTokens.js @@ -19,7 +19,6 @@ const helpers = { "near": require("./chain/near"), "bitcoin": require("./chain/bitcoin"), "litecoin": require("./chain/litecoin"), - "zcash": require("./chain/zcash"), "polkadot": require("./chain/polkadot"), "acala": require("./chain/acala"), "bifrost": require("./chain/bifrost"), diff --git a/projects/helper/tokenMapping.js b/projects/helper/tokenMapping.js index 95afce209da..7430b1db30d 100644 --- a/projects/helper/tokenMapping.js +++ b/projects/helper/tokenMapping.js @@ -22,7 +22,7 @@ const ibcChains = ['ibc', 'terra', 'terra2', 'crescent', 'osmosis', 'kujira', 's 'kopi', 'elys', "pryzm", "mantra", 'agoric', 'band', 'celestia', 'dydx', 'carbon', 'milkyway', 'regen', 'sommelier', 'stride', 'prom', 'babylon', 'xion' ] -const caseSensitiveChains = [...ibcChains, ...svmChains, 'tezos', 'ton', 'algorand', 'aptos', 'near', 'bitcoin', 'waves', 'tron', 'litecoin', 'polkadot', 'ripple', 'elrond', 'cardano', 'stacks', 'sui', 'ergo', 'mvc', 'renec', 'doge', 'stellar', 'massa', 'zcash', +const caseSensitiveChains = [...ibcChains, ...svmChains, 'tezos', 'ton', 'algorand', 'aptos', 'near', 'bitcoin', 'waves', 'tron', 'litecoin', 'polkadot', 'ripple', 'elrond', 'cardano', 'stacks', 'sui', 'ergo', 'mvc', 'renec', 'doge', 'stellar', 'massa', 'eclipse', 'acala', 'aelf', 'aeternity', 'alephium', 'bifrost', 'bittensor', 'verus', ] From 5eb4e62094917e03ac10ffcab7afcab069ed8045 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 3 Nov 2025 15:39:42 -0500 Subject: [PATCH 10/12] undo formatting change --- projects/helper/env.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/helper/env.js b/projects/helper/env.js index e776beaf733..407a509802d 100644 --- a/projects/helper/env.js +++ b/projects/helper/env.js @@ -38,8 +38,8 @@ const DEFAULTS = { NIBIRU_RPC: "https://evm-rpc.archive.nibiru.fi/", IOTA_RPC: "https://api.mainnet.iota.cafe", KAVA_ARCHIVAL_RPC: "https://evm.kava.io", - BIFROST_P_RPC: "wss://api-bifrost-polkadot.n.dwellir.com/" + _yek, - BIFROST_K_RPC: "wss://api-bifrost-kusama.n.dwellir.com/" + _yek, + BIFROST_P_RPC: "wss://api-bifrost-polkadot.n.dwellir.com/"+_yek, + BIFROST_K_RPC: "wss://api-bifrost-kusama.n.dwellir.com/"+_yek, HYDRAGON_RPC: "https://rpc-mainnet.hydrachain.org", TAC_RPC: "https://rpc.tac.build", FRAXTAL_RPC: "https://rpc.frax.com", From 9198df5de2fc0959c9b119f322524264d13fdbf9 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 3 Nov 2025 15:41:42 -0500 Subject: [PATCH 11/12] remove unused zenrockDCT --- projects/helper/bitcoin-book/fetchers.js | 64 ------------------------ 1 file changed, 64 deletions(-) diff --git a/projects/helper/bitcoin-book/fetchers.js b/projects/helper/bitcoin-book/fetchers.js index cfd16f353ad..b72da76de99 100644 --- a/projects/helper/bitcoin-book/fetchers.js +++ b/projects/helper/bitcoin-book/fetchers.js @@ -306,68 +306,4 @@ module.exports = { } }); }, - zenrockDCT: async () => { - const ZRCHAIN_WALLETS_API = 'https://api.diamond.zenrocklabs.io/zrchain/treasury/zenbtc_wallets'; - const DCT_PARAMS_API = 'https://api.diamond.zenrocklabs.io/dct/params'; - const ZRCHAIN_KEY_BY_ID_API = 'https://api.diamond.zenrocklabs.io/zrchain/treasury/key_by_id'; - - return getConfig('zenrock/dct_addresses', undefined, { - fetcher: async () => { - async function getZcashAddresses() { - const zecAddresses = []; - 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_ZCASH_MAINNET' && wallet.address) { - zecAddresses.push(wallet.address); - } - } - } - } - } - if (data.pagination && data.pagination.next_key) { - nextKey = data.pagination.next_key; - } else { - break; - } - } - return zecAddresses; - } - - async function getChangeAddresses() { - const changeAddresses = []; - - const { data: paramsData } = await axios.get(DCT_PARAMS_API); - const changeAddressKeyIDs = paramsData.params?.assets?.[0]?.change_address_key_ids || []; - for (const keyID of changeAddressKeyIDs) { - const { data: keyData } = await axios.get(`${ZRCHAIN_KEY_BY_ID_API}/${keyID}/WALLET_TYPE_ZCASH_MAINNET/`); - if (keyData.wallets && Array.isArray(keyData.wallets)) { - for (const wallet of keyData.wallets) { - if (wallet.type === 'WALLET_TYPE_ZCASH_MAINNET' && wallet.address) { - changeAddresses.push(wallet.address); - } - } - } - } - return changeAddresses; - } - - const [zecAddresses, changeAddresses] = await Promise.all([ - getZcashAddresses(), - getChangeAddresses(), - ]); - const allAddresses = [...zecAddresses, ...changeAddresses]; - return allAddresses; - } - }); - }, } \ No newline at end of file From c5b054b6c4e342caad01e47fe155141da554f788 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Mon, 3 Nov 2025 18:04:01 -0500 Subject: [PATCH 12/12] change Bitcoin->assets --- projects/zenrock/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/zenrock/index.js b/projects/zenrock/index.js index d152391026f..256a7a2ccc9 100644 --- a/projects/zenrock/index.js +++ b/projects/zenrock/index.js @@ -62,7 +62,7 @@ async function zcashTvl() { } module.exports = { - methodology: 'zrchain locks native Bitcoin 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.', + 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, },