Leverage

Leveraging in Curvance allows users to amplify their position by borrowing assets against their collateral and reinvesting them. This creates a more capital-efficient position with greater exposure to underlying assets. Curvance's leveraging system is built on its powerful Position Management framework, which simplifies complex DeFi operations into single, atomic transactions. This eliminates the manual loop of borrowing, swapping, and depositing that would otherwise be required to build leveraged positions.

For those that want to dive deep into the inner workings of our leverage system, check out our Position Management page here: Position Management

Core Structures for Leveraging

To understand leveraging in Curvance, you need to be familiar with two key data structures that control the leverage and deleverage operations:

LeverageAction

struct LeverageAction {
    IBorrowableCToken borrowToken;   // The cToken you want to borrow from.
    uint256 borrowAssets;            // Amount of underlying tokens to borrow.
    ICToken cToken;                  // cToken that will receive the swapped assets.
    SwapperLib.Swap swapData;        // Instructions for swapping borrowed tokens.
    bytes auxData;                   // Optional protocol-specific data.
}

DeleverageAction

struct DeleverageAction {
    ICToken cToken;                  // The cToken you're unwinding
    uint256 collateralAmount;        // Amount of cTokens to redeem
    IBorrowableCToken borrowToken;   // The cToken debt to repay
    SwapperLib.Swap[] swapData;      // Array of swaps to execute
    uint256 repayAmount;             // Amount of debt to repay
    bytes auxData;                   // Optional protocol-specific data
}

SwapperLib.Swap

Both structures use the SwapperLib.Swap structure to handle token swaps:

struct Swap {
    address target;        // Swap router address
    address inputToken;    // Token being swapped from
    address outputToken;   // Token being swapped to
    uint256 inputAmount;   // Amount to swap
    bytes call;            // Encoded swap call data
}

Important Note: LeverageStruct takes a single Swap while DeleverageStruct takes an array of Swap operations, allowing for multi-hop swaps when deleveraging.

Pre, Post, and Auxiliary Swap Operations

Pre-Swap when Leveraging

When leveraging, a swap is executed to obtain the collateral asset using the borrowed asset. You must provide the appropriate calldata hex string in the Swap.call struct member. Additionally, ensure the swap's receiver is set to the corresponding PositionManager contract.

Post-Swap when Deleveraging

During deleveraging, a swap is performed to exchange the collateral asset for the borrowed asset. You need to supply the correct calldata hex string in the Swap.call struct member. Also, make sure the swap's receiver is configured as the relevant PositionManager contract.

Auxiliary Swap for Complex Assets

When leveraging or deleveraging exotic assets, you may need to include auxiliary data to initiate the position. For instance, to enter a Pendle PT position, you must provide swap data to exchange the borrowed asset for Pendle PT via Pendle’s internal exchange.

Below is a code snippet demonstrating how to prepare this data:

const PendleData = {
    approx: {
        guessMin: ethers.utils.parseUnits("9.9", 18),
        guessMax: ethers.utils.parseUnits("10.0", 18),
        guessOffchain: ethers.utils.parseUnits("1.0", 18),
        maxIteration: 30,
        eps: ethers.utils.parseUnits("0.001", 18)
    },
    input: {
        tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC address
        netTokenIn: ethers.utils.parseUnits(estimatedUniswapOutputAmount.toString(), 18),
        tokenMintSy: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", // STETH address
        pendleSwap: "0x1e8b6Ac39f8A33f46a6Eb2D1aCD1047B99180AD1", // PENDLE_SWAP address
        swapData: {
            swapType: 1, // KYBERSWAP enum value
            extRouter: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5",
            extCalldata: "0xe21fd0e9...", // The calldata hex string
            needScale: false
        }
    }
};

// Create the leverage data object
const leverageAction = {
    borrowToken: cDAIAddress,
    borrowAmount: ethers.utils.parseUnits(amountForLeverage.toString(), 18),
    positionToken: cPendlePTAddress,
    auxData: null,
    swapData: swapData
};

// Encode the data
leverageAction .auxData = ethers.utils.defaultAbiCoder.encode(
    [
        "tuple(tuple(uint256 guessMin, uint256 guessMax, uint256 guessOffchain, uint256 maxIteration, uint256 eps) approx, tuple(address tokenIn, uint256 netTokenIn, address tokenMintSy, address pendleSwap, tuple(uint8 swapType, address extRouter, bytes extCalldata, bool needScale) swapData) input)",
        "address",
        "uint256"
    ],
    [
        [
            [
                PendleData.approx.guessMin,
                PendleData.approx.guessMax,
                PendleData.approx.guessOffchain,
                PendleData.approx.maxIteration,
                PendleData.approx.eps
            ],
            [
                PendleData.input.tokenIn,
                PendleData.input.netTokenIn,
                PendleData.input.tokenMintSy,
                PendleData.input.pendleSwap,
                [
                    PendleData.input.swapData.swapType,
                    PendleData.input.swapData.extRouter,
                    PendleData.input.swapData.extCalldata,
                    PendleData.input.swapData.needScale
                ]
            ]
        ],
        lpStethAddress,
        1
    ]
);

Setting Up Your Environment

Before interacting with Curvance, set up your development environment:

const { ethers } = require('ethers');

// Initialize provider and signer
const provider = new ethers.providers.JsonRpcProvider('YOUR_RPC_URL');
const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);

// Contract addresses
const MARKET_MANAGER = '0x...';
const POSITION_MANAGEMENT = '0x...';
const C_TOKEN_COLLATERAL = '0x...'; // Your collateral token
const C_TOKEN_DEBT = '0x...'; // Your borrow token

// Initialize contracts
const marketManager = new ethers.Contract(
  MARKET_MANAGER,
  positionManagerAbi,
  provider
);

const positionManagement = new ethers.Contract(
  POSITION_MANAGEMENT,
  [
   'function depositAndLeverage(uint256, tuple(address,uint256,address,tuple(address,address,address,uint256,bytes),bytes), uint256)',
    'function leverage(tuple(address,uint256,address,tuple(address,address,address,uint256,bytes),bytes), uint256)',
    'function deleverage(tuple(address,uint256,address,tuple[](address,address,address,uint256,bytes),uint256,bytes), uint256)'
  ],
  signer
);

Monitoring Position Health

Maintaining a healthy leverage ratio is crucial to avoid liquidation. Regularly check your position's health by using getPositionHealth() in the ProtocolReader contract. The function returns the healthiness of an account's position.

Use zero addresses and amounts to simply check an existing position.

If the value is greater than WAD (1e18), the position is healthy.

If the value is less than WAD (1e18), the position is unhealthy/liquidatable.

// Create ProtocolReader contract instance
  const protocolReader = new ethers.Contract(
    protocolReaderAddress,
    protocolReaderAbi,
    provider
  );

  async function checkPositionHealth() {
    try {
      const [positionHealth, errorCodeHit] = await
  protocolReader.getPositionHealth(
        "MARKET_MANAGER_ADDRESS",        // IMarketManager mm
        userAddress,                     // address account
        ethers.constants.AddressZero,    // address cToken
        ethers.constants.AddressZero,    // address borrowableCToken
        false,                           // bool isDeposit
        0,                               // uint256 collateralAssets
        false,                           // bool isRepayment
        0,                               // uint256 debtAssets
        0                                // uint256 bufferTime
      );

      console.log("Position Health:", positionHealth.toString());
      console.log("Error Code Hit:", errorCodeHit);

      return { positionHealth, errorCodeHit };
    } catch (error) {
      console.error("Error calling getPositionHealth:", error);
      throw error;
    }
  }

Last updated