Create a protected investing opportunity

Learn how to create a protected investment opportunity for another DeFi protocol.

After obtaining protection via a Cozy protection market, many users will want to deploy the newly borrowed funds to earn yield. That’s where Cozy Protected Investing comes in.

Cozy Protected Investing enables users to borrow, invest and track returns in one step, in one place. This guide details how you can create a Protected Investment opportunity for users on cozy.finance or on your DeFi app.

Deploy a protection market

Each Cozy Protected Investment opportunity requires a corresponding Protection Market. Please refer to the Create a protection market guide for details on developing and deploying trigger and protection market contracts.

Adding protected investment opportunities to the Cozy app

Deploy an invest/divest contract

Protection markets cover the borrow-side of an investment opportunity; however, you’ll also need to provide instructions for how the borrowed funds should be invested. For this, you’ll deploy an additional contract that is responsible for investing, divesting and managing any other assets related to the investment position (e.g. reward tokens or airdrops).

Please refer to Create an invest/divest contract guide for details on developing and deploying an invest/divest contract.

Data fetching

The Cozy app requires a few pieces of data to support an investment opportunity. The Cozy subgraph (covered in detail below) supplies much of this data, but the subgraph is limited to Cozy protocol-specific data (i.e. protection markets and positions in those markets). Data related to an investment opportunity on a separate DeFi protocol requires a separate data source.

That’s why it’s necessary to create an endpoint — on a per opportunity basis — which is responsible for investment performance data as well as data that facilitates invest/divest contract interaction. This endpoint — referred to as the opportunity's dataURI — uniquely identifies that opportunity.

Assuming a dataURI ending in /new-opty, this data endpoint must handle the following query parameters:

No query params

/new-opty - returns base data about the investment opportunity, like the rate of return, the remaining capacity to be deposited into the opportunity and whether the opportunity is disabled. Sample response:

// Typical
{
  "isDisabled": false,
  "investCapacity": "991422.323294438226440458",
  "investRate": 0.07662908620704814
}

// Two Step Invest Flow
{
  /* All fields from typical return plus... */
  "investFlow": "twoStepInvest",
  "twoStepDeployTime": "Friday at 10am PST"
}

Ethereum account

/new-opty?account=<ethereumAccount> - returns all the data of the base new-opty endpoint, but adds balance information for the passed in Ethereum account. Sample response:

// Typical Response
{
  /* All fields from base response plus... */
  "balances": {
    "balanceUnderlying": "0.00089522599655054241457996374997540428",
    "balanceUsd": 2.526979298059575,
    "lastUpdatedBlockNumber": 7368120,
    "receiptTokenBalances": [
      {
        "balance": "0.001678155500972884",
        "balanceUnderlying": "0.00089309310068732528257996374997540428",
        "balanceUsd": 2.520958713635045,
        "token": {
          "chainId": 42161,
          "address": "0x8e0b8c8bb9db49a46697f3a5bb8a308e744821d2",
          "decimals": 18,
          "name": "Curve.fi USD-BTC-ETH",
          "symbol": "crv3crypto"
        }
      }
    ],
    "rewardTokenBalances": [
      {
        "balance": "0.002417905391377535",
        "balanceUnderlying": "0.000002132895863217132",
        "balanceUsd": 0.006020584424530063,
        "token": {
          "chainId": 42161,
          "address": "0x11cdb42b0eb46d95f990bedd4695a6e3fa034978",
          "decimals": 18,
          "name": "Curve DAO Token",
          "symbol": "CRV"
        }
      }
    ],
    "totalWithdrawableAmount": "1678155500972884"
  }
}

// Two Step Invest Flow
{
  /* All fields from the typical response plus...  */
  "pendingDepositsUnderlying": "100000000000000000000",
  "scheduledWithdrawalReady": true,
  "scheduledWithdrawalUnderlying": "100000000000000000000",
}

When describing balance fields in this guide, we will always refer to their units as either raw or human-readable. Raw fields are the format used by smart contracts that have no decimal places. If you divide a raw value by Math.pow(10, decimals), you get the human-readable form.

Invest params

/new-opty?invest=[<marketContractAddress>, <investAmountInUnderlying>] - returns the parameters which will be passed into the invest method of the invest/divest contract deployed for this investment opportunity. Sample response:

// For invest function signature:
// function invest(address _ethMarket, uint256 _borrowAmount, uint256 _curveMinAmountOut) external payable
[
  '0x7d4377114fd3c61c59d72de48102bf6acd3882b1', // Cozy market address
  '697746377352637', // borrow armount in underlying
  '442383085561', // min amount out for call to Curve pool
];

Divest params

/new-opty?invest=[<marketContractAddress>, <divestAmountInUnderlying>, <eoaAddress>] - returns the parameters which will be passed into the divest method of the invest/divest contract deployed for this investment opportunity. Sample response

// For divest function signature:
// function divest(address _ethMarket, address _recipient, uint256 _redeemAmount, uint256 _curveMinAmountOut) external payable
[
  '0x7d4377114fd3c61c59d72de48102bf6acd3882b1', // market address
  '0x123abc123abc123abc123abc123abc123abc123a', // EOA address
  '1678155500972884', // redeem amount in LP tokens
  '855549635374828', // min amount out for call to Curve pool
];

Surfacing

Cozy displays investment opportunities based on a set of market-lists maintained in the cozy-invest-lists repo. Inspired by Uniswap’s token-lists standard, all Cozy market-lists conform to a simple, JSON schema.

Once all the smart contracts related to your investment opportunity have been deployed, you can create a new protection market entry in the community protection-market-list in Cozy’s cozy-invest-lists repo and submit a PR. Make sure to validate your entry to the schema using the validation framework found in the protection-market-list schema.

