Skip to content

Conversation

RohanNero
Copy link
Contributor

@RohanNero RohanNero commented Oct 3, 2025

NOTE

Please enable "Allow edits by maintainers" while putting up the PR.


  • If you would like to add a tvl adapter please submit the PR here.
  1. Once your adapter has been merged, it takes time to show on the UI. If more than 24 hours have passed, please let us know in Discord.
  2. Please fill the form below only if the PR is for listing a new protocol else it can be ignored/replaced with reason/details about the PR
  3. For updating listing info It is a different repo, you can find your listing in this file: https://github.com/DefiLlama/defillama-server/blob/master/defi/src/protocols/data4.ts, you can edit it there and put up a PR
  4. Do not edit/push poackage.json/package-lock.json file as part of your changes
  5. No need to go to our discord/other channel and announce that you've created a PR, we monitor all PRs and will review it asap

Add fee tracking for IPOR Protocol's PlasmaVaults. Their listed vaults can be found on their app and the documentation related to their management and performance fees can be found here.

Note: I only added tracking for Management fees based on ManagementFeeRealized event logs, the 2% performance fee charge still needs to be tracked.

@llamabutler
Copy link

The ipor-protocol adapter exports:

> [email protected] test
> ts-node --transpile-only cli/testAdapter.ts fees ipor-protocol

🦙 Running IPOR-PROTOCOL adapter 🦙
---------------------------------------------------
Start Date:	Thu, 02 Oct 2025 20:56:13 GMT
End Date:	Fri, 03 Oct 2025 20:56:13 GMT
---------------------------------------------------

chain     | Daily fees | Daily revenue | Start Time
---       | ---        | ---           | ---       
ethereum  | 456        | 456           | 1/1/2024  
arbitrum  | 28         | 28            | 1/1/2024  
unichain  | 0          | 0             | 1/1/2024  
tac       | 0          | 0             | 1/1/2024  
base      | 105        | 105           | 1/1/2024  
Aggregate | 589        | 589           |           

@llamabutler
Copy link

The ipor-protocol adapter exports:

> [email protected] test
> ts-node --transpile-only cli/testAdapter.ts fees ipor-protocol

🦙 Running IPOR-PROTOCOL adapter 🦙
---------------------------------------------------
Start Date:	Thu, 02 Oct 2025 20:57:50 GMT
End Date:	Fri, 03 Oct 2025 20:57:50 GMT
---------------------------------------------------

chain     | Daily fees | Daily revenue
---       | ---        | ---          
ethereum  | 456        | 456          
arbitrum  | 28         | 28           
unichain  | 0          | 0            
tac       | 0          | 0            
base      | 105        | 105          
Aggregate | 589        | 589          

