Cozy provides a protected borrowing marketplace for investing in decentralized finance protocols that offer protection against a loss of funds. You pay a small premium to purchase protection for funds you deposit and can leverage your investment by borrowing against it. This guide illustrates how you can write a script using TypeScript that you can then use to buy protection from a protection market programmatically,
All code snippets are from the buy-protection.ts script in the Cozy Developer Guides repository. See that repository for more context, definitions of helper methods used, etc.
Everything in this guide assumes that you have experience with JavaScript, ethers.js, and Solidity.
Supply collateral
Before you can buy protection by borrowing protected funds from a Cozy market, you must supply collateral to borrow against.
All markets in Cozy—money markets and protection markets—have a trigger state variable. If the value of the trigger property is the zero address, the money market does not have a trigger contract associated with it. A money market that does not have a trigger contract associated with it cannot be used for protected borrowing, but can be used for ordinary borrowing. If the value of the trigger property is anything except the zero address, the value of the trigger property represents the address of the trigger contract associated with that market. Only assets supplied to Cozy markets that have the zero address for the trigger property, known as money markets, can be used as collateral. Similarly, only Cozy markets with a defined trigger contract, known as protection markets, can be used for protected borrowing.
Therefore, the first step toward protected borrowing is to determine the address of the Cozy money market you intend to use as collateral. To see how to do that, let's assume that you want to buy protection for an ETH investment and you have 2 ETH to use as collateral.
Determine address of the money market
The following code snippet illustrates how to check the trigger property to see if it returns the zero address and how to find the address for the Cozy ETH Money Market where you will deposit your collateral:
buy-protection.ts
// STEP 0: ENVIRONMENT SETUPconstsupplyAmount='2'; // Supply 2 ETH as collateralconstborrowAmount='500'; // Borrow 500 USDC// STEP 1: SUPPLY COLLATERAL// We need the Comptroller, so create an instance of the Comptroller contractconstcomptrollerAddress=getContractAddress('Comptroller', chainId);const comptroller = new Contract(comptrollerAddress, comptrollerAbi, signer); // connect signer for sending transactions
// Let's say we have ETH to use as collateral// The first check is to make sure an ETH Money Market exists that we// can supply to. We know that Money Markets have a trigger address// of the zero address, so we use that to query the Comptroller for// the Money Market addressconstcozyEthAddress=awaitcomptroller.getCToken(ethAddress, AddressZero);// If the returned address is the zero address, a money market does// not exist and we cannot supply ETHif (cozyEthAddress === AddressZero) {logFailure('No ETH Money Market exists. Exiting script');return;}logSuccess(`Safe to continue: Found ETH Money Market at ${cozyEthAddress}`);
In this example, the ethAddress used as input to the getCToken method is the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE that Cozy uses to represent ETH.
Supply the collateral
After verifying that there's a valid Cozy money market available for depositing your collateral, you are ready to supply the collateral to that money market. In this example, you are supplying 2 ETH, and ETH has 18 decimal places, so your script need to account for that. The process is slightly different for ETH than for ERC-20 tokens. The following code and comments describe how supplying collateral using ETH differs from supplying collateral when using ERC-20 tokens.
buy-protection.ts
// Create a contract instance of the Cozy ETH money marketconstcozyEth=newContract(cozyEthAddress, cozyEthAbi, signer); // To supply ERC-20 tokens as collateral:// const cozyToken = new Contract(cozyTokenAddress, cozyTokenAbi, signer); // for tokens// We're now ready to supply the collateral to the market, but there's// some preparation we need to do beforehand. For example, ETH// has 18 decimal places, so we need to take that into account.// `parseUnits` is a method from ethers.jsconstparsedSupplyAmount=parseUnits(supplyAmount,18); // scale amount based on number of decimals// If using a token, here is where you'd approve the Cozy Money Market// contract to spend your tokens. If you trust the Cozy contract,// approve it to spend the maximum possible amount to avoid future// approvals and save gas. Below we show a sample snippet of an// approval transaction and verifying it was successful:// const approveTx = await token.approve(cozyToken.address, MaxUint256);// await approveTx.wait();// const allowance = await token.allowance(signer.address, cozyToken.address);// if (!allowance.eq(MaxUint256)) {// logFailure('CozyToken does not have sufficient allowance to spend our token. Exiting script');// return;// }// logSuccess('Approval transaction successful. Ready to mint CozyToken with our token');// Ready to mint our CozyETH from ETHconstmintTx=awaitcozyEth.mint({ value: parsedSupplyAmount, gasLimit:'5000000' }); // const mintTx =await cozyToken.mint(parsedSupplyAmount); // for tokensconst { log: mintLog, receipt: mintReceipt } =awaitfindLog(mintTx, cozyEth,'Mint', provider);logSuccess(`CozyETH successfully minted in transaction ${mintReceipt.transactionHash}`);
Verify transaction results
In some cases, the mint transaction can appear to be successful without the mint operation actually being successful.
Because of how Cozy does error handling, transactions can be successful—and be displayed as successful on Etherscan and other block explorers—but without doing what you expected. For example, if a transaction returns an error code and emits a Failure event instead of reverting, it might appear as if the transaction was successful when it has actually failed. You can read more about error codes and failures in Error codes, and see information about error handling history here.
You should manually ensure the mint transaction succeeded before continuing to the next step. You can use the findLog() helper method to simplify the verification process. For more details about using the findLog() method, see the utils.ts file in the Cozy developer Guides repository.
Enter markets
Supplying assets does not automatically mean you can use them as collateral. To use the supplied assets as collateral, you must explicitly call the enterMarkets function on the Comptroller. The following code snippet illustrates how to add your collateral to the market:
buy-protection.ts
// STEP 2: ENTER MARKETS// Supplying assets does not automatically mean we can use them as// collateral. To do that, we need to explicitly call enterMarkets// on the Comptroller for each asset we want to use as collateral. For// now, that's just ETH. (We use `em` as shorthand for `enterMarkets`// in our variable names)constmarkets= [cozyEth.address]; // array of markets to enterconstemTx=awaitcomptroller.enterMarkets(markets);const { log: emLog, receipt: emReceipt } =awaitfindLog(emTx, comptroller,'MarketEntered', provider);logSuccess(`Markets entered successfully: ETH can now be used as collateral`);
Borrow funds
Your account is now ready to borrow protected funds. Let's say there's a Yearn protection market for yUSDC, and you want to borrow protected USDC to invest in that protection market. The steps are very similar to the steps for supplying collateral, so let's jump straight to the code:
buy-protection.ts
// STEP 3: BORROW FUNDS// Your account is now ready to borrow funds// We want to borrow protected USDC so we can deposit it straight into// Yearn's yUSDC vault, so first let's verify the underlying token// we'd borrow is in fact USDCconstusdc=newContract(getContractAddress('USDC', chainId), erc20Abi, signer);constyearnProtectionMarketAddress=getContractAddress('YearnProtectionMarket', chainId);constyearnProtectionMarket=newContract(yearnProtectionMarketAddress, cozyTokenAbi, signer);constunderlying=awaityearnProtectionMarket.underlying();if (usdc.address !==getAddress(underlying)) {// We use ethers' getAddress() method to ensure both addresses are// checksummed before comparing them. If this block executes, the// underlying of the protection market is not the underlying we// want (USDC), so we exit the scriptlogFailure('USDC addresses do not match. Exiting script');return;}// Now we execute the borrowconstparsedBorrowAmount=parseUnits(borrowAmount,awaitusdc.decimals()); // scale amount based on number of decimalsconstborrowTx=awaityearnProtectionMarket.borrow(parsedBorrowAmount);const { log: borrowLog, receipt: borrowReceipt } =awaitfindLog(borrowTx, yearnProtectionMarket,'Borrow', provider);logSuccess(`Protected USDC borrowed in transaction ${borrowReceipt.transactionHash}`);// Done! You are now supplying ETH as collateral to borrow protected// USDC. The USDC debt will not need to be paid back if the Yearn// trigger event occurs, so the borrowed USDC can now be safely// supplied to Yearn
At this point, your account is now supplying 2 ETH as collateral to borrow 500 USDC. That USDC debt will not need to be paid back if the trigger event occurs, so you can safely deposit the 500 USDC into a Yearn vault.