Skip to content

Commit d256e9f

Browse files
authored
feat: calculate templar assets on native chains (#17249)
1 parent 9169f01 commit d256e9f

File tree

1 file changed

+69
-42
lines changed

1 file changed

+69
-42
lines changed

projects/templarfi/index.js

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,31 @@ const CALL_TIMEOUT_MS = 30000
1212
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
1313

1414
function 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(/1100_([a-zA-Z0-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(/^eth-0x([a-fA-F0-9]{40})\.omft\.near$/)) {
2834
const match = tokenId.match(/^eth-0x([a-fA-F0-9]{40})\.omft\.near$/)
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

3542
async 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+
312333
module.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

Comments
 (0)