Skip to content

Commit

Permalink
Merge pull request #114 from morpho-org/feature/integ-822-automate-ne…
Browse files Browse the repository at this point in the history
…w-pendle-marketscollats

 feat(blue-sdk-ethers-liquidation): INTEG-822 - make pendle integration support any PT token
  • Loading branch information
Rubilmax authored Oct 8, 2024
2 parents a45efdb + 76ef36e commit 94ed734
Show file tree
Hide file tree
Showing 7 changed files with 8,313 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
apiSdk,
mainnetAddresses,
pendle,
pendleTokens,
swap,
} from "@morpho-org/blue-sdk-ethers-liquidation";
import { Time } from "@morpho-org/morpho-ts";
Expand Down Expand Up @@ -81,6 +80,8 @@ export const check = async (

const ethPriceUsd = safeParseNumber(wethPriceUsd, 18);

const pendleTokens = await pendle.getPendleTokens(chainId);

return Promise.all(
(positions ?? []).map(async (position) => {
if (position.market.collateralAsset == null) return;
Expand Down Expand Up @@ -169,73 +170,12 @@ export const check = async (
let dstAmount = 0n;
// Handle Pendle Tokens
// To retrieve the tokens, we need to call the Pendle API to get the swap calldata
if (pendleTokens[chainId].has(market.config.collateralToken)) {
const pendleMarketData =
pendle.pendleMarkets[chainId][
market.config.collateralToken
];
const maturity = pendleMarketData?.maturity;
if (!maturity) {
throw Error("Pendle market not found");
}

srcAmount = seizedAssets;
srcToken = pendleMarketData.underlyingTokenAddress;
if (maturity < new Date()) {
// Pendle market is expired, we can directly redeem the collateral
// If called before YT's expiry, both PT & YT of equal amounts are needed and will be burned. Else, only PT is needed and will be burned.
const redeemCallData = await pendle.getPendleRedeemCallData(
chainId,
{
receiver: executorAddress,
slippage: 0.04,
yt: pendleMarketData.yieldTokenAddress,
amountIn: seizedAssets.toString(),
tokenOut: pendleMarketData.underlyingTokenAddress,
enableAggregator: true,
},
);

encoder
.erc20Approve(srcToken, redeemCallData.tx.to, MaxUint256)
.erc20Approve(
market.config.collateralToken,
redeemCallData.tx.to,
MaxUint256,
)
.pushCall(
redeemCallData.tx.to,
redeemCallData.tx.value ? redeemCallData.tx.value : 0n,
redeemCallData.tx.data,
);
} else {
// Pendle market is not expired, we need to swap the collateral token (PT) to the underlying token
const swapCallData = await pendle.getPendleSwapCallData(
chainId,
pendleMarketData.address,
{
receiver: executorAddress,
slippage: 0.04,
tokenIn: market.config.collateralToken,
tokenOut: pendleMarketData.underlyingTokenAddress,
amountIn: seizedAssets.toString(),
},
);
encoder
.erc20Approve(srcToken, swapCallData.tx.to, MaxUint256)
.erc20Approve(
market.config.collateralToken,
swapCallData.tx.to,
MaxUint256,
)
.pushCall(
swapCallData.tx.to,
swapCallData.tx.value ? swapCallData.tx.value : 0n,
swapCallData.tx.data,
);
srcAmount = BigInt(swapCallData.data.amountOut);
}
}
({ srcAmount, srcToken } = await encoder.handlePendleTokens(
chainId,
market.config.collateralToken,
seizedAssets,
pendleTokens,
));

switch (true) {
// In case of Usual tokens, there aren't much liquidity outside of curve, so we use it instead of 1inch/paraswap
Expand Down
37 changes: 21 additions & 16 deletions packages/blue-sdk-ethers-liquidation/src/LiquidationEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ import { Address, ChainId } from "@morpho-org/blue-sdk";
import {
curvePools,
mainnetAddresses,
pendleTokens,
pendle,
} from "@morpho-org/blue-sdk-ethers-liquidation";
import { CurveStableSwapNG__factory } from "@morpho-org/blue-sdk-ethers-liquidation/src/contracts/curve";
import {
getPendleRedeemCallData,
getPendleSwapCallData,
pendleMarkets,
} from "@morpho-org/blue-sdk-ethers-liquidation/src/tokens/pendle";
import {
USD0_USD0PP_USD0_INDEX,
USD0_USD0PP_USDPP_INDEX,
Expand All @@ -32,33 +27,43 @@ export class LiquidationEncoder extends ExecutorEncoder {
chainId: ChainId,
collatToken: string,
seizedAssets: bigint,
pendleTokens: pendle.PendleTokenListResponse,
): Promise<{ srcAmount: bigint; srcToken: string }> {
if (!pendleTokens[chainId].has(collatToken)) {
if (!pendle.isPendlePTToken(collatToken, chainId, pendleTokens)) {
return {
srcAmount: seizedAssets,
srcToken: collatToken,
};
}

const pendleMarketData = pendleMarkets[chainId][collatToken];
const maturity = pendleMarketData?.maturity;
const pendleMarketResponse = await pendle.getPendleMarketForPTToken(
chainId,
collatToken,
);
if (pendleMarketResponse.total !== 1) {
throw Error("Invalid Pendle market result");
}
const pendleMarketData = pendleMarketResponse.results[0]!;
const maturity = pendleMarketData.pt.expiry!;
if (!maturity) {
throw Error("Pendle market not found");
}

let srcAmount = seizedAssets;
let srcToken = pendleMarketData.underlyingTokenAddress;
let srcToken = pendleMarketData.underlyingAsset.address;

if (maturity < new Date()) {
if (new Date(maturity) < new Date()) {
// Pendle market is expired, we can directly redeem the collateral
const redeemCallData = await getPendleRedeemCallData(chainId, {
// If called before YT's expiry, both PT & YT of equal amounts are needed and will be burned. Else, only PT is needed and will be burned.
const redeemCallData = await pendle.getPendleRedeemCallData(chainId, {
receiver: this.address,
slippage: 0.04,
yt: pendleMarketData.yieldTokenAddress,
yt: pendleMarketData.yt.address,
amountIn: seizedAssets.toString(),
tokenOut: pendleMarketData.underlyingTokenAddress,
tokenOut: pendleMarketData.underlyingAsset.address,
enableAggregator: true,
});

this.erc20Approve(srcToken, redeemCallData.tx.to, MaxUint256)
.erc20Approve(collatToken, redeemCallData.tx.to, MaxUint256)
.pushCall(
Expand All @@ -68,14 +73,14 @@ export class LiquidationEncoder extends ExecutorEncoder {
);
} else {
// Pendle market is not expired, we need to swap the collateral token (PT) to the underlying token
const swapCallData = await getPendleSwapCallData(
const swapCallData = await pendle.getPendleSwapCallData(
chainId,
pendleMarketData.address,
{
receiver: this.address,
slippage: 0.04,
tokenIn: collatToken,
tokenOut: pendleMarketData.underlyingTokenAddress,
tokenOut: pendleMarketData.underlyingAsset.address,
amountIn: seizedAssets.toString(),
},
);
Expand Down
22 changes: 0 additions & 22 deletions packages/blue-sdk-ethers-liquidation/src/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,10 @@ declare module "@morpho-org/blue-sdk" {
export const mainnetAddresses = addresses[ChainId.EthMainnet] as ChainAddresses;
export const baseAddresses = addresses[ChainId.BaseMainnet] as ChainAddresses;

mainnetAddresses["PT-USDe-25JUL2024"] =
"0xa0021EF8970104c2d008F38D92f115ad56a9B8e1";
mainnetAddresses["PT-ezETH-26DEC2024"] =
"0xf7906F274c174A52d444175729E3fa98f9bde285";
mainnetAddresses["PT-sUSDE-24OCT2024"] =
"0xAE5099C39f023C91d3dd55244CAFB36225B0850E";
mainnetAddresses["PT-weETH-26DEC2024"] =
"0x6ee2b5E19ECBa773a352E5B21415Dc419A700d1d";
mainnetAddresses["PT-weETH-27JUN2024"] =
"0xc69Ad9baB1dEE23F4605a82b3354F8E40d1E5966";

mainnetAddresses["usd0"] = "0x73A15FeD60Bf67631dC6cd7Bc5B6e8da8190aCF5";
mainnetAddresses["usd0++"] = "0x35D8949372D46B7a3D5A56006AE77B215fc69bC0";
mainnetAddresses["usd0usd0++"] = "0x1d08E7adC263CfC70b1BaBe6dC5Bb339c16Eec52";

export const pendleTokens: Record<ChainId, Set<Address>> = {
[ChainId.EthMainnet]: new Set([
mainnetAddresses["PT-USDe-25JUL2024"],
mainnetAddresses["PT-ezETH-26DEC2024"],
mainnetAddresses["PT-sUSDE-24OCT2024"],
mainnetAddresses["PT-weETH-26DEC2024"],
mainnetAddresses["PT-weETH-27JUN2024"],
]),
[ChainId.BaseMainnet]: new Set(),
};

export const curvePools = {
"usd0usd0++": "0x1d08e7adc263cfc70b1babe6dc5bb339c16eec52",
usd0usdc: "0x14100f81e33c33ecc7cdac70181fb45b6e78569f",
Expand Down
Loading

0 comments on commit 94ed734

Please sign in to comment.