This guide illustrates how you can view and manage your protection positions programmatically using a script written in TypeScript. The tasks in this section assume that you are a participant in a protection market and have supplied collateral to purchase protection as described in Buy protection programmatically. If you haven't already done so, you should read the Buy protection programmatically guide or review the buy-protection.ts script before continuing with this guide.
All of the code snippets in this guide are from the manage-protection.ts script in the Cozy developer guides repository. See that repository for more context, definitions of helper methods used, and related information.
Everything in this guide assumes that you have experience with JavaScript, ethers.js, and Solidity.
Prepare to manage positions
The manage-protection.ts script includes code to set up a your environment and buy protection so that you have an account with positions to manage. Because those steps are covered in other guides, they aren't included in this guide. For managing positions, however, you need to specify how many decimals Cozy tokens have. You can add this information to the Environment Setup section of the code with a line like this:
manage-protection.ts
// All Cozy Tokens have 8 decimals, so we define this for convenienceconstcTokenDecimals=8;
After the Environment Setup instructions, the manage-protection.ts script includes a condensed version of the code for buying protection, which is covered in more detail in the buy-protection.ts script. You can view the condensed version of the code here.
View positions
As the last step of buying protection, you borrowed funds from a protection market. In this case, you have borrowed protected USDC from a Yearn protection market. Now, let's get an array of Cozy token (cToken) addresses for each market you've entered.
manage-protection.ts
// STEP 1: VIEWING POSITIONS// To start, let's get an array of CToken addresses for each market// we've entered. When you borrow from a market, you are automatically// entered into that marketconstourAddress=signer.address; // shorthand, for convenience// Get an array of CozyToken addresses of markets enteredconstassets=awaitcomptroller.getAssetsIn(ourAddress);
When you borrow from a market, you are automatically entered into that market. When you supply to a market, however, you must explicitly enter the market.
In practice, this distinction means that the getAssetsIn method only returns:
Assets that you have borrowed.
Assets that you have supplied and have used to enter markets.
If you have supplied assets but have not explicitly entered a market with them, those assets are not included in the results because there's no position to manage and the Comptroller does not need to be aware of those assets.
To determine whether you have borrowed from a market or supplied to the markets, you can loop through each asset and learn its current status. The comments in this code snippet explain what's happening at each step.
manage-protection.ts
console.log('Checking status of each asset...\n');for (constassetof assets) {// First we'll check balances in this loop and see if any of our// balances come from borrows. For brevity, we prefix all properties// about the underlying token with `u`constcToken=newContract(asset, cozyTokenAbi, signer);const [name,symbol] =awaitPromise.all([cToken.name(),cToken.symbol()]);console.log(`Current asset has name "${name}" and symbol "${symbol}"`);// Let's also learn about the underlying token. First we determine// if the underlying is ETH then get a contract instance for the// underlying token if the underlying is not ETHconstuAddr=awaitcToken.underlying();constisEth= uAddr === ethAddress; // true if underlying is ETH, false otherwiseconstunderlying= isEth ?null:newContract(uAddr, erc20Abi, signer);// Lastly we either read set the values if ETH is underlying, or// read the values if it's a tokenconstuName= underlying ?awaitunderlying.name() :'Ether';constuSymbol= underlying ?awaitcToken.symbol() :'ETH';constuDecimals= underlying ?awaitunderlying.decimals() :18;console.log(` Underlying ${uName} (${uSymbol}) has ${uDecimals} decimals`);// Get our balance of the Cozy tokenconstbalance=awaitcToken.balanceOf(ourAddress);constbalanceScaled=formatUnits(balance, cTokenDecimals);if (balance.eq(Zero)) {// Balance is zero, so we have not supplied the underlying tokenconsole.log(' No Cozy Token balance, so nothing supplied'); } else {// Balance is > 0, so we have supplied some amount of underlying// tokens. We get exchange rate to figure out how much underlying// we have. The exchange rate is a mantissa (18 decimal value), so// the value returned is scaled by// 10 ** (18 + underlyingDecimals - cTokenDecimals)constexchangeRate=awaitcToken.exchangeRateStored();constscale=18+ uDecimals - cTokenDecimals;constuBalance=balance.mul(exchangeRate);constuBalanceScaled=formatUnits(uBalance, scale + cTokenDecimals);console.log(` Balance of ${balanceScaled} Cozy Tokens (equal to ${uBalanceScaled} underlying)`); }// Now get our balance of the underlying tokenconstuBalance= underlying ?awaitunderlying.balanceOf(ourAddress) :awaitsigner.provider.getBalance(ourAddress);if (uBalance.eq(Zero)) {// Underlying balance is zero, so we have not borrowed the// underlying tokenconsole.log(` No underlying ${uSymbol} balance`); } else {// Underlying balance is above zero, BUT we still may not have// borrowed this token -- we may have already had someconstuBalanceScaled=formatUnits(uBalance, uDecimals);console.log(` Balance of ${uBalanceScaled} underlying ${uSymbol} tokens`);// Read the amount borrowedconstborrowBalance=awaitcToken.borrowBalanceStored(ourAddress);constborrowBalanceScaled=formatUnits(borrowBalance, uDecimals); // scale to human readable units// Now we determine if the funds were borrowedif (borrowBalance.eq(Zero)) console.log(` None of the underlying ${uSymbol} tokens we have were borrowed`);elseif (borrowBalance.eq(uBalance)) console.log(` All the underlying ${uSymbol} tokens we have were borrowed`);elseconsole.log(` ${borrowBalanceScaled} of the ${uBalanceScaled} underlying ${uSymbol} tokens were borrowed`); }console.log('\n');} // end for each asset
Check account liquidity
The amount of collateral you have is computed by multiplying the supplied USD balance in a market by that market's collateral factor, and summing that across all markets. Total borrow balances are subtracted from that, resulting in an account liquidity value. Users who have negative account liquidity, i.e. a shortfall, are subject to liquidation, and are prevented from withdrawing or borrowing assets.
To avoid liquidation, you must ensure that account liquidity is always greater than zero. You can check this for an account as follows:
manage-protection.ts
// STEP 2: CHECKING ACCOUNT LIQUIDITY// getAccountLiquidity() returns three values. The first is an error// code, the second is the excess liquidity, and the third is the// shortfall. Only one of the last two will ever be positiveconst [errorCode,liquidity,shortfall] =awaitcomptroller.getAccountLiquidity(signer.address);// Make sure there were no errors reading the dataif (errorCode.toString() !=='0') {logFailure(`Could not read liquidity. Received error code ${errorCode}. Exiting script`);return;}// There were no errors, so now we check if we have an excess or a// shortfallif (shortfall.gt(Zero)) {logFailure(`WARNING: Account is under-collateralized and may get liquidated! Shortfall amount: ${shortfall}`);} elseif (liquidity.gt(Zero)) {logSuccess(`Account has excess liquidity and is safe. Amount of liquidity: ${liquidity}`);} else {logFailure('WARNING: Account has no liquidity and no shortfall');}
Manage liquidity
After checking your account liquidity, you might find that you are at risk of having your account liquidated. If you find your account liquidity is too close to the minimum required—for example, you have a shortfall or only a small amount of excess liquidity—you can adjust your position by doing the following:
Supply additional collateral.
Repay debt.
Supply additional collateral
If you want to adjust your account liquidity (another way of measuring what's commonly known as your collateralization ratio) to reduce the risk of liquidation, you can supply more collateral.
To supply more collateral programmatically, follow the detailed steps in buy-protection.ts script (or reference the abbreviated version of the code for supplying collateral in the manage-protection.ts script).
Repay debt
An alternate way to reduce your chance of liquidation is to pay back some, or all, of your borrowed debt.
There are two options for repaying debts:
Repay debt you, as the transaction caller, borrowed.
Repay debt on behalf of another borrower.
repayBorrow
You can repay your own borrows using the repayBorrow() method. As the following code snippet illustrates, you first approve the contract to spend DAI, then execute the repay.
manage-protection.ts
// If we want to repay our own borrows, we can use the repayBorrow// method. First we approve the contract to spend our USDC, then we// execute the repay// Send approval transaction and wait it to be minedconstusdcApproveTx=awaitusdc.approve(yearnProtectionMarket.address, MaxUint256);awaitusdcApproveTx.wait();// Define the amount to repay, taking USDC's decimals into accountconstusdcDecimals=awaitusdc.decimals();constrepayAmount=parseUnits('25', usdcDecimals); // repay 25 USDCconstrepayTx=awaityearnProtectionMarket.repayBorrow(repayAmount);awaitfindLog(repayTx, yearnProtectionMarket,'RepayBorrow', provider);logSuccess('Successfully repaid a portion of the borrow');
This example, repays a debt of 25 DAI for the account that called the repayBorrow method.
If you wanted to repay the full amount instead of just 25 DAI, the best way to do this is to set the repayAmount to MaxUint256. The Cozy contracts recognize this as a magic number that will repay all your token debt. If you try to repay all token debt without using MaxUint256 as the amount, you'll be left with a very tiny borrow balance, known as dust. This happens because interest accrues at the beginning of the repay transaction, and it's extremely difficult to predict how much will accrue and send the exact amount of tokens required. Using MaxUint256 tells the contracts to repay the full debt after interest accrues.
repayBorrowBehalf
Alternatively, you could have executed the same logic in the code snippet above using the repayBorrowBehalf method. For example, you could have used repayBorrowBehalf(borrower,repayAmount) to repay repayAmount on behalf of borrower.
The repayBorrowBehalf() method is particularly useful for repaying ETH debt. Because ETH is required for gas, calculating how much ETH you need to repay ETH debt can be difficult. For example, if you wanted to repay a full ETH balance, you would need to calculate the gas required for the transaction as well as the interest accrued. Instead of trying to predict the exact gas usage plus interest accrual, you can repay full ETH debts by using a special contract called the Maximillion contract. The Maximillion contract lets you send extra ETH along with your transaction. The contract repays your debt using repayBorrowBehalf(), then refunds you the excess after paying back all debt.
The following code snippet illustrates using the Maximillion contract to repay ETH debt.
manage-protection.ts
// Repay all ETH debt with the Maximillion contract (we actually have// zero ETH debt in the script, but that's ok). First we get an// instance of the Maximillion contractconstmaximillionAddress=getContractAddress('Maximillion', chainId);constmaximillion=newContract(maximillionAddress, maximillionAbi, signer);// Get the address of the Cozy ETH Money MarketconstethMarketAddress=getContractAddress('CozyETH', chainId);// Now we do the repay. In this case our debt is zero, so any ETH sent// is excess ETH that will be refunded after repaying the debt. Notice// how we specify that we are repaying debt for ourselves,// `signer.address`, and we specify the address of the market to repay// debt in, `ethMarketAddress`. (We don't look for the success logs// since this is a dummy transaction and we have no debt, but in// practice you should).constvalue=parseUnits('0.1',18); // ETH has 18 decimalsconstrepayEthTx=awaitmaximillion.repayBehalfExplicit(signer.address, ethMarketAddress, { value });