Skip to content
Merged

axlp #10992

Show file tree
Hide file tree
Changes from 5 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
3 changes: 3 additions & 0 deletions coins/src/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,7 @@ export default {
goblin: require("./markets/goblin"),
ember: require("./yield/ember"),
suirewards: require("./markets/suirewards"),
axlp: require("./liquidStaking/axlp"),
morphoBlue: require("./moneyMarkets/morpho"),
lista: require("./moneyMarkets/morpho"),
};
66 changes: 66 additions & 0 deletions coins/src/adapters/liquidStaking/axlp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Write } from "../utils/dbInterfaces";
import { addToDBWritesList } from "../utils/database";
import { getApi } from "../utils/sdk";

const contracts: { [chain: string]: { [key: string]: { symbol: string, token: string, stakingContract: string, rate: number } } } = {
"arbitrum": {
"amlp": {
"symbol": "AMLP",
"token": "0x152f5E6142db867f905a68617dBb6408D7993a4b",
"stakingContract": "0x3a66b81be26f2d799C5A96A011e1E3FB2BA50999",
"rate": 1.0
},
"ahlp": {
"symbol": "AHLP",
"token": "0x5fd22dA8315992dbbD82d5AC1087803ff134C2c4",
"stakingContract": "0x1ba274EBbB07353657ed8C76A87ACf362E408D85",
"rate": 1.0
}
}
}

export async function axlp(timestamp: number = 0) {
const writes: Write[] = [];

await Promise.all(
Object.keys(contracts).map(async (chain) => {
const { amlp, ahlp } = contracts[chain];
const api = await getApi(chain, timestamp);
const amlpPrice = await api.call({
target: amlp.stakingContract,
abi: "uint256:price",
});

addToDBWritesList(
writes,
chain,
amlp.token,
amlpPrice / 10 ** 18,
18,
amlp.symbol,
timestamp,
"amlp",
amlp.rate
);

const ahlpPrice = await api.call({
target: ahlp.stakingContract,
abi: "uint256:price",
});

addToDBWritesList(
writes,
chain,
ahlp.token,
ahlpPrice / 10 ** 18,
18,
ahlp.symbol,
timestamp,
"ahlp",
ahlp.rate
);
})
);

return writes;
}
277 changes: 277 additions & 0 deletions coins/src/adapters/moneyMarkets/morpho.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
import { runInPromisePool } from "@defillama/sdk/build/generalUtil";
import { getApi } from "../utils/sdk";
import { request } from "graphql-request";
import { getCurrentUnixTimestamp } from "../../utils/date";
import { getBlock } from "@defillama/sdk/build/util/blocks";
import fetch from "node-fetch";
import { Write } from "../utils/dbInterfaces";
import { addToDBWritesList } from "../utils/database";
import { getTokenInfoMap } from "../utils/erc20";

type VaultDatas = {
[vault: string]: {
totalAssets: number;
positions: any[];
markets: string[];
};
};

const listaConfig: { [chain: string]: { vault: string; vaultInfo: string } } = {
bsc: {
vault: "0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C",
vaultInfo:
"https://api.lista.org/api/moolah/vault/list?page=1&pageSize=1000",
},
ethereum: {
vault: "0xf820fB4680712CD7263a0D3D024D5b5aEA82Fd70",
vaultInfo:
"https://api.lista.org/api/moolah/vault/list?page=1&pageSize=1000&chain=ethereum",
},
};

async function fetchMorphoVaultAddresses(chainId: string) {
let assets: { [vault: string]: string } = {};
let skip = 0;
let length = 1000;

while (length == 1000) {
const query = `
query {
vaults (first: ${length}, skip: ${skip}, orderBy: Address, where: {
chainId_in: [${chainId}]
}) {
items {
asset {
address
}
address
}
}}`;

const res: any = await request("https://api.morpho.org/graphql", query);
res.vaults.items.forEach((item: any) => {
assets[item.address.toLowerCase()] = item.asset.address.toLowerCase();
});
length = res.vaults.items.length;
skip += length;
}

return assets;
}