// Vaults & token mappings per chain
const vaultsPerChain: Record<string, { vault: string, token: string }[]> = {
[CHAIN.ETHEREUM]: [
{ vault: "0x20e934c725b6703f0aC696F1689008057dB9Ac44", token: ADDRESSES.ethereum.DAI }, // IPOR DAI Ethereum
Copy link
Member

Choose a reason for hiding this comment

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

please keep these vaults dynamic as we can't maintain the list manually

Copy link
Contributor Author

Choose a reason for hiding this comment

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

got it sorry, I'll fix this asap

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the list of token addresses and replaced it with a call to each vault's asset() function, but I'm not sure how to make the vault addresses dynamic since their PlasmaVaultFactory doesn't seem to be deployed yet.

@treeoflife2 treeoflife2 self-assigned this Oct 4, 2025
@llamabutler
Copy link

The ipor-protocol adapter exports:

> [email protected] test
> ts-node --transpile-only cli/testAdapter.ts fees ipor-protocol

🦙 Running IPOR-PROTOCOL adapter 🦙
---------------------------------------------------
Start Date:	Fri, 03 Oct 2025 20:22:04 GMT
End Date:	Sat, 04 Oct 2025 20:22:04 GMT
---------------------------------------------------

chain     | Daily fees | Daily revenue | Start Time
---       | ---        | ---           | ---       
ethereum  | 183        | 183           | 29/9/2024 
arbitrum  | 13         | 13            | 3/9/2024  
base      | 163        | 163           | 8/11/2024 
unichain  | 0          | 0             | 18/6/2025 
tac       | 0          | 0             | 11/7/2025 
Aggregate | 359        | 359           |           

@treeoflife2
Copy link
Member

@RohanNero can you replace hardcoded with how tvl adapter is fetching vaults, from github
https://github.com/DefiLlama/DefiLlama-Adapters/blob/main/projects/ipor-fusion/index.js

@llamabutler
Copy link

The ipor-protocol adapter exports:

> [email protected] test
> ts-node --transpile-only cli/testAdapter.ts fees ipor-protocol

🦙 Running IPOR-PROTOCOL adapter 🦙
---------------------------------------------------
Start Date:	Mon, 06 Oct 2025 11:24:21 GMT
End Date:	Tue, 07 Oct 2025 11:24:21 GMT
---------------------------------------------------

chain     | Daily fees | Daily revenue | Start Time
---       | ---        | ---           | ---       
ethereum  | 447        | 447           | 29/9/2024 
arbitrum  | 16         | 16            | 3/9/2024  
base      | 134        | 134           | 8/11/2024 
unichain  | 0          | 0             | 18/6/2025 
tac       | 0          | 0             | 11/7/2025 
Aggregate | 597        | 597           |           

@RohanNero
Copy link
Contributor Author

@RohanNero can you replace hardcoded with how tvl adapter is fetching vaults, from github https://github.com/DefiLlama/DefiLlama-Adapters/blob/main/projects/ipor-fusion/index.js

Thank you, that works perfectly

@treeoflife2
Copy link
Member

@RohanNero we have to also track the yield earned by vaults, which would be counted as supplysiderevenue
https://docs.llama.fi/list-your-project/other-dashboards

@RohanNero
Copy link
Contributor Author

@RohanNero we have to also track the yield earned by vaults, which would be counted as supplysiderevenue https://docs.llama.fi/list-your-project/other-dashboards

@treeoflife2 I've added a function that attempts to track the yield earned by the vault, but I'm unsure if I've written it correctly (output screenshots below).

const IPOR_GITHUB_ADDRESSES_URL = "https://raw.githubusercontent.com/IPOR-Labs/ipor-abi/refs/heads/main/mainnet/addresses.json";
const eventAbis = {
    managementFee: "event ManagementFeeRealized(uint256 unrealizedFeeInUnderlying, uint256 unrealizedFeeInShares)",
}
const methodology = {
    Fees: 'Management fees collected by the protocol are included in event logs emitted by each vault contract.',
    Revenue: 'Total fees collected by the protocol.',
    SupplySideRevenue: 'Revenue earned by PlasmaVault liquidity providers.'
}

const addManagementFees = async (options: FetchOptions, vaults: string[], vaultToToken: Record<string, string>, dailyFees: Balances, dailyRevenue: Balances) => {
    const logs = await options.api.getLogs({
        targets: vaults,
        eventAbi: eventAbis.managementFee,
        fromBlock: await options.getFromBlock(),
        toBlock: await options.getToBlock(),
    })

    logs.forEach((log: any) => {
        const token = vaultToToken[log.address.toLowerCase()]
        if (token) {
            dailyFees.add(token, log.args[0])
            dailyRevenue.add(token, log.args[0])
        }
    })
}

const addSupplySideRevenue = async (options: FetchOptions, vaults: string[], vaultToToken: Record<string, string>, dailySupplySideRevenue: Balances) => {

    const fromBlock = await options.getFromBlock();
    const toBlock = await options.getToBlock();

    const startAssets = await options.fromApi.multiCall({
        abi: 'function totalAssets() view returns (uint256)',
        calls: vaults.map(v => ({ target: v })),
        block: fromBlock,
        skipCache: true,
        requery: true,
        permitFailure: true
    });

    const endAssets = await options.toApi.multiCall({
        abi: 'function totalAssets() view returns (uint256)',
        calls: vaults.map(v => ({ target: v })),
        skipCache: true,
        requery: true,
        permitFailure: true,
        block: toBlock,
    });

    const depositLogs = await options.api.getLogs({
        targets: vaults,
        eventAbi: 'event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)',
        fromBlock: fromBlock,
        toBlock: toBlock,
    });

    const withdrawLogs = await options.api.getLogs({
        targets: vaults,
        eventAbi: 'event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares)',
        fromBlock: fromBlock,
        toBlock: toBlock,
    });

    const netDeposits: Record<string, bigint> = {};
    depositLogs.forEach(log => {
        const vault = log.address.toLowerCase();
        netDeposits[vault] = (netDeposits[vault] || 0n) + BigInt(log.args.assets);
    });
    withdrawLogs.forEach(log => {
        const vault = log.address.toLowerCase();
        netDeposits[vault] = (netDeposits[vault] || 0n) - BigInt(log.args.assets);
    });

    vaults.forEach((vault, i) => {

        if (!startAssets[i] || !endAssets[i]) return;
        const assetsGrowth = BigInt(endAssets[i]) - BigInt(startAssets[i]);
        const net = netDeposits[vault.toLowerCase()] || 0n;
        const rev = assetsGrowth - net;

        if (rev > 0) {
            const token = vaultToToken[vault.toLowerCase()];
            dailySupplySideRevenue.add(token, rev);
        }
    });
}


const fetch = () => {
    return async (options: FetchOptions) => {
        const dailyFees = options.createBalances()
        const dailyRevenue = options.createBalances()
        const dailySupplySideRevenue = options.createBalances()
        const config = await getConfig('ipor/assets', IPOR_GITHUB_ADDRESSES_URL);
        const chainConfig = config[options.chain];
        if (!chainConfig || !chainConfig.vaults) {
            return { dailyFees, dailyRevenue };
        }

        const vaults = chainConfig.vaults.map((vault: any) => vault.PlasmaVault);
        const assetCalls = await options.api.multiCall({
            abi: 'function asset() view returns (address)',
            calls: vaults.map((v: string) => ({ target: v })),
            permitFailure: true,
        })

        const vaultToToken: Record<string, string> = {}
        vaults.forEach((v: string, i: number) => {
            if (assetCalls[i]) {
                vaultToToken[v.toLowerCase()] = assetCalls[i]
            }
        })

        await addManagementFees(options, vaults, vaultToToken, dailyFees, dailyRevenue);
        await addSupplySideRevenue(options, vaults, vaultToToken, dailySupplySideRevenue);

        return { dailyFees, dailyRevenue, dailySupplySideRevenue }
    }
}

const adapter: SimpleAdapter = {
    version: 2,
    adapter: {
        [CHAIN.ETHEREUM]: {
            fetch: fetch(),
            start: "9-29-2024",
        },
        [CHAIN.ARBITRUM]: {
            fetch: fetch(),
            start: "9-3-2024",
        },
        [CHAIN.BASE]: {
            fetch: fetch(),
            start: "11-8-2024",
        },
        [CHAIN.UNICHAIN]: {
            fetch: fetch(),
            start: "6-18-2025",
        },
        [CHAIN.TAC]: {
            fetch: fetch(),
            start: "7-11-2025",
        },
    },
    methodology
}

export default adapter;

Test output examples:

image image image

@treeoflife2
Copy link
Member

@RohanNero logic looks correct, small changes, dailyfees would include supplysiderevenue too, and can you check if management fees is included in the supplyside logic or no? to be sure there is no double counting, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants