diff --git a/projects/helper/tokenMapping.js b/projects/helper/tokenMapping.js index 7430b1db3..04440d41b 100644 --- a/projects/helper/tokenMapping.js +++ b/projects/helper/tokenMapping.js @@ -42,7 +42,7 @@ const ibcMappings = { const fixBalancesTokens = { ozone: { // '0x83048f0bf34feed8ced419455a4320a735a92e9d': { coingeckoId: "ozonechain", decimals: 18 }, // was mapped to wrong chain - }, + }, camp: { [ADDRESSES.camp.WCAMP]: { coingeckoId: "camp-network", decimals: 18 }, // Wrapped CAMP (ERC-20 wrapper of native CAMP) [ADDRESSES.camp.ETH]: { coingeckoId: "ethereum", decimals: 18 }, // Wrapped ETH @@ -301,4 +301,4 @@ module.exports = { stripTokenHeader, getUniqueAddresses, eulerTokens, -} \ No newline at end of file +} diff --git a/projects/jurisprotocol/abi.json b/projects/jurisprotocol/abi.json new file mode 100644 index 000000000..760f8e6c8 --- /dev/null +++ b/projects/jurisprotocol/abi.json @@ -0,0 +1,63 @@ +{ + "protocol": { + "name": "Juris Protocol", + "description": "Decentralized staking and lending protocol on Terra Classic ecosystem", + "category": "Lending" + }, + "source_repo": "https://github.com/JurisProtocol/documents", + "tokens": { + "JURIS": { + "name": "Juris Protocol", + "symbol": "JURIS", + "decimals": 6, + "address": "terra1vhgq25vwuhdhn9xjll0rhl2s67jzw78a4g2t78y5kz89q9lsdskq2pxcj2", + "coingeckoId": "juris-protocol", + "type": "cw20", + "owner": "true" + }, + "LUNC": { + "name": "Terra Luna Classic", + "symbol": "LUNC", + "decimals": 6, + "address": "uluna", + "coingeckoId": "terra-luna-classic", + "type": "native" + }, + "USTC": { + "name": "TerraClassicUSD", + "symbol": "USTC", + "decimals": 6, + "address": "uusd", + "coingeckoId": "terraclassicusd", + "type": "native" + } + }, + "contracts": { + "staking": [ + "terra1rta0rnaxz9ww6hnrj9347vdn66gkgxcmcwgpm2jj6qulv8adc52s95qa5y" + ], + "lending": [], + "vesting": [ + "terra1w89kclh6qd4ftyll4k0x4cyd23lzd9krsntds4y0z2x67kymtf3qj9fgrl", + "terra14783nqrx4mjqfnymyqp88dsjf5c6axlt2m75wwt2supwkc0jxr0qyrhqtl", + "terra1lejvcrgmhcuedemdetv6qrru7yu8qgwn6e070fq6q4kpda838kpsghwl2u", + "terra1hdm8l39h4vmmy8yl48hev8r7qa8pl7uygcraawfljslpepcn7mjqtp83fg", + "terra1cjjy4yzzp6sdv6uq27u6l82gslpdkw4l3zk785674mh8gk9dn5qqvr4nr0", + "terra1pfefmmls2w67njucd2qgvv4qefcutyl95g986pd69caxdyzp7acsfp0fv8", + "terra1ux3em04y0ruwfhljlhl2p8743yyj84xh6s0nyqutyte9397yrq3qe77fm9", + "terra15fyg7elhndd7990eshsjz07p3rsssqsm4jjg48zgxpadhahmxd7stpfur5", + "terra18wdt4vcejy06mrpp6naju4vv2j7s7hlksew5dq247fxhxvrtxj7qx3x97u", + "terra1xgxxjzw9ej5ha2tgsd498w3w4nnu58m59xzwyysmvgnj0y2yrq6q4lm0ru", + "terra1w8ysh7fg5f8mfu5cjeumfhjwja6crf20k447a9gftv4lcn2jgpqsf3ztv2", + "terra1pljarz83hu9rfdvg88ft8dr76rwmkr5r0avv30ra3p6pgdepfutqrw90dr", + "terra1jw5e3hk3k2qz9985r2c3fd0ac5uuz6j98xp9ealnu9qt3dx2wtuqc202fn", + "terra1jt70skckl3a3zl98l7na4zqhuyyaerx97rdt9gy2u6eadxc2ke8s2lffsl", + "terra13jhdcxd3v7n6fnc4gjj692jsa4jfnh83aly87vj4kyn5c8gp02esgelln5", + "terra12xq35pp48mwp7qep2tdlnt2jg8pxmsll0vtnygay7tgh0djdk34sw6qhdl" + ], + "reserve": [], + "pool2": [], + "ownTokens": [], + "treasury": [] + } +} diff --git a/projects/jurisprotocol/index.js b/projects/jurisprotocol/index.js new file mode 100644 index 000000000..27f77d01a --- /dev/null +++ b/projects/jurisprotocol/index.js @@ -0,0 +1,106 @@ +const abi = require('./abi.json'); +const { queryContract, queryV1Beta1 } = require('../helper/chain/cosmos'); + +const { contracts, tokens } = abi; + +// Build helpers from abi.json +const TOKENS = Object.values(tokens || {}); +const TOKEN_BY_ADDR = Object.fromEntries(TOKENS.map(t => [t.address, t])); +const OWNED_CW20_SET = new Set( + TOKENS + .filter(t => t.type === 'cw20' && (t.owner === true || t.owner === 'true')) + .map(t => t.address) +); +const NATIVE_DENOMS = new Set( + TOKENS.filter(t => t.type === 'native').map(t => t.address) +); + +async function smartQuery(contract, msgObj) { + const res = await queryContract({ contract, chain: 'terra', data: msgObj }); + return res?.data ?? res ?? {}; +} + +async function bankBalances(address) { + const res = await queryV1Beta1({ + chain: 'terra', + // Standard Cosmos SDK REST path + url: `/bank/v1beta1/balances/${address}`, + }); + return res?.balances ?? []; +} + +async function cw20Balance(token, owner) { + const r = await smartQuery(token, { balance: { address: owner } }); + const raw = String(r.balance ?? r.amount ?? '0').replace(/[^0-9]/g, '') || '0'; + return raw; +} + +function nativeBalance(balances, denom) { + const row = balances.find(r => r.denom === denom); + return String(row?.amount ?? '0').replace(/[^0-9]/g, '') || '0'; +} + +// Core worker: optional exclusion for protocol-owned CW20s, with verbose logging +async function fetchBalances(moduleName, api, { excludeOwnedCw20 = false, logTag = '' } = {}) { + const owners = (contracts[moduleName] || []).filter(Boolean); + if (!owners.length) { + console.log(`[${logTag || moduleName}] no owners configured`); + return; + } + + await Promise.all(owners.map(async owner => { + console.log(`[${logTag || moduleName}] owner=${owner} — scanning balances`); + + const bank = await bankBalances(owner); + + // Natives + for (const denom of NATIVE_DENOMS) { + const bal = nativeBalance(bank, denom); + if (+bal > 0) { + console.log(`[${logTag || moduleName}] owner=${owner} native ${denom}=${bal}`); + api.add(denom, bal); + } + } + + // CW20s + for (const t of TOKENS) { + if (t.type !== 'cw20') continue; + const bal = await cw20Balance(t.address, owner); + if (+bal <= 0) continue; + + const info = TOKEN_BY_ADDR[t.address] || {}; + const sym = info.symbol || ''; + const isOwned = OWNED_CW20_SET.has(t.address); + const excluded = excludeOwnedCw20 && isOwned; + + console.log( + `[${logTag || moduleName}] owner=${owner} cw20 ${sym}(${t.address})=${bal}${excluded ? ' [EXCLUDED]' : ''}` + ); + + if (!excluded) api.add(t.address, bal); + } + + console.log(`[${logTag || moduleName}] owner=${owner} — done`); + })); +} + +// Build category fns from abi.json (default: no exclusions) +const terraExport = {}; +Object.keys(contracts).forEach(key => { + if (contracts[key]?.length) { + terraExport[key] = (api) => fetchBalances(key, api, { excludeOwnedCw20: false, logTag: key }); + } +}); + +// TVL: include all desired categories but exclude owner CW20s (e.g., JURIS) everywhere +terraExport.tvl = async (api) => { + const cats = Object.keys(contracts).filter(k => k !== 'tvl'); + await Promise.all(cats.map((k) => fetchBalances(k, api, { excludeOwnedCw20: true, logTag: `TVL:${k}` }))); +}; + +module.exports = { + methodology: + 'TVL sums native denoms and non-owned CW20s across configured contract owners; any CW20 with owner=true in abi.json (e.g., JURIS) is excluded from TVL but still logged for visibility.', + timetravel: false, + terra: terraExport, +};