Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions projects/helper/tokenMapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ const fixBalancesTokens = {
'0x2a25fbD67b3aE485e461fe55d9DbeF302B7D3989': { coingeckoId: 'usd-coin', decimals: 6 },
'0x83A15000b753AC0EeE06D2Cb41a69e76D0D5c7F7': { coingeckoId: 'ethereum', decimals: 18 },
},
alephium: {
'383bc735a4de6722af80546ec9eeb3cff508f2f68e97da19489ce69f3e703200': { coingeckoId: 'wrapped-bitcoin', decimals: 8 },
'722954d9067c5a5ad532746a024f2a9d7a18ed9b90e27d0a3a504962160b5600': { coingeckoId: 'usd-coin', decimals: 6 },
'556d9582463fe44fbd108aedc9f409f69086dc78d994b88ea6c9e65f8bf98e00': { coingeckoId: 'tether', decimals: 6 },
}
}

ibcChains.forEach(chain => fixBalancesTokens[chain] = { ...ibcMappings, ...(fixBalancesTokens[chain] || {}) })
Expand Down
93 changes: 93 additions & 0 deletions projects/linx/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const alephium = require('../helper/chain/alephium');
const { get } = require('../helper/http');

const config = {
linx: "vQcfta4Mm32L7Xsb7tYF2rrR76JWxjNv3oia8GPK6x71",
nodeApiHost: "https://node.mainnet.alephium.org",
}

const ALPH_TOKEN_ID = '0000000000000000000000000000000000000000000000000000000000000000';

const MARKET_CREATED_EVENT_INDEX = 4;
const MARKET_METHOD_INDEX = 4;

async function getEvents(contractAddress) {
let events = [];
let start = 0;
const limit = 100;

while (true) {
const response = await get(`${config.nodeApiHost}/events/contract/${contractAddress}?start=${start}&limit=${limit}`);
events = events.concat(response.events);
if (!response.events.length || response.nextStart === undefined) break;
start = response.nextStart;
}
return events;
}
Comment on lines +14 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a pagination safety guard to prevent infinite loops.
If nextStart fails to advance (API bug or edge case), the loop never terminates. Guard against nextStart <= start and missing events.

🔧 Suggested fix
     while (true) {
         const response = await get(`${config.nodeApiHost}/events/contract/${contractAddress}?start=${start}&limit=${limit}`);
-        events = events.concat(response.events);
-        if (!response.events.length || response.nextStart === undefined) break;
-        start = response.nextStart;
+        const nextStart = response?.nextStart;
+        const pageEvents = response?.events ?? [];
+        events = events.concat(pageEvents);
+        if (!pageEvents.length || nextStart === undefined || nextStart <= start) break;
+        start = nextStart;
     }
🤖 Prompt for AI Agents
In `@projects/linx/index.js` around lines 14 - 26, The loop in getEvents can hang
if the API returns a nextStart that doesn't advance or returns empty events;
after each fetch (in getEvents) add explicit guards: if response.events is empty
break; if response.nextStart is undefined break; and if response.nextStart <=
start break; optionally also add a safety counter (e.g., maxIterations) to
force-break after N iterations to protect against API bugs. Ensure you reference
and update the local start variable only when nextStart is a valid, strictly
greater value.


async function getMarkets() {
const events = await getEvents(config.linx);
let markets = events.filter(e => e.eventIndex === MARKET_CREATED_EVENT_INDEX).map(event => {
const marketId = event.fields[0].value;
const contractId = event.fields[1].value;
const loanTokenId = event.fields[2].value;
const collateralTokenId = event.fields[3].value;

return { marketId, contractId, loanTokenId, collateralTokenId };
});

markets = Array.from(new Map(markets.map(m => [m.marketId, m])).values());
return markets;
}

async function getTokenBalance(marketContractId, tokenId) {
const contractAddress = alephium.addressFromContractId(marketContractId);
if (tokenId === ALPH_TOKEN_ID) {
return BigInt((await alephium.getAlphBalance(contractAddress)).balance);
} else {
const tokensBalance = await alephium.getTokensBalance(contractAddress);
const tokenBalance = tokensBalance.find(b => b.tokenId === tokenId);
return BigInt(tokenBalance?.balance ?? 0);
}
}

async function tvl(api) {
const markets = await getMarkets();
const tokenAmounts = new Map();
for (const market of markets) {
const collateral = await getTokenBalance(market.contractId, market.collateralTokenId);
const loanBalance = await getTokenBalance(market.contractId, market.loanTokenId);
tokenAmounts.set(market.collateralTokenId, (tokenAmounts.get(market.collateralTokenId) ?? BigInt(0)) + collateral);
tokenAmounts.set(market.loanTokenId, (tokenAmounts.get(market.loanTokenId) ?? BigInt(0)) + loanBalance);
}

for (const [tokenId, amount] of tokenAmounts.entries()) {
api.add(tokenId, amount);
}
}

async function borrowed(api) {
const markets = await getMarkets();
const tokenAmounts = new Map();
for (const market of markets) {
const state = await alephium.contractMultiCall([{
group: 0,
address: config.linx,
methodIndex: MARKET_METHOD_INDEX,
args: [{ type: "ByteVec", value: market.marketId }]
}]);
const borrowAssets = BigInt(state[0].returns[2].value);
tokenAmounts.set(market.loanTokenId, (tokenAmounts.get(market.loanTokenId) ?? BigInt(0)) + borrowAssets);
}

for (const [tokenId, amount] of tokenAmounts.entries()) {
api.add(tokenId, amount);
}
}

module.exports = {
alephium: {
tvl,
borrowed
}
};
Loading