From ab51389ab4e26e8be048d2e7375717f118dfa572 Mon Sep 17 00:00:00 2001 From: Andrew <105604530+andrewlfc7@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:33:51 -0400 Subject: [PATCH 1/2] feat: add LOAN Protocol adapter for DefiLlama --- projects/ithaca/index.js | 1 + projects/loan-protocol/index.js | 111 ++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 projects/loan-protocol/index.js diff --git a/projects/ithaca/index.js b/projects/ithaca/index.js index cdb45d8182..eab40cf792 100644 --- a/projects/ithaca/index.js +++ b/projects/ithaca/index.js @@ -24,3 +24,4 @@ module.exports = { }), } }; + diff --git a/projects/loan-protocol/index.js b/projects/loan-protocol/index.js new file mode 100644 index 0000000000..bd1b644e76 --- /dev/null +++ b/projects/loan-protocol/index.js @@ -0,0 +1,111 @@ +const { post } = require('../helper/http'); +const sdk = require('@defillama/sdk'); + +const tokenMapping = { + 'xtokens:XBTC': 'bitcoin', + 'xtokens:XLTC': 'litecoin', + 'xtokens:XETH': 'ethereum', + 'xtokens:XXRP': 'ripple', + 'eosio.token:XPR': 'proton', + 'xtokens:XMT': 'metal', + 'xtokens:XUST': 'terrausd-wormhole', // optional/legacy + 'xtokens:XLUNA': 'terra-luna-2', // optional/legacy + 'xtokens:XUSDC': 'usd-coin', + 'xtokens:XDOGE': 'dogecoin', + 'xtokens:XUSDT': 'tether', +}; + +const API_ENDPOINT = 'https://proton.eosusa.io'; +const LENDING_CONTRACT = 'lending.loan'; + +function parseAsset(assetString) { + if (!assetString) return { amount: 0, symbol: '' }; + const [amount, symbol] = assetString.split(' '); + return { amount: parseFloat(amount), symbol }; +} + +async function fetchMarkets() { + const res = await post(`${API_ENDPOINT}/v1/chain/get_table_rows`, { + code: LENDING_CONTRACT, + scope: LENDING_CONTRACT, + table: 'markets', + limit: 100, + json: true, + }); + return res.rows || []; +} + +async function fetchLiquidity(tokenContract, symbol) { + const res = await post(`${API_ENDPOINT}/v1/chain/get_table_rows`, { + code: tokenContract, + scope: LENDING_CONTRACT, + table: 'accounts', + limit: 100, + json: true, + }); + const rows = res.rows || []; + const tokenBalance = rows.find(b => parseAsset(b.balance).symbol === symbol); + return tokenBalance ? parseAsset(tokenBalance.balance).amount : 0; +} + +// ---------------------------- +// TVL = borrows + cash reserves +// ---------------------------- +async function tvl() { + const balances = {}; + const markets = await fetchMarkets(); + + const promises = markets.map(async (market) => { + const totalVar = parseAsset(market.total_variable_borrows.quantity).amount; + const totalStable = parseAsset(market.total_stable_borrows.quantity).amount; + const totalBorrows = totalVar + totalStable; + + const [ , symbol ] = market.underlying_symbol.sym.split(','); + const tokenContract = market.underlying_symbol.contract; + + // liquidity available in lending contract + const cashAvailable = await fetchLiquidity(tokenContract, symbol); + const totalSupplied = totalBorrows + cashAvailable; + + const internalId = `${tokenContract}:${symbol}`; + const cgkId = tokenMapping[internalId]; + if (!cgkId) return; + + sdk.util.sumSingleBalance(balances, `coingecko:${cgkId}`, totalSupplied); + }); + + await Promise.all(promises); + return balances; +} + +// ---------------------------- +// Borrowed = borrows only +// ---------------------------- +async function borrowed() { + const balances = {}; + const markets = await fetchMarkets(); + + markets.forEach(market => { + const totalVar = parseAsset(market.total_variable_borrows.quantity).amount; + const totalStable = parseAsset(market.total_stable_borrows.quantity).amount; + const totalBorrows = totalVar + totalStable; + + const [ , symbol ] = market.underlying_symbol.sym.split(','); + const tokenContract = market.underlying_symbol.contract; + const internalId = `${tokenContract}:${symbol}`; + const cgkId = tokenMapping[internalId]; + if (!cgkId) return; + + sdk.util.sumSingleBalance(balances, `coingecko:${cgkId}`, totalBorrows); + }); + + return balances; +} + +module.exports = { + methodology: 'TVL = variable borrows + stable borrows + available liquidity in lending.loan. Borrowed = total outstanding borrows (variable + stable). Mapping is to CoinGecko IDs.', + proton: { + tvl, + borrowed, + } +}; \ No newline at end of file From 1c0f2fb978ed08e9e3ba533bc3b8748304773d82 Mon Sep 17 00:00:00 2001 From: Andrew <105604530+andrewlfc7@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:28:37 -0400 Subject: [PATCH 2/2] tweaking so tvl is just the available liquiditya --- projects/ithaca/index.js | 3 +- projects/loan-protocol/index.js | 103 +++++++++++++++----------------- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/projects/ithaca/index.js b/projects/ithaca/index.js index eab40cf792..443a333cdd 100644 --- a/projects/ithaca/index.js +++ b/projects/ithaca/index.js @@ -23,5 +23,4 @@ module.exports = { ], }), } -}; - +}; \ No newline at end of file diff --git a/projects/loan-protocol/index.js b/projects/loan-protocol/index.js index bd1b644e76..7805fba467 100644 --- a/projects/loan-protocol/index.js +++ b/projects/loan-protocol/index.js @@ -1,5 +1,5 @@ -const { post } = require('../helper/http'); -const sdk = require('@defillama/sdk'); +const { post } = require('../helper/http') +const sdk = require('@defillama/sdk') const tokenMapping = { 'xtokens:XBTC': 'bitcoin', @@ -8,20 +8,20 @@ const tokenMapping = { 'xtokens:XXRP': 'ripple', 'eosio.token:XPR': 'proton', 'xtokens:XMT': 'metal', - 'xtokens:XUST': 'terrausd-wormhole', // optional/legacy - 'xtokens:XLUNA': 'terra-luna-2', // optional/legacy 'xtokens:XUSDC': 'usd-coin', 'xtokens:XDOGE': 'dogecoin', 'xtokens:XUSDT': 'tether', -}; + 'xtokens:XUST': 'terrausd-wormhole', + 'xtokens:XLUNA': 'terra-luna-2', +} -const API_ENDPOINT = 'https://proton.eosusa.io'; -const LENDING_CONTRACT = 'lending.loan'; +const API_ENDPOINT = 'https://proton.eosusa.io' +const LENDING_CONTRACT = 'lending.loan' function parseAsset(assetString) { - if (!assetString) return { amount: 0, symbol: '' }; - const [amount, symbol] = assetString.split(' '); - return { amount: parseFloat(amount), symbol }; + if (!assetString) return { amount: 0, symbol: '' } + const [amount, symbol] = assetString.split(' ') + return { amount: parseFloat(amount), symbol } } async function fetchMarkets() { @@ -31,81 +31,74 @@ async function fetchMarkets() { table: 'markets', limit: 100, json: true, - }); - return res.rows || []; + }) + return res.rows || [] } async function fetchLiquidity(tokenContract, symbol) { + // available liquidity (cash) held by lending.loan for a given token const res = await post(`${API_ENDPOINT}/v1/chain/get_table_rows`, { code: tokenContract, scope: LENDING_CONTRACT, table: 'accounts', limit: 100, json: true, - }); - const rows = res.rows || []; - const tokenBalance = rows.find(b => parseAsset(b.balance).symbol === symbol); - return tokenBalance ? parseAsset(tokenBalance.balance).amount : 0; + }) + const rows = res.rows || [] + const tokenBalance = rows.find(b => parseAsset(b.balance).symbol === symbol) + return tokenBalance ? parseAsset(tokenBalance.balance).amount : 0 } // ---------------------------- -// TVL = borrows + cash reserves +// TVL = only available liquidity (cash) // ---------------------------- async function tvl() { - const balances = {}; - const markets = await fetchMarkets(); + const balances = {} + const markets = await fetchMarkets() const promises = markets.map(async (market) => { - const totalVar = parseAsset(market.total_variable_borrows.quantity).amount; - const totalStable = parseAsset(market.total_stable_borrows.quantity).amount; - const totalBorrows = totalVar + totalStable; - - const [ , symbol ] = market.underlying_symbol.sym.split(','); - const tokenContract = market.underlying_symbol.contract; - - // liquidity available in lending contract - const cashAvailable = await fetchLiquidity(tokenContract, symbol); - const totalSupplied = totalBorrows + cashAvailable; - - const internalId = `${tokenContract}:${symbol}`; - const cgkId = tokenMapping[internalId]; - if (!cgkId) return; - - sdk.util.sumSingleBalance(balances, `coingecko:${cgkId}`, totalSupplied); - }); - - await Promise.all(promises); - return balances; + const [ , symbol ] = market.underlying_symbol.sym.split(',') + const tokenContract = market.underlying_symbol.contract + const internalId = `${tokenContract}:${symbol}` + const cgkId = tokenMapping[internalId] + if (!cgkId) return + + const cashAvailable = await fetchLiquidity(tokenContract, symbol) + sdk.util.sumSingleBalance(balances, `coingecko:${cgkId}`, cashAvailable) + }) + + await Promise.all(promises) + return balances } // ---------------------------- -// Borrowed = borrows only +// Borrowed = total variable + stable borrows // ---------------------------- async function borrowed() { - const balances = {}; - const markets = await fetchMarkets(); + const balances = {} + const markets = await fetchMarkets() markets.forEach(market => { - const totalVar = parseAsset(market.total_variable_borrows.quantity).amount; - const totalStable = parseAsset(market.total_stable_borrows.quantity).amount; - const totalBorrows = totalVar + totalStable; + const totalVar = parseAsset(market.total_variable_borrows.quantity).amount + const totalStable = parseAsset(market.total_stable_borrows.quantity).amount + const totalBorrows = totalVar + totalStable - const [ , symbol ] = market.underlying_symbol.sym.split(','); - const tokenContract = market.underlying_symbol.contract; - const internalId = `${tokenContract}:${symbol}`; - const cgkId = tokenMapping[internalId]; - if (!cgkId) return; + const [ , symbol ] = market.underlying_symbol.sym.split(',') + const tokenContract = market.underlying_symbol.contract + const internalId = `${tokenContract}:${symbol}` + const cgkId = tokenMapping[internalId] + if (!cgkId) return - sdk.util.sumSingleBalance(balances, `coingecko:${cgkId}`, totalBorrows); - }); + sdk.util.sumSingleBalance(balances, `coingecko:${cgkId}`, totalBorrows) + }) - return balances; + return balances } module.exports = { - methodology: 'TVL = variable borrows + stable borrows + available liquidity in lending.loan. Borrowed = total outstanding borrows (variable + stable). Mapping is to CoinGecko IDs.', + methodology: 'TVL = only available liquidity (cash held by lending.loan). Borrowed = total variable + stable borrows (outstanding debt). Deposits = TVL + Borrowed, but we report liquidity as TVL per DefiLlama standards.', proton: { tvl, borrowed, } -}; \ No newline at end of file +} \ No newline at end of file