Amongst other things, your list entry will include:

  • Invest/divest contract details: the contract addresses and signatures of the invest/divest contracts you deployed

  • Investment opportunity strategy: a description of the steps taken when borrowed funds are deployed

Sample protection-market-list entry:

{
  "chainId": 42161,
  "address": "0x7d4377114fd3c61c59d72de48102bf6acd3882b1",
  "symbol": "Cozy-ETH-1",
  "name": "Cozy-ETH-1-Curve 3Crypto Trigger",
  "decimals": 8,
  "tags": [],
  "trigger": "0x73ea553cd95211956420329c2c7d23851c577725",
  "underlying": {
    "chainId": 1,
    "decimals": 18,
    "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
    "name": "Ether",
    "symbol": "ETH"
  },
  "investmentOpportunities": [
    {
      "invest": {
        "address": "0x551F34Ba6cd876CAe2265DA1f3e030e82f703A13",
        "signature": "function invest(address _ethMarket, uint256 _borrowAmount, uint256 _curveMinAmountOut) external payable"
      },
      "divest": {
        "address": "0x551F34Ba6cd876CAe2265DA1f3e030e82f703A13",
        "signature": "function divest(address _ethMarket, address _recipient, uint256 _redeemAmount, uint256 _curveMinAmountOut) external payable"
      },
      "dataURI": "https://cozy-api-k1ojdh8ct-cozy-finance.vercel.app/api/opportunities/arb-eth-tricrypto",
      "logoURI": "https://app.cozy.finance/images/tokens/crvtricrypto.png",
      "name": "crvTricrypto",
      "tokens": {
        "input": [
          {
            "chainId": 1,
            "decimals": 18,
            "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
            "name": "Ether",
            "symbol": "ETH"
          }
        ],
        "receipt": [
          {
            "chainId": 42161,
            "address": "0x8e0b8c8bb9db49a46697f3a5bb8a308e744821d2",
            "decimals": 18,
            "name": "Curve.fi USD-BTC-ETH",
            "symbol": "crv3crypto"
          }
        ],
        "rewards": [
          {
            "chainId": 1,
            "address": "0xd533a949740bb3306d119cc777fa900ba034cd52",
            "decimals": 18,
            "name": "Curve DAO Token",
            "symbol": "CRV"
          }
        ]
      },
      "strategy": [
        {
          "action": "Borrow",
          "object": "ETH",
          "logoURI": "https://app.cozy.finance/images/tokens/eth.png",
          "platformId": 0,
          "token": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
        },
        {
          "action": "Deposit to",
          "object": "crvTricrypto",
          "logoURI": "https://app.cozy.finance/images/tokens/crvtricrypto.png",
          "platformId": 3,
          "token": "0x8e0b8c8bb9db49a46697f3a5bb8a308e744821d2"
        }
      ]
    }
  ]
}

PR review

After submitting your new protection market list entry to the cozy-invest-lists repo for PR, we’ll review your invest/divest contracts and test your dataURI endpoint to ensure everything is working properly. Please ensure your invest/divest contracts are verified on Etherscan, so we can review the code.

Adding protected investment opportunities to your DeFi app

Hosting a protected investment opportunity on your site is easy and allows new users to engage with your project while staying protected.

Depositing collateral

To enter into a protected investing position, a user must first have supplied collateral to borrow against. This collateral must be supplied to a Cozy money market, a process is detailed in this guide.

Each market has a collateralFactor which is applied to funds deposited into a money market that determines how much funds can be borrowed by that account (i.e. If $100 was supplied to a money market with a 0.75 collateral factor, that account can borrow $75).

If a user’s borrows exceed the total value of their collateral, they will be subject to liquidation.

Entering a protected investing position

In order to create a protected investing position, the flow is as simple as:

  1. Borrow from the Cozy protection market you just deployed. See this guide on how to Buy protection programmatically. Note: a user must have already deposited collateral for the borrow to succeed!

  2. Follow the same logic your app usually does for entering into a new position, for the amount that was borrowed.

Tracking an investing position

To track the performance of the investing position, you’ll need data on both the borrow-side and invest-side of the position (from the Cozy market side and other DeFi protocol side, respectively).

The Cozy Subgraph supplies all the data for borrow-side. For example, the user’s collateral balance, how much borrowing power they have and their protected borrow balance can all be found in the Cozy Subgraph.

The subgraph automatically indexes all Cozy protection markets and indexes all positions which are opened on those markets. In the subgraph, these are represented by Market & AccountCToken entities.

Please see discussion of these two entities below, but for additional information consult the comments in the Schema panel of the subgraph’s page.

Markets

The Market entity holds a lot of critical information for presenting a Cozy protection market including:

  • Basic descriptive data: the name, symbol, underlying of the market and associated trigger contract address

  • Key numbers used by the protocol: borrow cap, exchange rate, borrow rate

AccountCTokens

The AccountCToken entity represents a position opened by a user on a protection market. It includes data on:

  • Current balances of the position: the cToken balance and the stored borrow balance

  • Historical tabulations for this user in the market: total underlying borrowed, repaid, supplied or redeemed

Key calculations

To calculate the borrow balance of a position in underlying:

const borrowBalanceUnderlying =
  position.storedBorrowBalance * market.borrowIndex /
  position.accountBorrowIndex;

To calculate the supply balance of a position in underlying:

const supplyBalanceUnderlying = position.cTokenBalance * market.exchangeRate;

Exiting a position

To close out a position, follow the opposite steps to opening it:

  1. Follow the normal steps for closing a position in your protocol.

  2. Repay the Cozy protection market using the funds just received from closing the position on your protocol. For details on repaying debt on Cozy, please consult the Repay Debt section of the Manage protection programmatically guide.

Last updated