Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/adaptors/monolith-market/factoryAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = [{"type":"constructor","inputs":[{"name":"_operator","type":"address","internalType":"address"},{"name":"_minDebtFloor","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"MAX_FEE_BPS","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"acceptOperator","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"customFeeBps","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"deploy","inputs":[{"name":"params","type":"tuple","internalType":"struct Factory.DeployParams","components":[{"name":"name","type":"string","internalType":"string"},{"name":"symbol","type":"string","internalType":"string"},{"name":"collateral","type":"address","internalType":"address"},{"name":"psmAsset","type":"address","internalType":"address"},{"name":"psmVault","type":"address","internalType":"address"},{"name":"feed","type":"address","internalType":"address"},{"name":"collateralFactor","type":"uint256","internalType":"uint256"},{"name":"minDebt","type":"uint256","internalType":"uint256"},{"name":"timeUntilImmutability","type":"uint256","internalType":"uint256"},{"name":"operator","type":"address","internalType":"address"},{"name":"manager","type":"address","internalType":"address"},{"name":"halfLife","type":"uint64","internalType":"uint64"},{"name":"targetFreeDebtRatioStartBps","type":"uint16","internalType":"uint16"},{"name":"targetFreeDebtRatioEndBps","type":"uint16","internalType":"uint16"},{"name":"redeemFeeBps","type":"uint16","internalType":"uint16"},{"name":"stalenessThreshold","type":"uint32","internalType":"uint32"},{"name":"maxBorrowDeltaBps","type":"uint16","internalType":"uint16"},{"name":"psmVaultMinTotalSupply","type":"uint128","internalType":"uint128"}]}],"outputs":[{"name":"lender","type":"address","internalType":"address"},{"name":"coin","type":"address","internalType":"address"},{"name":"vault","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"deployments","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"deploymentsLength","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"feeBps","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"feeRecipient","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getFeeOf","inputs":[{"name":"_lender","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"interestModel","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"isDeployed","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"minDebtFloor","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"operator","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"pendingOperator","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"pullReserves","inputs":[{"name":"_deployment","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setCustomFeeBps","inputs":[{"name":"_address","type":"address","internalType":"address"},{"name":"_feeBps","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setFeeBps","inputs":[{"name":"_feeBps","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setFeeRecipient","inputs":[{"name":"_feeRecipient","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setPendingOperator","inputs":[{"name":"_pendingOperator","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"CustomFeeBpsSet","inputs":[{"name":"lender","type":"address","indexed":true,"internalType":"address"},{"name":"feeBps","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Deployed","inputs":[{"name":"lender","type":"address","indexed":true,"internalType":"address"},{"name":"coin","type":"address","indexed":true,"internalType":"address"},{"name":"vault","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"FeeBpsUpdated","inputs":[{"name":"feeBps","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"FeeRecipientUpdated","inputs":[{"name":"feeRecipient","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"OperatorUpdated","inputs":[{"name":"operator","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"PendingOperatorUpdated","inputs":[{"name":"pendingOperator","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"ReservesPulled","inputs":[{"name":"lender","type":"address","indexed":true,"internalType":"address"},{"name":"recipient","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}];
168 changes: 168 additions & 0 deletions src/adaptors/monolith-market/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
const sdk = require('@defillama/sdk');
const superagent = require('superagent');
const ethers = require('ethers');
const { getUniqueAddresses } = require('@defillama/sdk/build/generalUtil');
const utils = require('../utils');
const lensAbi = require('./lensAbi');
const lenderAbi = require('./lenderAbi');
const factoryAbi = require('./factoryAbi');
const vaultAbi = require('./vaultAbi');
const path = require('path');

const PROJECT = 'monolith-market';

const FACTORIES = {
ethereum: { chainId: 1, blocksPerYear: 2609750, factory: '0x6D961c9DCF1AD73566822BA4B087892e3839B849', lens: '0x0f3a7cd1828698D2B6daEf081d5c319c0734fA1c', fromBlock: 24949282 },
}

const simpleCalls = (arr, params) => {
return arr.map(a => ({ target: a, params }))
}

async function getChainPools(chain) {
const latestBlock = await sdk.api.util.getLatestBlock(chain);
sdk.api.util.getLogs
const toBlock = latestBlock.number;

const { factory, chainId, lens, blocksPerYear, fromBlock } = FACTORIES[chain];

const logsRawData = await sdk.api.util.getLogs({
Copy link
Copy Markdown
Contributor

@0xkr3p 0xkr3p May 18, 2026

Choose a reason for hiding this comment

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

we should use sdk.getEventLogs its the more common pattern across the codebase, something like this:

const logs = await sdk.getEventLogs({
    target: factory,
    fromBlock,
    toBlock,
    chain,
    eventAbi: 'event Deployed(address indexed lender, address indexed coin,
  address indexed vault)',
  });
  const lenders = logs.map((l) => l.args.lender);
  const coins   = logs.map((l) => l.args.coin);
  const vaults  = logs.map((l) => l.args.vault);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we should use sdk.getEventLogs its the more common pattern across the codebase, something like this:

const logs = await sdk.getEventLogs({
    target: factory,
    fromBlock,
    toBlock,
    chain,
    eventAbi: 'event Deployed(address indexed lender, address indexed coin,
  address indexed vault)',
  });
  const lenders = logs.map((l) => l.args.lender);
  const coins   = logs.map((l) => l.args.coin);
  const vaults  = logs.map((l) => l.args.vault);

Done, function undefined locally somehow but works with the pipeline here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@0xkr3p anything else?

target: factory, fromBlock, toBlock, chain: chain, keys: ['topics'], topics: ['0xc95935a66d15e0da5e412aca0ad27ae891d20b2fb91cf3994b6a3bf2b8178082']
});
// clean raw data from sdk
const logs = logsRawData.output.map(d => d.slice(-3).map(a => a.replace('0x000000000000000000000000', '0x')));

const lenders = logs.map(l => l[0]);
const coins = logs.map(l => l[1]);
const vaults = logs.map(l => l[2]);

const [
rates,
vaultSymbols,
coinSymbols,
vaultAssets,
totalPaidDebts,
totalFreeDebts,
collateralFactors,
collaterals,
collateralsPriceData,
] = (await Promise.all([
sdk.api.abi.multiCall({
chain, abi: lensAbi.find(a => a.name === 'getRates'), calls: lenders.map(lender => {
return {
target: lens,
params: [lender],
}
})
}),
sdk.api.abi.multiCall({ chain, abi: 'erc20:symbol', calls: simpleCalls(vaults) }),
sdk.api.abi.multiCall({ chain, abi: 'erc20:symbol', calls: simpleCalls(coins) }),
sdk.api.abi.multiCall({ chain, abi: vaultAbi.find(a => a.name === 'totalAssets'), calls: simpleCalls(vaults) }),
sdk.api.abi.multiCall({ chain, abi: lenderAbi.find(a => a.name === 'totalPaidDebt'), calls: simpleCalls(lenders) }),
sdk.api.abi.multiCall({ chain, abi: lenderAbi.find(a => a.name === 'totalFreeDebt'), calls: simpleCalls(lenders) }),
sdk.api.abi.multiCall({ chain, abi: lenderAbi.find(a => a.name === 'collateralFactor'), calls: simpleCalls(lenders) }),
sdk.api.abi.multiCall({ chain, abi: lenderAbi.find(a => a.name === 'collateral'), calls: simpleCalls(lenders) }),
sdk.api.abi.multiCall({ chain, abi: lenderAbi.find(a => a.name === 'getCollateralPrice'), calls: simpleCalls(lenders) }),
])).map(r => r.output.map(o => o.output));

const [
collateralSymbols,
collateralDecimals,
collateralDeposits,
pricesData,
] = (await Promise.all([
sdk.api.abi.multiCall({ chain, abi: 'erc20:symbol', calls: simpleCalls(collaterals) }),
sdk.api.abi.multiCall({ chain, abi: 'erc20:decimals', calls: simpleCalls(collaterals) }),
sdk.api.abi.multiCall({
chain, abi: 'erc20:balanceOf', calls: collaterals.map((col,i) => {
return {
target: col,
params: [lenders[i]],
}
})
}),
utils.getPrices(coins.concat(collaterals), chain),
])).map(r => r.output ? r.output.map(o => o.output) : r);

const { pricesByAddress } = pricesData;

// CDP markets, where coins are minted against collaterals
const cdpMarkets = lenders.map((m, marketIndex) => {
const collateral = collaterals[marketIndex];
const collateralSymbol = collateralSymbols[marketIndex];
const collateralDecimal = collateralDecimals[marketIndex];
const mintedCoin = coinSymbols[marketIndex];

const coin = coins[marketIndex];
const coinPriceUsd = pricesByAddress[coin.toLowerCase()] || 0;

const { price: oraclePrice } = collateralsPriceData[marketIndex];
const oraclePriceUsd = (Number(oraclePrice) / (10 ** (36 - collateralDecimal))) || 0;
// use defillama if available otherwise fallback to oracle price
const collateralPriceUsd = pricesByAddress[collateral.toLowerCase()] || oraclePriceUsd;
const totalSupplyUsd = collateralPriceUsd * Number(collateralDeposits[marketIndex]) / (10 ** collateralDecimal)
const totalBorrowUsd = coinPriceUsd * (Number(totalPaidDebts[marketIndex]) / 1e18 + Number(totalFreeDebts[marketIndex]) / 1e18);
const borrowApr = Math.min(Number(rates[marketIndex][0]) / 1e16, 999_999_999_999);
const borrowApy = borrowApr < 999_999_999_999 ? utils.aprToApy(borrowApr, blocksPerYear) : 999_999_999_999;

return {
pool: `monolith-market-lending-${m}`,
chain: 'Ethereum',
project: PROJECT,
symbol: collateralSymbol,
mintedCoin,
apyBase: 0,
// cdp => tvlUsd = totalSupplyUsd
tvlUsd: totalSupplyUsd,
underlyingTokens: [collateral],
url: 'https://app.monolith.market/1/coin/' + marketIndex,
totalSupplyUsd,
totalBorrowUsd,
apyBaseBorrow: borrowApy,
borrowable: true,
ltv: Number(collateralFactors[marketIndex]) / 1e4,
};
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// savings vaults (ERC4626), a vault's asset is the coin minted by the cdp markets
const savingsVaults = lenders.map((m, marketIndex) => {
const underlying = coins[marketIndex];
const vaultSymbol = vaultSymbols[marketIndex];
const coinPriceUsd = pricesByAddress[underlying.toLowerCase()] || 0;
const totalSupplyUsd = coinPriceUsd * Number(vaultAssets[marketIndex]) / 1e18
const stakingApr = Number(rates[marketIndex][1]) / 1e16;
const apy = utils.aprToApy(stakingApr, blocksPerYear);

return {
pool: `monolith-market-savings-${vaults[marketIndex]}`,
chain: 'Ethereum',
project: PROJECT,
symbol: vaultSymbol,
tvlUsd: totalSupplyUsd,
apyBase: apy,
underlyingTokens: [underlying],
url: 'https://app.monolith.market/1/coin/' + marketIndex,
totalSupplyUsd,
tvlUsd: totalSupplyUsd,
borrowable: false,
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

return cdpMarkets.concat(savingsVaults).filter((p) => utils.keepFinite(p));
}

async function apy() {
const factoriesChains = Object.keys(FACTORIES);

const chainPools = await Promise.all(
factoriesChains.map(factoryChain => getChainPools(factoryChain))
)

return chainPools.flat();
}

module.exports = {
timetravel: false,
apy,
url: 'https://app.monolith.market',
};
Loading
Loading