Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
172 changes: 172 additions & 0 deletions projects/dexfinance-vault-v2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
const ADDRESSES = require('../helper/coreAssets.json');
const { abi } = require("../dexfinance-vault/abi");
const { sumTokens2, unwrapSlipstreamNFT, unwrapUniswapV3NFT } = require('../helper/unwrapLPs');

const CONFIG = {
sonic: {
factory: "0x095d35c49d2d0ea2eba3e2f9e377966db35af7e2",
},
avax: {
factory: "0x5764dad2fd4b6918949c6ae86081819ca8c19749",
},
bsc: {
factory: "0xc9dc65aed28bdb016726d32d0f8c2cd5c9461961",
},
ethereum: {
factory: "0x4c1a8a04577286ce58d0723b1a90160f380e550a",
},
base: {
factory: "0xcb34f261a5284554bb9fea8aa12a0578c4ba3fc6",
},
arbitrum: {
factory: "0x061f8132b344cb2a32d3895eb3ebc2ff87455f79",
},
};

const GDEX_TOKEN = "0x53Cb59D32a8d08fC6D3f81454f150946A028A44d";
const STAKING_CONTRACT = "0xd7D11E2d4E8E7b65E905aa9d16E488C37195Ca62";
const POOL_ADDRESS = "0x65B6ee9CaC744D4eed9886406EAD6bc4E5681068";

const getVaults = async (api, factory) => {
const vaults = await api.fetchList({ lengthAbi: abi.factory.vaultsLength, itemAbi: abi.factory.vaults, target: factory, permitFailure: true });
const farmsAll = await api.fetchList({ lengthAbi: abi.vault.farmsLength, itemAbi: abi.vault.farms, targets: vaults, groupedByInput: true, permitFailure: true });

return vaults.map((vault, i) => {
const farms = farmsAll[i] || [];
return farms.map(farm => ({ vault, farm }));
}).flat();
};

const getVaultsConnectors = async (api, vaultFarms) => {
const connectorsCalls = vaultFarms.map(({ farm, vault }) => ({ params: farm.beacon, target: vault }));
const connectors = await api.multiCall({ abi: abi.vault.farmConnector, calls: connectorsCalls, permitFailure: true });

return vaultFarms
.map((item, i) => {
const connector = connectors[i];
if (!connector) return null;
delete item.farm.data;
return { ...item, connector };
}).filter(item => item !== null);
};

const getChainFarms = async (api) => {
const { factory } = CONFIG[api.chain];
const vaultFarms = await getVaults(api, factory);
const vaultFarmsWithConnectors = await getVaultsConnectors(api, vaultFarms);

const calls = vaultFarmsWithConnectors.map(({ connector }) => connector);
const [stakingTokens, tokenIds] = await Promise.all([
api.multiCall({ calls, abi: abi.farm.stakingToken, permitFailure: true }),
api.multiCall({ calls, abi: abi.farm.tokenId, permitFailure: true }),
]);

const nftFarms = [];
const tokenFarms = [];
vaultFarmsWithConnectors.forEach((item, i) => {
if (!stakingTokens[i]) return;
if (tokenIds[i] && +tokenIds[i] > 0) {
nftFarms.push({ ...item, stakingToken: stakingTokens[i], tokenId: tokenIds[i] });
} else {
tokenFarms.push({ ...item, stakingToken: stakingTokens[i] });
}
});

return { nftFarms, tokenFarms };
};

