Files
DefiLlama-Adapters/projects/superfluid.js
realshaman f1bca21207 fix imports
2025-08-28 14:57:47 -06:00

244 lines
7.7 KiB
JavaScript

const ADDRESSES = require('./helper/coreAssets.json')
const { getBlock, blockQuery } = require("./helper/http");
const supertokensQuery = ({ first = 1000, id_gt = "" } = {}) => `
query get_supertokens($block: Int) {
tokens(
first: ${first},
block: { number: $block },
where: { isSuperToken: true${id_gt ? `, id_gt: "${id_gt}"` : ""} },
orderBy: id,
orderDirection: asc
) {
id
underlyingAddress
name
underlyingToken { name decimals symbol id }
symbol
decimals
isSuperToken
isNativeAssetSuperToken
isListed
}
}`;
const blacklistedSuperTokens = new Set(
["0x441bb79f2da0daf457bad3d401edb68535fb3faa"].map((i) => i.toLowerCase())
);
// Fetch and paginate all SuperTokens at a given block
async function fetchAllSuperTokens(graphUrl, blockForQuery) {
const PAGE_SIZE = 1000;
let lastId = "";
const allTokens = [];
let hasMore = true
while (hasMore) {
const query = supertokensQuery({ first: PAGE_SIZE, id_gt: lastId });
const res = await blockQuery(graphUrl, query, {
api: { getBlock: () => blockForQuery, block: blockForQuery },
});
const tokens = res.tokens;
if (!tokens?.length) {
hasMore = false
break;
}
allTokens.push(...tokens);
if (tokens.length < PAGE_SIZE) {
hasMore = false
break;
}
lastId = tokens[tokens.length - 1].id;
}
return allTokens;
}
// ALEPH custom locker address used on Base and Avalanche
const ALEPH_LOCKER = "0xb6e45ADfa0C7D70886bBFC990790d64620F1BAE8".toLowerCase();
// ALEPH SuperToken address (same on Base and Avalanche)
const ALEPH_SUPERTOKEN = "0xc0Fbc4967259786C743361a5885ef49380473dCF".toLowerCase();
// MIVA token locker addresses (on xDai/Gnosis). Circulating = totalSupply - sum(locker balances)
const MIVA_LOCKERS = [
"0x50e39b354c90146de80a577e13129bb0ba36ee45",
"0x791B3A48D2dca38871C9900783653b15aCae0Aea",
"0x6A0491132aF4d0925F857A5000bb21e5C5C195EA",
"0x8F00FC7756C9E901963B723AD1821E5EB8C69C02",
"0x10DAF0DF6Ec9bEF452F3073A56f6adB4B1809222",
"0xa2eac044fe1e004cAaC4E8C4164a39F4Cc522b6f",
"0x89Abea6823cfd903fB503A1DB17a7ce890A3232e",
"0xFd989d6E3244cFb5470597E7B93E4430CC29EfE9",
"0x867e84EB2789c95eEF6d6991cC4bC6B48e1519b8",
"0x16daae140FbC2F854Cf61af0512Bd8CD627d0B8e",
"0xA298D0b6B9216f7d9EB252DeA06280b748eFe8E5",
"0x1d9896F00fd51df839B2F5B7fFdD0bD60b471CeF",
"0xDfdec8DF5cfF5DaAb3ec635E477517AC92251dfD",
"0xeCD2D1bB2776f00AD15F976F349A1ab01F8ce398",
"0x5B339241312024382C9768b3598f60eCF34Ae779",
];
// Main function for all chains to get balances of superfluid tokens
async function getChainBalances(allTokens, chain, block, isVesting, api) {
// Init empty balances
let balances = {};
// Abi MultiCall to get supertokens supplies
const supply = await api.multiCall({
abi: "erc20:totalSupply", // abi['totalSupply'],
calls: allTokens.map(token => token.id),
});
for (let i = 0; i < supply.length; i++) {
const totalSupply = supply[i];
const {
id,
underlyingAddress,
underlyingToken,
decimals,
name,
symbol,
isNativeAssetSuperToken,
} = allTokens[i];
// Accumulate to balances, the balance for tokens on mainnet or sidechain
let prefixedUnderlyingAddress = underlyingAddress;
if (
underlyingAddress &&
blacklistedSuperTokens.has(underlyingAddress.toLowerCase())
)
continue;
// ALEPH custom logic (Base and Avalanche): no underlying; circulating = totalSupply - locker balance
if (
(chain === 'base' || chain === 'avax') &&
id.toLowerCase() === ALEPH_SUPERTOKEN
) {
const lockerHolding = await api.call({ abi: 'erc20:balanceOf', target: id, params: [ALEPH_LOCKER] });
const circulating = Math.max(0, totalSupply - lockerHolding);
api.add(id, circulating);
continue;
}
// MIVA token special logic (Gnosis/xDai): circulating = totalSupply - sum(locker balances)
if (symbol && symbol.toUpperCase() === 'MIVA') {
const lockerHoldings = await api.multiCall({
abi: 'erc20:balanceOf',
calls: MIVA_LOCKERS.map((locker) => ({ target: id, params: [locker] })),
});
const totalLocked = lockerHoldings.reduce((sum, v) => sum + (Number(v) || 0), 0);
const circulating = Math.max(0, totalSupply - totalLocked);
api.add(id, circulating);
continue;
}
if (isNativeAssetSuperToken) {
// For native asset SuperTokens (like ETHx), use the chain's native token
api.add(ADDRESSES.null, totalSupply);
continue;
}
if (underlyingToken) {
// For wrapped tokens (default), convert to underlying units
const underlyingDecimals = (underlyingToken || { decimals: 18 }).decimals;
const underlyingTokenBalance =
(totalSupply * 10 ** underlyingDecimals) / 10 ** decimals;
api.add(prefixedUnderlyingAddress, underlyingTokenBalance);
continue;
}
// For pure SuperTokens (no underlying), use the SuperToken's own address
api.add(id, totalSupply);
}
}
async function retrieveSupertokensBalances(
chain,
block,
isVesting,
api,
graphUrl
) {
let blockNum;
try {
blockNum = await getBlock(api.timestamp, chain, { [chain]: block });
} catch (e) {
// If block lookup fails for this chain/date (e.g. pre-genesis), skip it
return;
}
// Ensure we don't query subgraphs before their start block set in the manifest (pre protocol deployment)
const subgraphStartBlocks = {
scroll: 2_575_000,
degen: 26188017,
base: 1000000,
ethereum: 15870000,
celo: 16393000,
bsc: 18800000,
avax: 14700000,
arbitrum: 7600000,
optimism: 4300000,
polygon: 11650500,
xdai: 14820000,
};
const minStart = subgraphStartBlocks[chain] || 0;
// If requested block is before the subgraph started indexing ( protocol deployment ), return 0 for this chain
if (minStart && blockNum < minStart) return;
const blockForQuery = (blockNum || 0);
const allTokens = await fetchAllSuperTokens(graphUrl, blockForQuery);
const filteredTokens = allTokens.filter((t) => t.isSuperToken);
await getChainBalances(filteredTokens, chain, block, isVesting, api);
return api.getBalances();
}
/**
* List of subgraphs can be retrieved from https://docs.superfluid.finance/docs/technical-reference/subgraph
*/
const subgraphEndpoints = {
arbitrum: {
graph: "https://arbitrum-one.subgraph.x.superfluid.dev",
},
avax: {
graph: "https://avalanche-c.subgraph.x.superfluid.dev",
},
base: {
graph: "https://base-mainnet.subgraph.x.superfluid.dev",
},
bsc: {
graph: "https://bsc-mainnet.subgraph.x.superfluid.dev",
},
// degen: {
// graph: "https://degenchain.subgraph.x.superfluid.dev",
// },
ethereum: {
graph: "https://eth-mainnet.subgraph.x.superfluid.dev",
},
optimism: {
graph: "https://optimism-mainnet.subgraph.x.superfluid.dev",
},
polygon: {
graph: "https://polygon-mainnet.subgraph.x.superfluid.dev",
},
scroll: {
graph: "https://scroll-mainnet.subgraph.x.superfluid.dev",
},
xdai: {
graph: "https://xdai-mainnet.subgraph.x.superfluid.dev",
},
celo: {
graph: "https://celo-mainnet.subgraph.x.superfluid.dev",
},
};
module.exports = {
methodology: `TVL is the value of SuperTokens in circulation. SuperTokens are Superfluid protocol's extension of the ERC20 token standard with additional functionalities like Money Streaming or Distributions. More on SuperTokens here: https://docs.superfluid.finance/docs/concepts/overview/super-tokens`,
// hallmarks: [[1644278400, "Fake ctx hack"]],
};
Object.keys(subgraphEndpoints).forEach((chain) => {
const { graph } = subgraphEndpoints[chain];
module.exports[chain] = {
tvl: async (api, _b, { [chain]: block }) =>
retrieveSupertokensBalances(chain, block, false, api, graph),
}
});