From caa5b43a7976beb68232dc68ae7d928de106c71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gloom0x0=20=F0=9F=A6=BE?= <122207760+Gloom0x0@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:48:18 +0200 Subject: [PATCH 1/4] Add Shift Protocol TVL adapter and config Introduces ShiftTvlFeed ABI, config for contract addresses, and an index.js adapter to calculate TVL for Shift Protocol vaults. TVL is aggregated from USDC deposits using the latest on-chain data. --- projects/shift-protocol/ShiftTvlFeed.json | 266 ++++++++++++++++++++++ projects/shift-protocol/config.js | 5 + projects/shift-protocol/index.js | 38 ++++ 3 files changed, 309 insertions(+) create mode 100644 projects/shift-protocol/ShiftTvlFeed.json create mode 100644 projects/shift-protocol/config.js create mode 100644 projects/shift-protocol/index.js diff --git a/projects/shift-protocol/ShiftTvlFeed.json b/projects/shift-protocol/ShiftTvlFeed.json new file mode 100644 index 00000000000..0e3a9477c5e --- /dev/null +++ b/projects/shift-protocol/ShiftTvlFeed.json @@ -0,0 +1,266 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_accessControlContract", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "CountMustBePositive", + "type": "error" + }, + { + "inputs": [], + "name": "IndexOutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_role", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "TvlUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "accessControlContract", + "outputs": [ + { + "internalType": "contract IAccessControl", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastTvl", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplySnapshot", + "type": "uint256" + } + ], + "internalType": "struct ShiftTvlFeed.TvlData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_count", + "type": "uint256" + } + ], + "name": "getLastTvlEntries", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplySnapshot", + "type": "uint256" + } + ], + "internalType": "struct ShiftTvlFeed.TvlData[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getTvlEntry", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplySnapshot", + "type": "uint256" + } + ], + "internalType": "struct ShiftTvlFeed.TvlData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "init", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_shiftVaultContract", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shiftVault", + "outputs": [ + { + "internalType": "contract IShiftVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "updateTvl", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "updateTvlForDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/projects/shift-protocol/config.js b/projects/shift-protocol/config.js new file mode 100644 index 00000000000..ecafa9df10f --- /dev/null +++ b/projects/shift-protocol/config.js @@ -0,0 +1,5 @@ +module.exports = { + base: [ + "0x6196810Be8e6a1D0fB0Ec10c789966c88340F19b", + ], +}; diff --git a/projects/shift-protocol/index.js b/projects/shift-protocol/index.js new file mode 100644 index 00000000000..eb174bd9526 --- /dev/null +++ b/projects/shift-protocol/index.js @@ -0,0 +1,38 @@ +const { getProvider } = require("@defillama/sdk/build/general"); +const { Contract, formatUnits } = require("ethers"); +const abi = require("./ShiftTvlFeed.json"); +const contractsByChain = require("./config"); + +function getChainTvlFunction(chain) { + return async function tvl(_, _block, _chainBlocks) { + const provider = getProvider(chain); + let totalTvl = 0; + + for (const address of contractsByChain[chain]) { + const contract = new Contract(address, abi, provider); + const [tvlData, decimals] = await Promise.all([ + contract.getLastTvl(), + contract.decimals() + ]); + + const tvlValue = parseFloat(formatUnits(tvlData.value, decimals)); + totalTvl += tvlValue; + } + + return { + usd: totalTvl + }; + }; +} + +const adapter = { + methodology: "TVL is calculated as the aggregated amount of USDC deposited across all of Shift's vault, inclusive of losses or gains." +}; + +for (const chain of Object.keys(contractsByChain)) { + adapter[chain] = { + tvl: getChainTvlFunction(chain) + }; +} + +module.exports = adapter; \ No newline at end of file From 166d65497616ed44972b1adf74cec7c7304c842b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gloom0x0=20=F0=9F=A6=BE?= <122207760+Gloom0x0@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:42:16 +0200 Subject: [PATCH 2/4] Refactor TVL calculation to use ERC20 and getSharePrice Removed ShiftTvlFeed.json ABI and updated TVL logic in index.js to use standard ERC20 totalSupply and decimals, along with a custom getSharePrice() call. The config.js contract address was updated. TVL is now calculated as totalSupply * getSharePrice (formatted with 6 decimals) for each contract. --- projects/shift-protocol/ShiftTvlFeed.json | 266 ---------------------- projects/shift-protocol/config.js | 2 +- projects/shift-protocol/index.js | 79 +++++-- 3 files changed, 61 insertions(+), 286 deletions(-) delete mode 100644 projects/shift-protocol/ShiftTvlFeed.json diff --git a/projects/shift-protocol/ShiftTvlFeed.json b/projects/shift-protocol/ShiftTvlFeed.json deleted file mode 100644 index 0e3a9477c5e..00000000000 --- a/projects/shift-protocol/ShiftTvlFeed.json +++ /dev/null @@ -1,266 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "_accessControlContract", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "AlreadyInitialized", - "type": "error" - }, - { - "inputs": [], - "name": "CountMustBePositive", - "type": "error" - }, - { - "inputs": [], - "name": "IndexOutOfBounds", - "type": "error" - }, - { - "inputs": [], - "name": "NotInitialized", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_role", - "type": "string" - } - ], - "name": "Unauthorized", - "type": "error" - }, - { - "inputs": [], - "name": "ZeroAddress", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "TvlUpdated", - "type": "event" - }, - { - "inputs": [], - "name": "accessControlContract", - "outputs": [ - { - "internalType": "contract IAccessControl", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getLastTvl", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "supplySnapshot", - "type": "uint256" - } - ], - "internalType": "struct ShiftTvlFeed.TvlData", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_count", - "type": "uint256" - } - ], - "name": "getLastTvlEntries", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "supplySnapshot", - "type": "uint256" - } - ], - "internalType": "struct ShiftTvlFeed.TvlData[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "getTvlEntry", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "supplySnapshot", - "type": "uint256" - } - ], - "internalType": "struct ShiftTvlFeed.TvlData", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "init", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_shiftVaultContract", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "shiftVault", - "outputs": [ - { - "internalType": "contract IShiftVault", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "updateTvl", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "updateTvlForDeposit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/projects/shift-protocol/config.js b/projects/shift-protocol/config.js index ecafa9df10f..c4baf360a20 100644 --- a/projects/shift-protocol/config.js +++ b/projects/shift-protocol/config.js @@ -1,5 +1,5 @@ module.exports = { base: [ - "0x6196810Be8e6a1D0fB0Ec10c789966c88340F19b", + "0xaf69Bf9ea9E0166498c0502aF5B5945980Ed1E0E", ], }; diff --git a/projects/shift-protocol/index.js b/projects/shift-protocol/index.js index eb174bd9526..147a9ba466e 100644 --- a/projects/shift-protocol/index.js +++ b/projects/shift-protocol/index.js @@ -1,38 +1,79 @@ -const { getProvider } = require("@defillama/sdk/build/general"); -const { Contract, formatUnits } = require("ethers"); -const abi = require("./ShiftTvlFeed.json"); -const contractsByChain = require("./config"); +const sdk = require('@defillama/sdk'); +const { formatUnits } = require('ethers'); +const contractsByChain = require('./config'); function getChainTvlFunction(chain) { - return async function tvl(_, _block, _chainBlocks) { - const provider = getProvider(chain); + return async (_timestamp, _ethBlock, chainBlocks) => { + const block = chainBlocks[chain]; let totalTvl = 0; - for (const address of contractsByChain[chain]) { - const contract = new Contract(address, abi, provider); - const [tvlData, decimals] = await Promise.all([ - contract.getLastTvl(), - contract.decimals() - ]); + const addresses = contractsByChain[chain] || []; + for (const address of addresses) { + // totalSupply using standard ERC20 short ABI + let totalSupply; + try { + totalSupply = await sdk.api.abi.call({ + target: address, + abi: 'erc20:totalSupply', + block, + chain, + }); + } catch (e) { + // no totalSupply -> skip this contract + console.warn(`totalSupply() missing for ${address} on ${chain}: ${e.message || e}`); + continue; + } - const tvlValue = parseFloat(formatUnits(tvlData.value, decimals)); - totalTvl += tvlValue; + // decimals using standard ERC20 short ABI (fallback to 18 if fails) + let decimals = 18; + try { + const decRes = await sdk.api.abi.call({ + target: address, + abi: 'erc20:decimals', + block, + chain, + }); + decimals = Number(decRes.output); + } catch (e) { + console.warn(`decimals() missing for ${address} on ${chain}, defaulting to 18: ${e.message || e}`); + decimals = 18; + } + + // getSharePrice() - custom function, format with 6 decimals + let sharePrice = 0; + try { + const spRes = await sdk.api.abi.call({ + target: address, + abi: 'function getSharePrice() view returns (uint256)', + block, + chain, + }); + // format with 6 decimals as requested + sharePrice = Number(formatUnits(spRes.output, 6)); + } catch (e) { + // If contract doesn't expose getSharePrice, log and treat as 0 + console.warn(`getSharePrice() missing for ${address} on ${chain}, using 0: ${e.message || e}`); + sharePrice = 0; + } + + const supplyHuman = Number(formatUnits(totalSupply.output, decimals)); + const tvlForAddress = supplyHuman * sharePrice; + totalTvl += tvlForAddress; } return { - usd: totalTvl + usd: totalTvl, }; }; } const adapter = { - methodology: "TVL is calculated as the aggregated amount of USDC deposited across all of Shift's vault, inclusive of losses or gains." + methodology: + 'TVL = totalSupply() (human units using decimals) * getSharePrice() (formatted with 6 decimals). Aggregated across configured contracts.', }; for (const chain of Object.keys(contractsByChain)) { - adapter[chain] = { - tvl: getChainTvlFunction(chain) - }; + adapter[chain] = { tvl: getChainTvlFunction(chain) }; } module.exports = adapter; \ No newline at end of file From 133add44129e17e88a00cf85ff8e4b99811865fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gloom0x0=20=F0=9F=A6=BE?= <122207760+Gloom0x0@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:46:11 +0200 Subject: [PATCH 3/4] Update TVL methodology description in adapter Revised the methodology field in the adapter to provide a clearer explanation of how TVL is calculated, emphasizing the aggregation of total supply and share price across contracts. --- projects/shift-protocol/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/shift-protocol/index.js b/projects/shift-protocol/index.js index 147a9ba466e..312bf558e21 100644 --- a/projects/shift-protocol/index.js +++ b/projects/shift-protocol/index.js @@ -69,7 +69,7 @@ function getChainTvlFunction(chain) { const adapter = { methodology: - 'TVL = totalSupply() (human units using decimals) * getSharePrice() (formatted with 6 decimals). Aggregated across configured contracts.', + 'TVL is calculated by summing total supply of shares distributed to depositors and multiplied by their share price (comprehensive of profit and loss). Aggregated across configured contracts.', }; for (const chain of Object.keys(contractsByChain)) { From f6b12b3dccf4f356460167544f33ac3208d1d309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gloom0x0=20=F0=9F=A6=BE?= <122207760+Gloom0x0@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:39:30 +0200 Subject: [PATCH 4/4] Refactor error handling in TVL calculation Simplifies error handling for ABI calls in the TVL calculation by removing try/catch blocks and checking for missing outputs instead. Logs warnings and applies sensible defaults when data is unavailable. --- projects/shift-protocol/index.js | 66 +++++++++++++++----------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/projects/shift-protocol/index.js b/projects/shift-protocol/index.js index 312bf558e21..2a84ad5ce19 100644 --- a/projects/shift-protocol/index.js +++ b/projects/shift-protocol/index.js @@ -10,50 +10,44 @@ function getChainTvlFunction(chain) { const addresses = contractsByChain[chain] || []; for (const address of addresses) { // totalSupply using standard ERC20 short ABI - let totalSupply; - try { - totalSupply = await sdk.api.abi.call({ - target: address, - abi: 'erc20:totalSupply', - block, - chain, - }); - } catch (e) { - // no totalSupply -> skip this contract - console.warn(`totalSupply() missing for ${address} on ${chain}: ${e.message || e}`); + const totalSupply = await sdk.api.abi.call({ + target: address, + abi: 'erc20:totalSupply', + block, + chain, + }); + + if (!totalSupply || !totalSupply.output) { + console.warn(`totalSupply() missing for ${address} on ${chain}`); continue; } // decimals using standard ERC20 short ABI (fallback to 18 if fails) - let decimals = 18; - try { - const decRes = await sdk.api.abi.call({ - target: address, - abi: 'erc20:decimals', - block, - chain, - }); - decimals = Number(decRes.output); - } catch (e) { - console.warn(`decimals() missing for ${address} on ${chain}, defaulting to 18: ${e.message || e}`); - decimals = 18; + const decRes = await sdk.api.abi.call({ + target: address, + abi: 'erc20:decimals', + block, + chain, + }); + + const decimals = decRes && decRes.output ? Number(decRes.output) : 18; + if (!decRes || !decRes.output) { + console.warn(`decimals() missing for ${address} on ${chain}, defaulting to 18`); } // getSharePrice() - custom function, format with 6 decimals + const spRes = await sdk.api.abi.call({ + target: address, + abi: 'function getSharePrice() view returns (uint256)', + block, + chain, + }); + let sharePrice = 0; - try { - const spRes = await sdk.api.abi.call({ - target: address, - abi: 'function getSharePrice() view returns (uint256)', - block, - chain, - }); - // format with 6 decimals as requested + if (spRes && spRes.output) { sharePrice = Number(formatUnits(spRes.output, 6)); - } catch (e) { - // If contract doesn't expose getSharePrice, log and treat as 0 - console.warn(`getSharePrice() missing for ${address} on ${chain}, using 0: ${e.message || e}`); - sharePrice = 0; + } else { + console.warn(`getSharePrice() missing for ${address} on ${chain}, using 0`); } const supplyHuman = Number(formatUnits(totalSupply.output, decimals)); @@ -76,4 +70,4 @@ for (const chain of Object.keys(contractsByChain)) { adapter[chain] = { tvl: getChainTvlFunction(chain) }; } -module.exports = adapter; \ No newline at end of file +module.exports = adapter;