DeFi points integration instructions

This page is dedicated for DeFi protocols utilizing Mellow LRTs and helps to setup points distribution for legitimate users of DeFi protocol.

DeFi Integrations

Get contracts

Async function to load contracts used to hold/manage LRTs

loadContracts: (toBlock: number) => Promise<void>;

This function is used to populate the following variables.

/**
 * Metadata
*/
defiPools: IDefiPool[];
/**
 * List of contracts holding LRTs e.g. UniswapV3 Pool, Pendle SY, Zircuit ZtakingPool
*/
vaultContracts: { [vault: string]: string[] };

Get user shares

Async function to load user shares between 2 blocks (inclusive).

This function is called for all contracts defined in vaultContracts.

loadUserShares: (contract: string, fromBlock: number, toBlock: number) =>
  Promise<IDeFiUserShare[]>;

A user share represents the amount of LRT owned by an address pro-rata to others.

User shares are sorted by block and a new entry must be added each time LRT ratios change.

Example:

blockuseramountvaultprotocol_address

1

0x01

100

0x0

0x0

1

0x02

200

0x0

0x0

1

0x03

50

0x0

0x0

3

0x03

20

0x0

0x0

4

0x01

150

0x0

0x0

  • At block 1, 0x01 owns ~28% of the user shares (100 / (100 + 200 + 50)), 0x02 owns ~57% and 0x03 owns ~14%.

  • At block 3, 0x03 underlying LRT balance changed, he now owns ~6% of the user shares (20 / (100 + 200 + 20)), while 0x01 owns ~31% and 0x02 owns ~62%.

Example

import { PublicClient } from "viem";

export class ZircuitDeFiCollector implements IDeFiCollector {
  chainId: number;
  client: PublicClient;
  vaultContracts = {};
  defiPools: IDefiPool[] = [];

  constructor(chainId: number, client: PublicClient) {
    this.chainId = chainId;
    this.client = client;
  }

  public async loadContracts() {
    if (this.chainId === 1) {
      this.vaultContracts = {
        "0x7a4EffD87C2f3C55CA251080b1343b605f327E3a": [
          "0xF047ab4c75cebf0eB9ed34Ae2c186f3611aEAfa6",
        ],
        "0x84631c0d0081FDe56DeB72F6DE77abBbF6A9f93a": [
          "0x84631c0d0081FDe56DeB72F6DE77abBbF6A9f93a",
        ],
        // ...
      };
      this.defiPools = [
        {
          id: "pool-zircuit-rstETH",
          name: "rstETH",
          protocol: "Zircuit",
          vault: "0x7a4EffD87C2f3C55CA251080b1343b605f327E3a",
          protocol_address: "0xF047ab4c75cebf0eB9ed34Ae2c186f3611aEAfa6",
          url: "https://stake.zircuit.com/",
          boost: 2,
        },
        {
          id: "pool-zircuit-re7lrt",
          name: "Re7LRT",
          protocol: "Zircuit",
          vault: "0x84631c0d0081FDe56DeB72F6DE77abBbF6A9f93a",
          protocol_address: "0xF047ab4c75cebf0eB9ed34Ae2c186f3611aEAfa6",
          url: "https://stake.zircuit.com/",
          boost: 2,
        },
        // ...
      ];
    }
  }

  public async loadUserShares(
    contract: string, // ZtakingPool
    fromBlock: number,
    toBlock: number
  ) {
    const userShares: IDeFiUserShare[] = [];
    const vaults = Object.keys(this.vaultContracts);
    const depositEvents = await getDepositEvents(contract);
    const withdrawalEvents = await getWithdrawalEvents(contract);
    // balance changes events
    const events = [...depositEvents, ...withdrawalEvents].sort(
      (a, b) => a.block - b.block
    );

    for (const event of events) {
      // get user vaults balances at this block
      const balances = await this.client.getMulticallBalance(
        event.block,
        event.user,
        vaults
      );
      for (const balance of balances) {
        userShares.push({
          block: event.block,
          user: event.user,
          vault: balance.vault,
          protocol_address: contract,
        });
      }
    }

    return userShares;
  }
}

Interface

interface IDeFiUserShare {
  block: number;
  /**
   * User address (checksummed)
   */
  user: string;
  /**
   * Share amount
   */
  amount: bigint;
  /**
   * Vault address (checksummed)
   */
  vault: string;
  /**
   * Protocol address (checksummed) e.g. LP, SY, Credit Account
   */
  protocol_address: string;
}

/**
 * Pool metadata
 */
export interface IDefiPool {
  /**
   * Unique pool identifier
   */
  id: string;
  /**
   * Pool display name
   */
  name: string;
  /**
   * Protocol display name
   */
  protocol: string;
  /**
   * Vault address (checksummed)
   */
  vault: string;
  /**
   * Protocol address (checksummed) e.g. LP, SY, Credit Account
   */
  protocol_address: string;
  /**
   * Link to protocol pool page
   */
  url: string;
  /**
   * Mellow points multiplier e.g. 2, 3 for 2x or 3x boost
   */
  boost: number;
}

export interface IDeFiCollector {
  /**
   * Metadata
   */
  defiPools: IDefiPool[];

  /**
   * LRT holders e.g. UniswapV3 Pool, Pendle SY
   */
  vaultContracts: { [vault: string]: string[] };

  loadContracts: (toBlock: number) => Promise<void>;

  loadUserShares: (
    contract: string,
    fromBlock: number,
    toBlock: number
  ) => Promise<IDeFiUserShare[]>;
}