From df618fc2cfb90b7956a479a69d0ef2ba28d521b9 Mon Sep 17 00:00:00 2001 From: SmolinPavel Date: Mon, 1 Dec 2025 23:04:23 +0100 Subject: [PATCH] feat: calculate templar assets on native chains --- projects/templarfi/index.js | 111 ++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/projects/templarfi/index.js b/projects/templarfi/index.js index 5e91975e99c..5c746833836 100644 --- a/projects/templarfi/index.js +++ b/projects/templarfi/index.js @@ -12,6 +12,7 @@ const CALL_TIMEOUT_MS = 30000 const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)) function detectCrossChainToken(tokenId) { + // Stellar tokens via HOT omnichain bridge (chain ID 1100) if (tokenId.includes('v2_1.omni.hot.tg:1100_')) { const stellarMappings = { '111bzQBB5v7AhLyPMDwS8uJgQV24KaAPXtwyVWu2KXbbfQU6NXRCz': 'coingecko:stellar', @@ -19,17 +20,23 @@ function detectCrossChainToken(tokenId) { } const match = tokenId.match(/1100_([a-zA-Z0-9]+)$/) if (match && stellarMappings[match[1]]) { - return stellarMappings[match[1]] + return { chain: 'stellar', token: stellarMappings[match[1]] } } } - if (tokenId === 'btc.omft.near') return 'coingecko:bitcoin' + // Bitcoin via omnichain bridge + if (tokenId === 'btc.omft.near') { + return { chain: 'bitcoin', token: 'coingecko:bitcoin' } + } + + // Ethereum tokens via omnichain bridge if (tokenId.match(/^eth-0x([a-fA-F0-9]{40})\.omft\.near$/)) { const match = tokenId.match(/^eth-0x([a-fA-F0-9]{40})\.omft\.near$/) - return `ethereum:0x${match[1].toLowerCase()}` + return { chain: 'ethereum', token: `ethereum:0x${match[1].toLowerCase()}` } } - return tokenId + // Native NEAR tokens + return { chain: 'near', token: tokenId } } async function withRetry(fn, maxAttempts = MAX_RETRY_ATTEMPTS, delayMs = RETRY_DELAY_MS) { @@ -82,7 +89,7 @@ function extractTokenAddress(assetConfig, assetType) { } const crossChainResult = detectCrossChainToken(tokenId) - if (crossChainResult !== tokenId) { + if (crossChainResult.chain !== 'near') { return crossChainResult } @@ -94,7 +101,7 @@ function extractTokenAddress(assetConfig, assetType) { return detectCrossChainToken(parts[1]) } - return tokenId + return { chain: 'near', token: tokenId } } throw new Error(`Unsupported ${assetType} asset format: missing both Nep141 and valid Nep245`) @@ -106,8 +113,8 @@ function validateConfiguration(configuration) { if (!configuration.collateral_asset) throw new Error('Missing collateral_asset in configuration') } -function scaleTokenAmount(amount, tokenAddress, decimals) { - if (tokenAddress.startsWith('coingecko')) { +function scaleTokenAmount(amount, tokenInfo, decimals) { + if (tokenInfo.token.startsWith('coingecko')) { return amount.div(Math.pow(10, decimals)).toFixed(); } @@ -256,61 +263,81 @@ async function processMarket(marketContract) { } } -async function tvl() { - const balances = {} - +async function getMarketData() { const deployments = await fetchAllDeployments(TEMPLAR_REGISTRY_CONTRACTS) if (deployments.length === 0) { - console.log('No Templar deployments found for TVL calculation') - return balances + console.log('No Templar deployments found') + return [] } const results = await Promise.allSettled(deployments.map(processMarket)) + const marketData = [] results.forEach((result, index) => { if (result.status === 'fulfilled') { - if (result.value === null) { - return + if (result.value !== null) { + marketData.push(result.value) } - const { borrowAssetToken, collateralAssetToken, availableLiquidity, totalCollateral, borrowDecimals, collateralDecimals } = result.value - sumSingleBalance(balances, borrowAssetToken, scaleTokenAmount(availableLiquidity, borrowAssetToken, borrowDecimals)) - sumSingleBalance(balances, collateralAssetToken, scaleTokenAmount(totalCollateral, collateralAssetToken, collateralDecimals)) } else { throw new Error(`Market ${deployments[index]} failed: ${result.reason?.message || result.reason}`) } }) - return balances + return marketData } -async function borrowed() { - const balances = {} - - const deployments = await fetchAllDeployments(TEMPLAR_REGISTRY_CONTRACTS) - if (deployments.length === 0) { - console.log('No Templar deployments found for borrowed calculation') - return balances - } - - const results = await Promise.allSettled(deployments.map(processMarket)) - - results.forEach((result, index) => { - if (result.status === 'fulfilled') { - if (result.value === null) { - return - } - const { borrowAssetToken, totalBorrowed, borrowDecimals } = result.value - sumSingleBalance(balances, borrowAssetToken, scaleTokenAmount(totalBorrowed, borrowAssetToken, borrowDecimals)) - } else { - throw new Error(`Market ${deployments[index]} failed: ${result.reason?.message || result.reason}`) +function aggregateByChain(marketData, type) { + const chainBalances = {} + + marketData.forEach(market => { + const { borrowAssetToken, collateralAssetToken, availableLiquidity, totalBorrowed, totalCollateral, borrowDecimals, collateralDecimals } = market + + if (type === 'tvl') { + // Add borrow asset liquidity + if (!chainBalances[borrowAssetToken.chain]) chainBalances[borrowAssetToken.chain] = {} + sumSingleBalance(chainBalances[borrowAssetToken.chain], borrowAssetToken.token, scaleTokenAmount(availableLiquidity, borrowAssetToken, borrowDecimals)) + + // Add collateral + if (!chainBalances[collateralAssetToken.chain]) chainBalances[collateralAssetToken.chain] = {} + sumSingleBalance(chainBalances[collateralAssetToken.chain], collateralAssetToken.token, scaleTokenAmount(totalCollateral, collateralAssetToken, collateralDecimals)) + } else if (type === 'borrowed') { + if (!chainBalances[borrowAssetToken.chain]) chainBalances[borrowAssetToken.chain] = {} + sumSingleBalance(chainBalances[borrowAssetToken.chain], borrowAssetToken.token, scaleTokenAmount(totalBorrowed, borrowAssetToken, borrowDecimals)) } }) + + return chainBalances +} + +let cachedMarketData = null - return balances +async function getChainTvl(chain) { + if (!cachedMarketData) { + cachedMarketData = await getMarketData() + } + const chainBalances = aggregateByChain(cachedMarketData, 'tvl') + return chainBalances[chain] || {} } +async function getChainBorrowed(chain) { + if (!cachedMarketData) { + cachedMarketData = await getMarketData() + } + const chainBalances = aggregateByChain(cachedMarketData, 'borrowed') + return chainBalances[chain] || {} +} + +// Supported chains for cross-chain assets +const SUPPORTED_CHAINS = ['near', 'stellar', 'ethereum', 'bitcoin'] + module.exports = { - methodology: 'TVL is calculated by summing the net borrow asset liquidity (deposits minus outstanding loans) and full collateral deposits for each market deployment.', + methodology: 'TVL is calculated by summing the net borrow asset liquidity (deposits minus outstanding loans) and full collateral deposits for each market deployment. Assets are attributed to their origin chain (Stellar, Ethereum, Bitcoin).', start: 1754902109, - near: { tvl, borrowed }, } + +SUPPORTED_CHAINS.forEach(chain => { + module.exports[chain] = { + tvl: () => getChainTvl(chain), + borrowed: () => getChainBorrowed(chain), + } +})