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"
}

Typical Fields

Description

isDisabled

Boolean. Whether the investment opportunity is still accepting funds. All invest transactions will fail if this is true.

investingCapacity

String. The amount of funds in human-readable units which the investment opportunity can accept. The invest transaction will fail in an attempt to deposit funds beyond this amount. Null if there is no maximum capacity for the opportunity.

investRate

Number. The percentage return in decimal form associated with funds invested into this opportunity. The investRate minus the borrowRate equals the netReturnRate — or the overall APY — associated with this opportunity.

Two Step Invest Fields

Description

investFlow

String. Sometimes, an investment opportunity will have a non-standard flow. The Cozy app currently supports only one non-standard flow: the twoStepInvest flow. This flow is used by protocols like Ribbon, in which funds are not immediately deployed to an investment strategy when deposited, but instead batched into a single deployment each period.

twoStepDeployTime

String. The time when funds in a twoStepinvest opportunity are deployed, for display in the UI, of the format “ at ”

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",
}

Typical Response Fields

Description

balanceUnderlying

String. The total value of the position in human-readable units of underlying from the borrow market for the investment opportunity

balanceUsd

Number. The total value of the position in USD

lastUpdatedBlockNumber

Number. The block number corresponding to the data in this response. Used for communicating the sync-status of the data currently being presented. (i.e. does the state of the invest position data match the data from the subgraph)

receiptTokenBalances

Array. An array of the receipt tokens associated with this position

rewardTokenBalances

Array. An array of the reward tokens associated with this position

totalWithdrawableAmount

String. The number of receipt tokens as a raw, contract-friendly string that can be redeemed when divesting from the investment opportunity

Two Step Invest Fields

Description

pendingDepositsUnderlying

String. The amount of funds in human-readable units of underlying that have been deposited into the protocol but not yet deployed to earn yield

scheduledWithdrawalReady

Boolean. Whether funds that were previously scheduled to be withdrawn are now ready to be withdrawn

scheduledWithdrawalUnderlying

String. The amount of funds in human-readable units of underlying that were previously scheduled to be withdrawn but require additional time before they are ready to be withdrawn

Token Info Fields

Description

balance

String. Balance in human-readable units of this token for the investment account

balanceUnderlying

String. The value of this token balance, in human-readable units of underlying from the borrow market for the investment opportunity

balanceUsd

Number. The value of this token balance, in USD

decimals

Number. The number of decimals to divide a raw balance by to produce its human-readable form

token

Object. The standard details of an erc20 token

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"
        }
      ]
    }
  ]
}

Selected Fields

Description

platformId

Number. Cozy’s ID for the platform to which funds will be deposited; please consult the Cozy-Finance/platform-ids repo for the correct platformId and submit a PR to add your protocol if it’s not listed.

tokens#input

Object. Details on tokens that are deposited to enter into the opportunity

tokens#receipt

Object. Details on tokens that are returned in receipt when an opportunity is entered; these tokens are exchanged again to exit the position.

tokens#rewards

Object. Details on all the tokens which may be accrued as rewards while a position is active

divest#address

String. Address of the invest/divest contract you deployed

divest#signature

String. Signature of the divest function on the invest/divest contract you deployed

invest#address

String. Address of the invest/divest contract you deployed

invest#signature

String. Signature of the invest function on the invest/divest contract you deployed

strategy

Array. Array of strategyLegs which capture the steps an investment opportunity takes when deploying funds; used only for display in the UI.

strategy#action

String. The action taking place in this leg of the investing strategy

strategy#actionReceiver

String. The receiver of the action taking place (grammatically, the direct or indirect object)

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

Selected Market Fields

Description

borrowCap

String. Total cumulative amount in human-readable units of underlying that can be borrowed from a market at one time

borrowRate

String. The annualized interest rate in decimal form users must pay to borrow from a market

exchangeRate

String. The exchange rate of underlying / receipt token in decimal form

cash

String. The amount in human-readable underlying available to be borrowed on a market. Any borrow transactions greater than the available cash in the market will fail.

collateralFactor

String. Factor applied to the supplied funds of 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)

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

Selected AccountCTokens Fields

Description

cTokenBalance

String. The receipt token balance of this position in human-readable units

enteredMarket

Boolean. Whether the user’s supplied funds should be used as collateral for other borrows (see guide on supplying collateral)

storedBorrowBalance

String. Current borrow balance stored in the protocol contracts in human-readable units (excludes interest since last accrual block; see calculations below to get up-to-date borrowBalance)

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