@@ -12,24 +12,31 @@ const CALL_TIMEOUT_MS = 30000
1212const sleep = ( ms ) => new Promise ( resolve => setTimeout ( resolve , ms ) )
1313
1414function detectCrossChainToken ( tokenId ) {
15+ // Stellar tokens via HOT omnichain bridge (chain ID 1100)
1516 if ( tokenId . includes ( 'v2_1.omni.hot.tg:1100_' ) ) {
1617 const stellarMappings = {
1718 '111bzQBB5v7AhLyPMDwS8uJgQV24KaAPXtwyVWu2KXbbfQU6NXRCz' : 'coingecko:stellar' ,
1819 '111bzQBB65GxAPAVoxqmMcgYo5oS3txhqs1Uh1cgahKQUeTUq1TJu' : 'coingecko:usd-coin' ,
1920 }
2021 const match = tokenId . match ( / 1 1 0 0 _ ( [ a - z A - Z 0 - 9 ] + ) $ / )
2122 if ( match && stellarMappings [ match [ 1 ] ] ) {
22- return stellarMappings [ match [ 1 ] ]
23+ return { chain : 'stellar' , token : stellarMappings [ match [ 1 ] ] }
2324 }
2425 }
2526
26- if ( tokenId === 'btc.omft.near' ) return 'coingecko:bitcoin'
27+ // Bitcoin via omnichain bridge
28+ if ( tokenId === 'btc.omft.near' ) {
29+ return { chain : 'bitcoin' , token : 'coingecko:bitcoin' }
30+ }
31+
32+ // Ethereum tokens via omnichain bridge
2733 if ( tokenId . match ( / ^ e t h - 0 x ( [ a - f A - F 0 - 9 ] { 40 } ) \. o m f t \. n e a r $ / ) ) {
2834 const match = tokenId . match ( / ^ e t h - 0 x ( [ a - f A - F 0 - 9 ] { 40 } ) \. o m f t \. n e a r $ / )
29- return `ethereum:0x${ match [ 1 ] . toLowerCase ( ) } `
35+ return { chain : 'ethereum' , token : `ethereum:0x${ match [ 1 ] . toLowerCase ( ) } ` }
3036 }
3137
32- return tokenId
38+ // Native NEAR tokens
39+ return { chain : 'near' , token : tokenId }
3340}
3441
3542async function withRetry ( fn , maxAttempts = MAX_RETRY_ATTEMPTS , delayMs = RETRY_DELAY_MS ) {
@@ -82,7 +89,7 @@ function extractTokenAddress(assetConfig, assetType) {
8289 }
8390
8491 const crossChainResult = detectCrossChainToken ( tokenId )
85- if ( crossChainResult !== tokenId ) {
92+ if ( crossChainResult . chain !== 'near' ) {
8693 return crossChainResult
8794 }
8895
@@ -94,7 +101,7 @@ function extractTokenAddress(assetConfig, assetType) {
94101 return detectCrossChainToken ( parts [ 1 ] )
95102 }
96103
97- return tokenId
104+ return { chain : 'near' , token : tokenId }
98105 }
99106
100107 throw new Error ( `Unsupported ${ assetType } asset format: missing both Nep141 and valid Nep245` )
@@ -106,8 +113,8 @@ function validateConfiguration(configuration) {
106113 if ( ! configuration . collateral_asset ) throw new Error ( 'Missing collateral_asset in configuration' )
107114}
108115
109- function scaleTokenAmount ( amount , tokenAddress , decimals ) {
110- if ( tokenAddress . startsWith ( 'coingecko' ) ) {
116+ function scaleTokenAmount ( amount , tokenInfo , decimals ) {
117+ if ( tokenInfo . token . startsWith ( 'coingecko' ) ) {
111118 return amount . div ( Math . pow ( 10 , decimals ) ) . toFixed ( ) ;
112119 }
113120
@@ -256,61 +263,81 @@ async function processMarket(marketContract) {
256263 }
257264}
258265
259- async function tvl ( ) {
260- const balances = { }
261-
266+ async function getMarketData ( ) {
262267 const deployments = await fetchAllDeployments ( TEMPLAR_REGISTRY_CONTRACTS )
263268 if ( deployments . length === 0 ) {
264- console . log ( 'No Templar deployments found for TVL calculation ' )
265- return balances
269+ console . log ( 'No Templar deployments found' )
270+ return [ ]
266271 }
267272
268273 const results = await Promise . allSettled ( deployments . map ( processMarket ) )
274+ const marketData = [ ]
269275
270276 results . forEach ( ( result , index ) => {
271277 if ( result . status === 'fulfilled' ) {
272- if ( result . value = == null ) {
273- return
278+ if ( result . value ! == null ) {
279+ marketData . push ( result . value )
274280 }
275- const { borrowAssetToken, collateralAssetToken, availableLiquidity, totalCollateral, borrowDecimals, collateralDecimals } = result . value
276- sumSingleBalance ( balances , borrowAssetToken , scaleTokenAmount ( availableLiquidity , borrowAssetToken , borrowDecimals ) )
277- sumSingleBalance ( balances , collateralAssetToken , scaleTokenAmount ( totalCollateral , collateralAssetToken , collateralDecimals ) )
278281 } else {
279282 throw new Error ( `Market ${ deployments [ index ] } failed: ${ result . reason ?. message || result . reason } ` )
280283 }
281284 } )
282285
283- return balances
286+ return marketData
284287}
285288
286- async function borrowed ( ) {
287- const balances = { }
288-
289- const deployments = await fetchAllDeployments ( TEMPLAR_REGISTRY_CONTRACTS )
290- if ( deployments . length === 0 ) {
291- console . log ( 'No Templar deployments found for borrowed calculation' )
292- return balances
293- }
294-
295- const results = await Promise . allSettled ( deployments . map ( processMarket ) )
296-
297- results . forEach ( ( result , index ) => {
298- if ( result . status === 'fulfilled' ) {
299- if ( result . value === null ) {
300- return
301- }
302- const { borrowAssetToken, totalBorrowed, borrowDecimals } = result . value
303- sumSingleBalance ( balances , borrowAssetToken , scaleTokenAmount ( totalBorrowed , borrowAssetToken , borrowDecimals ) )
304- } else {
305- throw new Error ( `Market ${ deployments [ index ] } failed: ${ result . reason ?. message || result . reason } ` )
289+ function aggregateByChain ( marketData , type ) {
290+ const chainBalances = { }
291+
292+ marketData . forEach ( market => {
293+ const { borrowAssetToken, collateralAssetToken, availableLiquidity, totalBorrowed, totalCollateral, borrowDecimals, collateralDecimals } = market
294+
295+ if ( type === 'tvl' ) {
296+ // Add borrow asset liquidity
297+ if ( ! chainBalances [ borrowAssetToken . chain ] ) chainBalances [ borrowAssetToken . chain ] = { }
298+ sumSingleBalance ( chainBalances [ borrowAssetToken . chain ] , borrowAssetToken . token , scaleTokenAmount ( availableLiquidity , borrowAssetToken , borrowDecimals ) )
299+
300+ // Add collateral
301+ if ( ! chainBalances [ collateralAssetToken . chain ] ) chainBalances [ collateralAssetToken . chain ] = { }
302+ sumSingleBalance ( chainBalances [ collateralAssetToken . chain ] , collateralAssetToken . token , scaleTokenAmount ( totalCollateral , collateralAssetToken , collateralDecimals ) )
303+ } else if ( type === 'borrowed' ) {
304+ if ( ! chainBalances [ borrowAssetToken . chain ] ) chainBalances [ borrowAssetToken . chain ] = { }
305+ sumSingleBalance ( chainBalances [ borrowAssetToken . chain ] , borrowAssetToken . token , scaleTokenAmount ( totalBorrowed , borrowAssetToken , borrowDecimals ) )
306306 }
307307 } )
308+
309+ return chainBalances
310+ }
311+
312+ let cachedMarketData = null
308313
309- return balances
314+ async function getChainTvl ( chain ) {
315+ if ( ! cachedMarketData ) {
316+ cachedMarketData = await getMarketData ( )
317+ }
318+ const chainBalances = aggregateByChain ( cachedMarketData , 'tvl' )
319+ return chainBalances [ chain ] || { }
310320}
311321
322+ async function getChainBorrowed ( chain ) {
323+ if ( ! cachedMarketData ) {
324+ cachedMarketData = await getMarketData ( )
325+ }
326+ const chainBalances = aggregateByChain ( cachedMarketData , 'borrowed' )
327+ return chainBalances [ chain ] || { }
328+ }
329+
330+ // Supported chains for cross-chain assets
331+ const SUPPORTED_CHAINS = [ 'near' , 'stellar' , 'ethereum' , 'bitcoin' ]
332+
312333module . exports = {
313- methodology : 'TVL is calculated by summing the net borrow asset liquidity (deposits minus outstanding loans) and full collateral deposits for each market deployment.' ,
334+ 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). ' ,
314335 start : 1754902109 ,
315- near : { tvl, borrowed } ,
316336}
337+
338+ SUPPORTED_CHAINS . forEach ( chain => {
339+ module . exports [ chain ] = {
340+ tvl : ( ) => getChainTvl ( chain ) ,
341+ borrowed : ( ) => getChainBorrowed ( chain ) ,
342+ }
343+ } )
0 commit comments