const tvl = async (api) => {
const { nftFarms, tokenFarms } = await getChainFarms(api);

// NFT positions
const nftPositionMapping = {};
nftFarms.forEach(({ stakingToken, tokenId }) => {
const nft = stakingToken.toLowerCase();
if (!nftPositionMapping[nft]) nftPositionMapping[nft] = [];
nftPositionMapping[nft].push(tokenId);
});
const nftAddresses = Object.keys(nftPositionMapping);
const [factoryResults, deployerResults, poolManagerResults] = await Promise.all([
api.multiCall({ calls: nftAddresses, abi: 'address:factory', permitFailure: true }),
api.multiCall({ calls: nftAddresses, abi: 'address:deployer', permitFailure: true }),
api.multiCall({ calls: nftAddresses, abi: 'address:poolManager', permitFailure: true }),
]);

// detect Shadow: deployer exists AND deployer has RamsesV3Factory
const deployersToCheck = deployerResults.map((d, i) => d ? { target: d, idx: i } : null).filter(Boolean);
const ramsesFactoryResults = deployersToCheck.length
? await api.multiCall({ calls: deployersToCheck.map(d => d.target), abi: 'address:RamsesV3Factory', permitFailure: true })
: [];
const shadowSet = new Set();
deployersToCheck.forEach((d, j) => { if (ramsesFactoryResults[j]) shadowSet.add(d.idx); });

// detect Slipstream and Algebra from factory
const factoriesWithIndex = factoryResults.map((f, i) => f ? { target: f, idx: i } : null).filter(Boolean);
const [poolImplResults, poolByPairResults] = factoriesWithIndex.length ? await Promise.all([
api.multiCall({ calls: factoriesWithIndex.map(f => f.target), abi: 'address:poolImplementation', permitFailure: true }),
api.multiCall({ calls: factoriesWithIndex.map(f => ({ target: f.target, params: [ADDRESSES.null, ADDRESSES.null] })), abi: 'function poolByPair(address, address) view returns (address)', permitFailure: true }),
]) : [[], []];
const slipstreamSet = new Set();
const algebraSet = new Set();
factoriesWithIndex.forEach((f, j) => {
if (poolImplResults[j]) slipstreamSet.add(f.idx);
else if (poolByPairResults[j] !== null && poolByPairResults[j] !== undefined) algebraSet.add(f.idx);
});
Comment on lines +111 to +114
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Algebra detection may have false positives with zero address.

The check poolByPairResults[j] !== null && poolByPairResults[j] !== undefined would pass if poolByPair returns the zero address for non-existent pairs. This could incorrectly classify non-Algebra factories as Algebra.

🐛 Suggested fix: Also check for zero address
   factoriesWithIndex.forEach((f, j) => {
     if (poolImplResults[j]) slipstreamSet.add(f.idx);
-    else if (poolByPairResults[j] !== null && poolByPairResults[j] !== undefined) algebraSet.add(f.idx);
+    else if (poolByPairResults[j] && poolByPairResults[j] !== ADDRESSES.null) algebraSet.add(f.idx);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/dexfinance-vault-v2/index.js` around lines 111 - 114, The current
classification loop using factoriesWithIndex incorrectly treats a zero address
from poolByPairResults[j] as a valid Algebra pool; update the condition inside
the forEach (which references factoriesWithIndex, poolImplResults,
poolByPairResults, slipstreamSet, algebraSet, and f.idx) to also ensure
poolByPairResults[j] is not the zero address (e.g. compare against
'0x0000000000000000000000000000000000000000' or a shared ADDRESS_ZERO constant)
before adding f.idx to algebraSet, keeping the existing slipstreamSet check
intact.


for (let i = 0; i < nftAddresses.length; i++) {
const positionIds = nftPositionMapping[nftAddresses[i]];
if (shadowSet.has(i)) {
await unwrapSlipstreamNFT({ api, nftAddress: nftAddresses[i], positionIds, isShadow: true });
} else if (slipstreamSet.has(i)) {
await unwrapSlipstreamNFT({ api, nftAddress: nftAddresses[i], positionIds });
} else if (algebraSet.has(i)) {
await unwrapUniswapV3NFT({ api, nftAddress: nftAddresses[i], uniV3ExtraConfig: { positionIds }, isAlgebra: true });
} else if (poolManagerResults[i]) {
await sumTokens2({ api, uniV4ExtraConfig: { nftAddress: nftAddresses[i], positionIds } });
} else if (factoryResults[i]) {
await sumTokens2({ api, uniV3ExtraConfig: { nftAddress: nftAddresses[i], positionIds } });
}
}

// ERC20 tokens and LPs
const liquidityCalls = tokenFarms.map(({ connector }) => ({ target: connector, params: [connector] }));
const liquidities = await api.multiCall({ calls: liquidityCalls, abi: abi.vault.liquidity, permitFailure: true });
tokenFarms.forEach(({ stakingToken }, i) => {
if (liquidities[i]) api.add(stakingToken, liquidities[i]);
});
await sumTokens2({ api, resolveLP: true });
Comment on lines +131 to +137
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if liquidity() is called on vault or connector in existing dexfinance adapters
echo "=== Checking existing dexfinance adapter patterns ==="
rg -n "abi\.vault\.liquidity" projects/dexfinance*/

echo ""
echo "=== Checking how liquidity calls are structured ==="
rg -nB2 -A2 "liquidityCalls" projects/dexfinance*/

Repository: DefiLlama/DefiLlama-Adapters

Length of output: 2996


🏁 Script executed:

#!/bin/bash
# Check what properties are available in tokenFarms
echo "=== Checking tokenFarms structure in getChainFarms ==="
rg -nB5 -A10 "function getChainFarms" projects/dexfinance-vault-v2/

echo ""
echo "=== Checking farm/vault structure ==="
rg -n "connector\|vault\|farm" projects/dexfinance-vault-v2/index.js | head -30

echo ""
echo "=== Looking for ABI definitions ==="
rg -n "abi" projects/dexfinance-vault-v2/index.js | head -20

Repository: DefiLlama/DefiLlama-Adapters

Length of output: 1860


🏁 Script executed:

#!/bin/bash
# Check the beginning of dexfinance-vault-v2/index.js to find getChainFarms
echo "=== First 150 lines of dexfinance-vault-v2/index.js ==="
head -150 projects/dexfinance-vault-v2/index.js | cat -n

echo ""
echo "=== Check the ABI definitions ==="
cat projects/dexfinance-vault/abi.js

Repository: DefiLlama/DefiLlama-Adapters

Length of output: 9338


Clarify ABI usage for liquidity() calls.

The code calls abi.vault.liquidity on the connector address (line 132), but the ABI is defined under vault: in the ABI file. This differs from v1 dexfinance adapter which calls the same method on calculationConnector instead. If v2 connectors expose their own liquidity(address) method, this architectural change should be documented. If not, the method should be called on the vault address with the connector as a parameter (as in v1), and the ABI should be repositioned accordingly to reflect its actual target.

The same pattern appears at line 163-164. The permitFailure: true flag masks call failures, which is acceptable if intentional, but the target mismatch should be clarified.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/dexfinance-vault-v2/index.js` around lines 131 - 137, The
liquidity() multicall is currently using abi.vault.liquidity with the connector
address as target (see liquidityCalls and abi.vault.liquidity), which may be
wrong: either connectors must implement liquidity(address) or the call should
target the vault (as in v1 where calculationConnector was used). Update the
multicall to target the correct contract (vault or calculationConnector) and/or
move the liquidity ABI to the correct namespace to reflect its real target, and
add a short code comment documenting which contract (connector vs vault) is
expected to expose liquidity(address); keep permitFailure: true only if silent
failures are intentional.

};

module.exports = {
hallmarks: [
['2025-04-12', "Launch on Base"],
['2025-05-03', "Launch on Sonic"],
['2025-08-12', "Launch on Avalanche"],
['2025-10-03', "Launch on BNB Chain"],
['2026-01-29', "Launch on Ethereum"],
['2026-02-17', "Launch on Arbitrum"],
],
sonic: { tvl },
avax: { tvl },
bsc: { tvl },
ethereum: { tvl },
base: {
tvl: async (api) => {
await tvl(api)
await sumTokens2({ api, tokens: [ADDRESSES.base.WETH], owners: [POOL_ADDRESS] })
api.removeTokenBalance(GDEX_TOKEN)
},
staking: async (api) => {
await sumTokens2({ api, tokens: [GDEX_TOKEN], owners: [STAKING_CONTRACT, POOL_ADDRESS] })
// gDEX held directly by vault farm connectors
const { tokenFarms } = await getChainFarms(api);
const liquidityCalls = tokenFarms.map(({ connector }) => ({ target: connector, params: [connector] }));
const liquidities = await api.multiCall({ calls: liquidityCalls, abi: abi.vault.liquidity, permitFailure: true });
tokenFarms.forEach(({ stakingToken }, i) => {
if (liquidities[i] && stakingToken.toLowerCase() === GDEX_TOKEN.toLowerCase())
api.add(GDEX_TOKEN, liquidities[i]);
});
},
},
arbitrum: { tvl },
};
53 changes: 38 additions & 15 deletions projects/helper/unwrapLPs.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ const factories = {}

const getFactoryKey = (chain, nftAddress) => `${chain}:${nftAddress}`.toLowerCase()

const algebraPositionsABI = 'function positions(uint256) view returns (uint88 nonce, address operator, address token0, address token1, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)';
const algebraGlobalStateABI = 'function globalState() view returns (uint160 price, int24 tick, uint16 lastFee, uint8 pluginConfig, uint16 communityFee, bool unlocked)';

async function unwrapUniswapV3NFT({
balances,
owner,
Expand All @@ -347,7 +350,8 @@ async function unwrapUniswapV3NFT({
api,
blacklistedTokens = [],
whitelistedTokens = [],
uniV3ExtraConfig = {}
uniV3ExtraConfig = {},
isAlgebra = false,
}) {
const chain = api.chain

Expand Down Expand Up @@ -389,7 +393,7 @@ async function unwrapUniswapV3NFT({
}

const positions = await api.multiCall({
abi: uniV3ABI.positions,
abi: isAlgebra ? algebraPositionsABI : uniV3ABI.positions,
target: nftAddress,
calls: positionIds
})
Expand All @@ -398,14 +402,20 @@ async function unwrapUniswapV3NFT({
positions.forEach(position => lpInfo[getKey(position)] = position)
const lpInfoArray = Object.values(lpInfo)

const poolInfos = await api.multiCall({
abi: uniV3ABI.getPool,
target: factory,
calls: lpInfoArray.map((info) => ({ params: [info.token0, info.token1, info.fee] })),
})
const poolInfos = isAlgebra
? await api.multiCall({
abi: 'function poolByPair(address, address) view returns (address)',
target: factory,
calls: lpInfoArray.map((info) => ({ params: [info.token0, info.token1] })),
})
: await api.multiCall({
abi: uniV3ABI.getPool,
target: factory,
calls: lpInfoArray.map((info) => ({ params: [info.token0, info.token1, info.fee] })),
})

const slot0 = await api.multiCall({
abi: uniV3ABI.slot0,
abi: isAlgebra ? algebraGlobalStateABI : uniV3ABI.slot0,
calls: poolInfos
})

Expand All @@ -418,6 +428,7 @@ async function unwrapUniswapV3NFT({
let { token0, token1, fee } = position
token0 = token0.toLowerCase()
token1 = token1.toLowerCase()
if (isAlgebra) return `${token0}-${token1}`
return `${token0}-${token1}-${fee}`
}

Expand All @@ -434,8 +445,10 @@ async function unwrapUniswapV3NFT({
const positionId = position.tokenId ?? position.tokenID ?? position.id
if (positionId && blacklistedPositionIds.has(String(positionId))) return

const poolKey = `${token0.toLowerCase()}-${token1.toLowerCase()}-${position.fee}`.toLowerCase()
if (blacklistedPools.includes(poolKey)) return
if (!isAlgebra) {
const poolKey = `${token0.toLowerCase()}-${token1.toLowerCase()}-${position.fee}`.toLowerCase()
if (blacklistedPools.includes(poolKey)) return
}

let amount0 = 0
let amount1 = 0
Expand Down Expand Up @@ -541,17 +554,25 @@ async function getPositionIdsByNftAddress({ api, nftsAndOwners, }) {
return positionIdsByNftAddress
}

async function unwrapSlipstreamNFT({ api, balances, owner, positionIds = [], nftAddress, blacklistedTokens = [], whitelistedTokens = [], uniV3ExtraConfig = {}, }) {
const shadowV3PositionsABI = 'function positions(uint256) view returns (address token0, address token1, int24 tickSpacing, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)';

async function unwrapSlipstreamNFT({ api, balances, owner, positionIds = [], nftAddress, blacklistedTokens = [], whitelistedTokens = [], uniV3ExtraConfig = {}, isShadow = false, }) {
if (!balances) balances = api.getBalances()
const chain = api.chain

blacklistedTokens = getUniqueAddresses(blacklistedTokens, chain)
whitelistedTokens = getUniqueAddresses(whitelistedTokens, chain)
let nftIdFetcher = uniV3ExtraConfig.nftIdFetcher ?? nftAddress

const factoryKey = getFactoryKey(chain, nftAddress)
if (!factories[factoryKey]) factories[factoryKey] = api.call({ target: nftAddress, abi: uniV3ABI.factory, })
let factory = (await factories[factoryKey])
let factory
if (isShadow) {
const deployer = await api.call({ target: nftAddress, abi: 'address:deployer' })
factory = await api.call({ target: deployer, abi: 'address:RamsesV3Factory' })
} else {
const factoryKey = getFactoryKey(chain, nftAddress)
if (!factories[factoryKey]) factories[factoryKey] = api.call({ target: nftAddress, abi: uniV3ABI.factory, })
factory = (await factories[factoryKey])
}

if ((!positionIds || positionIds.length === 0) && owner) { // if positionIds are not provided and owner address is passed
const nftPositions = await api.call({ target: nftIdFetcher, params: owner, abi: 'erc20:balanceOf' })
Expand All @@ -561,7 +582,8 @@ async function unwrapSlipstreamNFT({ api, balances, owner, positionIds = [], nft
}))
}

const positions = (await api.multiCall({ abi: slipstreamNftABI.positions, target: nftAddress, calls: positionIds, }))
const positionsAbi = isShadow ? shadowV3PositionsABI : slipstreamNftABI.positions
const positions = (await api.multiCall({ abi: positionsAbi, target: nftAddress, calls: positionIds, }))

const lpInfo = {}
positions.forEach(position => lpInfo[getKey(position)] = position)
Expand Down Expand Up @@ -1284,6 +1306,7 @@ module.exports = {
PANCAKE_NFT_ADDRESS,
unwrapUniswapLPs,
unwrapSlipstreamNFT,
unwrapUniswapV3NFT,
sumTokens,
genericUnwrapCvx,
unwrapLPsAuto,
Expand Down
Loading