async function morpho(
timestamp: number = 0,
vaultAssets: { [vault: string]: string } = {},
api: any,
target: string
) {
const threeDaysAgo =
(timestamp == 0 ? getCurrentUnixTimestamp() : timestamp) - 3 * 24 * 60 * 60;
const threeDaysAgoBlock = await getBlock(api.chain, threeDaysAgo);

if (!api.chainId) throw new Error("Chain ID not found");
const allMarkets: string[] = [];
const currentVaultDatas: VaultDatas = {};
const previousVaultDatas: VaultDatas = {};

await runInPromisePool({
items: Object.keys(vaultAssets),
concurrency: 5,
processor: (vault: string) => fetchVaultPositions(vault, true),
});

await runInPromisePool({
items: Object.keys(vaultAssets),
concurrency: 5,
processor: (vault: string) => fetchVaultPositions(vault, false),
});

async function fetchVaultPositions(vault: string, isCurrent: boolean) {
Copy link
Member

Choose a reason for hiding this comment

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

should we have two flows, simpler one for vaults with less than 5M tvl or borrowed less than 80% and stricter check for the rest?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you have something in mind for how this could be simpler? Would this still be for marking down bad debt vaults?

const totalAssets = await api.call({
target: vault,
abi: "uint256:totalAssets",
});

const withdrawQueueLength = await api.call({
target: vault,
abi: "uint256:withdrawQueueLength",
});

const markets = await api.multiCall({
target: vault,
abi: "function withdrawQueue(uint256 index) view returns (bytes32)",
calls: Array.from({ length: withdrawQueueLength }, (_, i) => ({
params: i,
})),
});

// vaults position in market
const positions = await api.multiCall({
target,
abi: "function position(bytes32, address) view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)",
calls: markets.map((market: string) => ({
params: [market, vault],
})),
});

allMarkets.push(...markets);
isCurrent
? (currentVaultDatas[vault] = { totalAssets, positions, markets })
: (previousVaultDatas[vault] = { totalAssets, positions, markets });
}

const uniqueMarkets = [...new Set(allMarkets)];
const [currentMarketData, previousMarketData] = await Promise.all([
fetchMarketData(),
fetchMarketData(threeDaysAgoBlock.block),
]);

async function fetchMarketData(block?: number) {
const marketDataArray = await api.multiCall({
target,
abi: "function market(bytes32) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
calls: uniqueMarkets.map((market: string) => ({ params: market })),
block,
});
const marketData: {
[market: string]: {
totalSupplyAssets: number;
totalSupplyShares: number;
totalBorrowAssets: number;
};
} = {};
marketDataArray.forEach((m: any, i: number) => {
marketData[uniqueMarkets[i]] = m;
});

return marketData;
}

const currentTotalWithdrawables = aggregateWithdrawable(
currentVaultDatas,
currentMarketData
);
const previousTotalWithdrawables = aggregateWithdrawable(
previousVaultDatas,
previousMarketData
);

function aggregateWithdrawable(vaultDatas: VaultDatas, marketData: any) {
let totalWithdrawables: { [vault: string]: number } = {};
Object.keys(vaultDatas).map((vault: string) => {
totalWithdrawables[vault] = 0;
const { positions, markets } = vaultDatas[vault];

markets.map((market: string, i: number) => {
const { totalSupplyAssets, totalSupplyShares, totalBorrowAssets } =
marketData[market];
if (positions[i].supplyShares == 0) return;
const supplyAssets =
(positions[i].supplyShares * totalSupplyAssets) / totalSupplyShares;

const availableLiquidity = Math.max(
totalSupplyAssets - totalBorrowAssets,
0
);

const withdrawable = Math.min(supplyAssets, availableLiquidity);

totalWithdrawables[vault] += Number(withdrawable);
});
});

return totalWithdrawables;
}

const problemVaultList: string[] = [];
Object.keys(currentVaultDatas).map((vault: string) => {
const { totalAssets } = currentVaultDatas[vault];
if (totalAssets == 0) return;

const currentWithdrawable = currentTotalWithdrawables[vault];
const previousWithdrawable = previousTotalWithdrawables[vault];
if (currentWithdrawable / totalAssets > 0.01) return;

if (!previousVaultDatas[vault]) {
if (currentWithdrawable / totalAssets < 0.1)
console.log(
`${vault}: ${((currentWithdrawable / totalAssets) * 100).toFixed(2)}%`
);

problemVaultList.push(vault);
return;
}

const { totalAssets: previousTotalAssets } = previousVaultDatas[vault];
if (
previousWithdrawable &&
previousWithdrawable / previousTotalAssets > 0.95
)
return;

problemVaultList.push(vault);
console.log(
`${vault}: ${((currentWithdrawable / totalAssets) * 100).toFixed(2)}%`
);
});

const metadata = await getTokenInfoMap(api.chain, problemVaultList);

const writes: Write[] = [];
problemVaultList.map(async (vault: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

A small optimization detail here: use forEach instead of map because you're not returning anything. You're using side effects (on writes) rather than transforming the array directly, so map is unnecessary since it’s meant to return data from the array transformation, which isn’t the case here

const { symbol, decimals } = metadata[vault];
if (!symbol || !decimals) return;
addToDBWritesList(
writes,
api.chain,
vault,
0,
decimals,
symbol,
timestamp,
"morpho",
1.01
Copy link
Member

Choose a reason for hiding this comment

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

this is to ensure that it never gets overwritten?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah

);
});

return writes;
}

async function getListaVaults(chain: string) {
const {
data: { list: vaults },
} = await fetch(listaConfig[chain].vaultInfo).then((r) => r.json());
const listaVaults: { [vault: string]: string } = {};
vaults.map(
(vault: any) =>
(listaVaults[vault.address.toLowerCase()] = vault.asset.toLowerCase())
);
return listaVaults;
}

export async function lista(timestamp: number = 0) {
return await Promise.all(
Object.keys(listaConfig).map(async (chain) => {
const api = await getApi(chain, timestamp);
const vaults = await getListaVaults(chain);
return await morpho(timestamp, vaults, api, listaConfig[chain].vault);
})
);
}

export async function morphoBlue(timestamp: number = 0) {
const chains = ["ethereum", "base", "hyperliquid", "katana", "arbitrum", "wc", "unichain", "polygon", "optimism", "plume_mainnet", "sei", "lisk", "etlk"];
return await Promise.all(
chains.map(async (chain) => {
const api = await getApi(chain, timestamp);
if (!api.chainId) return;
const vaults = await fetchMorphoVaultAddresses(api.chainId.toString());
return await morpho(
timestamp,
vaults,
api,
"0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"
);
})
);
}
Loading