Curve Liquidity Monitoring

This function monitors the liquidity of a Curve pool by comparing the total supply of the liquidity token with the sum of the balances of the underlying tokens. The function will be triggered on every change to the balances inside the swap token and will send the two liquidity metrics to the function's webhook. We use the sbcore.feeds module to retrieve the live price feed of the tokens.

In this example we use a variable slot trigger to monitor the token balances:

{
    "type": "TRIGGER_TYPE_STORAGE_ACCESS",
    "variable": {
        "variable_slot": "0x1"
    },
    "storage_address": "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7"
}
import { ethers } from "https://cdn.skypack.dev/[email protected]";

const providerString = "http://chain-provider";

// Curve swap contract and corresponding liquidity token addresses
const swapAddress = "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7";
const swapTokenList = [
    { address: "0x6b175474e89094c44da98b954eedeac495271d0f", decimals: 18 }, // DAI
    { address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", decimals: 6 }, // USDC
    { address: "0xdac17f958d2ee523a2206206994597c13d831ec7", decimals: 6 }, // USDT
];

const liquidityTokenAddress = "0x6c3f90f043a72fa612cbac8115ee7e52bde6e490";
const liquidityTokenDecimals = 18;

export async function triggerHandler(context, data) {
    const totalCoinsValue = await getCoinsUSDValue(
        swapAddress,
        swapTokenList,
        context.blockNumber,
    );
    const liquidityTokenUSDValue = await getLiquidityTokenUSDValue(
        liquidityTokenAddress,
        liquidityTokenDecimals,
        context.blockNumber,
    );

    return {
        totalCoinsValue: totalCoinsValue,
        liquidityTokenValue: liquidityTokenUSDValue,
        valueDifference: Math.abs(totalCoinsValue - liquidityTokenUSDValue),

        context: context,
        data: data,
    };
}

async function getCoinsUSDValue(swapAddress, tokenList, blockNumber) {
    const provider = new ethers.providers.JsonRpcProvider(providerString);

    const swapAbi = ["function balances(uint256) view returns (uint256)"];
    const swapContract = new ethers.Contract(swapAddress, swapAbi, provider);

    let usdCoinsValue = 0;
    for (const [index, token] of tokenList.entries()) {
        let coinAmount = await swapContract
            .balances(index, { blockTag: blockNumber })
            .then((result) => Number(result.div("1" + "0".repeat(token.decimals))));

        let coinValue = await sbcore.feeds.getTokenPrice(token.address);
        usdCoinsValue += coinAmount * coinValue;
    }
    return usdCoinsValue;
}

async function getLiquidityTokenUSDValue(
    liquidityTokenAddress,
    liquidityTokenDecimals,
    blockNumber,
) {
    const provider = new ethers.providers.JsonRpcProvider(providerString);

    const totalSupplyAbi = ["function totalSupply() view returns (uint256)"];
    const liquidityTokenContract = new ethers.Contract(
        liquidityTokenAddress,
        totalSupplyAbi,
        provider,
    );

    const liquidityTokenSupply = await liquidityTokenContract
        .totalSupply({ blockTag: blockNumber })
        .then((result) =>
            Number(result.div("1" + "0".repeat(liquidityTokenDecimals)))
        );

    const liquidityTokenValue = await sbcore.feeds.getTokenPrice(
        liquidityTokenAddress,
    );
    return liquidityTokenSupply * liquidityTokenValue;
}