DEV Community

Adam Bavosa
Adam Bavosa

Posted on • Originally published at Medium

Supplying Assets to the Compound Protocol

The Compound Protocol is a series of interest rate markets running on the Ethereum blockchain. When users and applications supply an asset to the Compound protocol, they begin earning a variable interest income instantly. Interest accrues every Ethereum block (~15 seconds), and users can withdraw their principal plus interest anytime.

Under the hood, users are contributing their assets to a large pool of liquidity (a "market") that is available for other users to borrow, and they share in the interest that borrowers pay back to the pool.

When users supply assets, they receive cTokens from Compound in exchange. cTokens are ERC20 tokens that can be redeemed for their underlying assets at any time. As interest accrues to the assets supplied, cTokens are redeemable at an exchange rate (relative to the underlying asset) that constantly increases over time, based on the rate of interest earned by the underlying asset.

Non-technical users can interact with the Compound protocol using an interface like Dharma or app.compound.finance; developers can create their own applications that interact with Compound's smart contracts.

In this guide, we're going to walk through supplying assets via Web3.js JSON RPC and via proxy smart contracts that live on the blockchain. These are the 2 ways in which developers can write software to utilize the Compound protocol.

There are examples in JavaScript and also Solidity.

Table of Contents for This Guide

If you are new to Ethereum, we suggest that you start by Setting up your Development Environment for Ethereum.

All of the code referenced in this guide can be found in this GitHub Repository: Quick Start: Supplying Assets to the Compound Protocol.

To copy the repository to your computer, run this on the command line after you've installed git:

git clone git@github.com:compound-developers/compound-supply-examples.git

Compound Markets

The Compound protocol enables developers to build innovative products on DeFi. So far, we've seen crypto wallets equipped with savings APRs, a no-loss lottery system, an interest-earning system for donation income, and more.

The smart contracts that power Compound are deployed to the Ethereum blockchain. This means that at the time of this guide's writing, the only types of assets that Compound can support are Ethereum and Ethereum tokens.

The supported assets are listed here https://compound.finance/markets. Based on the implementation of Ethereum, we have to utilize 2 similar processes:

  • The ETH supply method

  • The ERC20 token supply method

Like mentioned earlier, when someone supplies an asset to the protocol, they are given cTokens in exchange. The method for getting cETH is different from the method for getting cDAI or cREP. We'll run through code examples and explanations for the two different asset supply methods.

When supplying Ether to the Compound protocol, an application can send ETH directly to the payable mint function in the cEther contract. Following that mint, cEther is minted for the wallet or contract that invoked the mint function. Remember that if you are calling this function from another smart contract, that contract needs a payable function in order to receive ETH when you redeem the cTokens later.

The operation is slightly different for cERC20 tokens when compared to cEther. In order to mint cERC20 tokens, the invoking wallet or contract needs to first call the approve function on the underlying token's contract. All ERC20 token contracts have an approve function.

The approval needs to indicate that the corresponding cToken contract is permitted to take up to the specified amount from the sender address. Subsequently, when the mint function is invoked, the cToken contract retrieves the indicated amount of underlying tokens from the sender address, based on the prior approve call.

Example code for each method (JS and Solidity) is available, open source, in the GitHub Repository linked above.

Connecting to the Ethereum Network

Each of the code examples in this guide enable you to supply your ETH or ERC20 token to Compound on the Ethereum main net, any of the public test nets, or your own local machine's test net.

You will need to use the contract address and HTTP provider URL for the particular network that you want to use with the Compound protocol. The contract addresses for their corresponding networks are listed here: https://compound.finance/developers#networks.

If you are trying to test with your local test net, use the Main net contract addresses. We will make a fork of the main net which will run on our localhost.

If you want to use a public test net (like Ropsten, Gรถerli, Kovan, or Rinkeby), make an Infura account at https://infura.io/ to get your API key. If you are using your own localhost test net, or the production main net, we will use the Cloudflare entrypoint for the main net (https://cloudflare-eth.com) which does not require an account or API key.

For more on connecting to a public Ethereum network, see the instructions in Setting up your Development Environment for Ethereum.

Supplying to Compound on a Localhost Network

To run an Ethereum local test net on your machine, we will fork the Main net. This means that you can interact with the production smart contracts in a test environment. No real ETH will be used and no modifications to the production blockchain will occur. If you haven't already, install Node.js. Click here to install the LTS of Node.js and NPM.

Let's install and initialize Ganache CLI.

npm i -g ganache-cli

## or for yarn fans: yarn global add ganache-cli

Run this command in a second command line window before you start running the code referenced later in this guide. The command spins up a test Ethereum blockchain on your localhost.

ganache-cli \
  -f https://cloudflare-eth.com/ \
  -m "clutch captain shoe salt awake harvest setup primary inmate ugly among become" \
  -i 999 \
  -u 0x9759A6Ac90977b93B58547b4A71c78317f391A28

A quick explanation of each of the command line flags:

  • -f Forks the Main Ethereum network to your local machine for development and testing.

  • -m Runs Ganache with an Ethereum key set based on the mnemonic passed. The first 10 addresses have 100 test ETH in their balance on the local test net every time you boot Ganache. Do not use this mnemonic anywhere other than your localhost test net.

  • -i Sets an explicit network ID to avoid confusion and errors.

  • -u Unlocks an address so you can write to your localhost test blockchain without knowing that address's private key. We are unlocking the above address so we can mint our own test DAI on our localhost test net (more on this later).

Once you have run Ganache CLI on your command line, it will log 10 wallet addresses, and 10 private keys. Each of the wallets will have 100 test ETH in them which can be used for executing smart contracts locally. Copy and save the first private key.

Supplying to Compound on a Public Network

If you are supplying to Compound on the Main net, Ropsten, Gรถerli, Kovan, or Rinkeby, you should have already found and copied the Compound contract address for that network (see how above). You'll need it for later.

You also should have collected some ETH for that network by purchasing/mining (Main), or a test net's faucet (all the others).

For example, here is Ropsten's faucet https://faucet.ropsten.be/. You can send yourself 1 ETH every 24 hours from a single IP address. This is test ETH that is only applicable to the Ropsten test network.

Next, copy and safely store your wallet's private key. Don't do this if you are only testing on your localhost. The private key is used to sign transactions that are sent on the Ethereum network. The purpose of this is to certify that the transaction was created and submitted by a unique wallet.

If you are using MetaMask for your Ethereum wallet, open the menu, click the 3 dots on the right, Account Details, Export Private Key, and input your MetaMask password. This will reveal your private key. Keep it safe! Copy this value and save it for later.

It is a best practice to store a key like this as an environment variable on your local machine. When a key is stored as an environment variable, it can be referenced in code files by a variable name, instead of explicitly with a string. This promotes code cleanliness, and reduces the risk of exposing your secret.

Again, if you are only testing smart contracts on your localhost Ganache today, don't get your MetaMask private key. We'll rely on the private key from the Ganache command line log.

How to Supply ETH to Compound via Web3.js

Supplying Ether (ETH) to the Compound protocol is as easy as calling the "mint" function in the Compound cEther smart contract. The "mint" function transfers ETH to the Compound contract address, and mints cETH tokens. The cETH tokes are transferred to the wallet of the supplier.

Remember that the amount of ETH that can be exchanged for cETH increases every Ethereum block, which is about every 15 seconds. There is no minimum or maximum amount of time that suppliers need to keep their asset in the protocol. See the varying exchange rate (Supply APY) for each cToken at https://compound.finance/markets.

For more information on cToken concepts, and how the exchange rate has progressed over time, see the cToken page.

In order to call the mint function, you need to first:

  • Have ETH in your Ethereum wallet.

  • Find your Ethereum wallet's private key.

  • Connect to the network via Infura API key or Cloudflare (see above section Connecting to the Ethereum Network)

There are several programming languages that have Ethereum Web3 libraries, but the most popular at the time of this guide's writing is JavaScript.

We'll be using Node.js JavaScript to call the mint function. The following code snippets are from this Node.js file in the supplying assets guide GitHub Repository. Web browser JavaScript is nearly identical to these code examples.

Let's import Web3.js, and initialize the Web3 object. It's pointing to our localhost's Ganache, which has 100 test ETH in each of the test wallets. We get the same 10 test wallet addresses every time we run Ganache CLI with the mnemonic in the above command (from the Connecting to the Ethereum Network section).

If you are using a public network (Ropsten, Kovan, etc.), make sure your wallet has ETH, and that you have your wallet private key stored as an environment variable. Also, have your Infura API key ready if you are deploying to a public test net.

Replace the HTTP provider URL in the Web3 declaration line with the appropriate network's provider if you are not using the Ganache test environment.

const Web3 = require('web3');
const web3 = new Web3('http://127.0.0.1:8545');

Next, we'll add our wallet's private key as a variable. It's a best practice to access this as an environment variable.

// Your Ethereum wallet private key
const privateKey = process.env.myWalletPrivateKey;

// Add your Ethereum wallet to the Web3 object
web3.eth.accounts.wallet.add('0x' + privateKey);
const myWalletAddress = web3.eth.accounts.wallet[0].address;

If you are writing web browser JavaScript instead of Node.js, you can add the user's private key to the Web3 object by using the ethereum.enable() command. Here is the alternative code snippet.

// Add your Ethereum wallet to the Web3 object
ethereum.enable();
const myWalletAddress = web3.eth.accounts.wallet[0].address;

Next we'll make some variables for the contract address and the contract ABI. The contract addresses are posted on this page: https://compound.finance/developers#networks. Remember to use the main net address if you are testing with Ganache CLI. The ABI is the same regardless of the Ethereum network that we are using.

// Main Net Contract for cETH (the supply process is different for cERC20 tokens)
const contractAddress = '0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5';
const abiJson = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"mint","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"reserveFactorMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"exchangeRateStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"pendingAdmin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getCash","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newComptroller","type":"address"}],"name":"_setComptroller","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalBorrows","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"repayBorrow","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"comptroller","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"reduceAmount","type":"uint256"}],"name":"_reduceReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"initialExchangeRateMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"accrualBlockNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"totalBorrowsCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"redeemAmount","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"accrueInterest","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"borrowIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrower","type":"address"},{"name":"cTokenCollateral","type":"address"}],"name":"liquidateBorrow","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"supplyRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"liquidator","type":"address"},{"name":"borrower","type":"address"},{"name":"seizeTokens","type":"uint256"}],"name":"seize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newPendingAdmin","type":"address"}],"name":"_setPendingAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"exchangeRateCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getAccountSnapshot","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrowAmount","type":"uint256"}],"name":"borrow","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"redeemTokens","type":"uint256"}],"name":"redeem","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrower","type":"address"}],"name":"repayBorrowBehalf","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"_acceptAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newInterestRateModel","type":"address"}],"name":"_setInterestRateModel","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"interestRateModel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"borrowRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newReserveFactorMantissa","type":"uint256"}],"name":"_setReserveFactor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isCToken","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"comptroller_","type":"address"},{"name":"interestRateModel_","type":"address"},{"name":"initialExchangeRateMantissa_","type":"uint256"},{"name":"name_","type":"string"},{"name":"symbol_","type":"string"},{"name":"decimals_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"interestAccumulated","type":"uint256"},{"indexed":false,"name":"borrowIndex","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"AccrueInterest","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"minter","type":"address"},{"indexed":false,"name":"mintAmount","type":"uint256"},{"indexed":false,"name":"mintTokens","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"redeemer","type":"address"},{"indexed":false,"name":"redeemAmount","type":"uint256"},{"indexed":false,"name":"redeemTokens","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"borrowAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"Borrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"payer","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"RepayBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"liquidator","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"cTokenCollateral","type":"address"},{"indexed":false,"name":"seizeTokens","type":"uint256"}],"name":"LiquidateBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldPendingAdmin","type":"address"},{"indexed":false,"name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldAdmin","type":"address"},{"indexed":false,"name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldComptroller","type":"address"},{"indexed":false,"name":"newComptroller","type":"address"}],"name":"NewComptroller","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldInterestRateModel","type":"address"},{"indexed":false,"name":"newInterestRateModel","type":"address"}],"name":"NewMarketInterestRateModel","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldReserveFactorMantissa","type":"uint256"},{"indexed":false,"name":"newReserveFactorMantissa","type":"uint256"}],"name":"NewReserveFactor","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"admin","type":"address"},{"indexed":false,"name":"reduceAmount","type":"uint256"},{"indexed":false,"name":"newTotalReserves","type":"uint256"}],"name":"ReservesReduced","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"error","type":"uint256"},{"indexed":false,"name":"info","type":"uint256"},{"indexed":false,"name":"detail","type":"uint256"}],"name":"Failure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Approval","type":"event"}];
const compoundCEthContract = new web3.eth.Contract(abiJson, contractAddress);

The next section of code is where the magic happens. The first call in the main function gets the supply rate. This is the amount of ETH, per block, that is added to each of your supplied ETH. This is calculated using the prevailing interest rate for this cToken.

Then we supply our ETH to the protocol by calling the mint function, which mints cETH. The cETH is transferred to our wallet address.

const main = async function() {
  const supplyRatePerBlockMantissa = await compoundCEthContract.methods.
    supplyRatePerBlock().call()
  const interestPerEthThisBlock = supplyRatePerBlockMantissa / 1e18
  console.log(`Each supplied ETH will increase by ${interestPerEthThisBlock}` +
    ` this block, based on the current interest rate.`)

  console.log('Supplying ETH to the Compound Protocol...');
  // Mint some cETH by supplying ETH to the Compound Protocol
  await compoundCEthContract.methods.mint().send({
    from: myWalletAddress,
    gasLimit: web3.utils.toHex(150000),      // posted at compound.finance/developers#gas-costs
    gasPrice: web3.utils.toHex(20000000000), // use ethgasstation.info (mainnet only)
    value: web3.utils.toHex(web3.utils.toWei('1', 'ether'))
  });

  console.log('cETH "Mint" operation successful.');

The 3 subsequent function calls are not necessary, but they are here for illustration. The first method calls a getter function in the Compound contract that shows how much underlying ETH our cToken balance entitles us to. The second function shows our wallet's cToken balance. The third function gets the current exchange rate of cETH to ETH.

const _balanceOfUnderlying = await compoundCEthContract.methods
    .balanceOfUnderlying(myWalletAddress).call();
  let balanceOfUnderlying = web3.utils.fromWei(_balanceOfUnderlying).toString();
  console.log("ETH supplied to the Compound Protocol:", balanceOfUnderlying);

  const _cTokenBalance = await compoundCEthContract.methods.
    balanceOf(myWalletAddress).call();
  let cTokenBalance = (_cTokenBalance / 1e8).toString();
  console.log("My wallet's cETH Token Balance:", cTokenBalance);

  let exchangeRateCurrent = await compoundCEthContract.methods.
    exchangeRateCurrent().call();
  exchangeRateCurrent = (exchangeRateCurrent / 1e28).toString();
  console.log("Current exchange rate from cETH to ETH:", exchangeRateCurrent);

Our code sends 1 ETH to the contract, and gives our wallet cETH. The ratio of cETH to ETH should be in the ballpark of 50 to 1. Remember that the exchange rate of underlying to cToken increases over time.

Lastly, after the supply operation is complete, we'll redeem our cTokens. This is what a user or application will do when they want to withdraw their crypto asset from the Compound protocol.

The first method, redeem, redeems based on the cToken amount passed to the function call.

The second method, redeemUnderlying, which is commented out, redeems ETH based on the the amount passed to the function call.

  console.log('Redeeming the cETH for ETH...');

  console.log('Exchanging all cETH based on cToken amount...');
  await compoundCEthContract.methods.redeem(cTokenBalance * 1e8).send({
    from: myWalletAddress,
    gasLimit: web3.utils.toHex(150000),      // posted at compound.finance/developers#gas-costs
    gasPrice: web3.utils.toHex(20000000000), // use ethgasstation.info (mainnet only)
  });

  // console.log('Exchanging all cETH based on underlying ETH amount...');
  // let ethAmount = web3.utils.toWei(balanceOfUnderlying).toString()
  // await compoundCEthContract.methods.redeemUnderlying(ethAmount).send({
  //   from: myWalletAddress,
  //   gasLimit: web3.utils.toHex(150000),      // posted at compound.finance/developers#gas-costs
  //   gasPrice: web3.utils.toHex(20000000000), // use ethgasstation.info (mainnet only)
  // });

  cTokenBalance = await compoundCEthContract.methods.balanceOf(myWalletAddress).call();
  cTokenBalance = (cTokenBalance / 1e8).toString();
  console.log("My wallet's cETH Token Balance:", cTokenBalance);
}

Finally, we execute the main function and declare an error handler.

main().catch((err) => {
  console.error(err);
});

If you cloned the GitHub repository, be sure to run npm install in the root directory of the project before you try to run the script.

Here is the command for running the script from the root directory of the project:

node web3-js-examples/supply-eth-via-web3.js

Script example output:

Each supplied ETH will increase by 8.1940822e-11 this block, based on the current interest rate.

Supplying ETH to the Compound Protocol...

cETH "Mint" operation successful.

ETH supplied to the Compound Protocol: 0.999999999846752664
My wallet's cETH Token Balance: 49.97113435
Current exchange rate from cETH to ETH: 0.02001155292658976

Redeeming the cETH for ETH...
Exchanging all cETH based on cToken amount...

My wallet's cETH Token Balance: 0

How to Supply a Supported ERC20 Token to Compound via Solidity

The following will run through an example of adding an ERC20 token to the Compound protocol using Solidity smart contracts. The full Solidity file can be found in the project GitHub repository.

Here's an overview of supplying a token to Compound with Solidity:

Prerequisites

  • Get some ETH into your own Ethereum wallet by purchasing/mining (or faucets on test nets). This will be used for gas costs. If you're using Ganache CLI on a localhost, you're ready.

  • Get some ERC20 token, in this case DAI. If you are working in the production environment, purchase some DAI for your Ethereum wallet. If you are working with a Ganache CLI test blockchain, follow the instructions in the section Minting Test Net DAI to get your test wallet some DAI.

  • Get the address of the ERC20 contract. For DAI, instructions are in the Minting Test Net DAI section.

  • Get the address of the Compound cToken contract. See DAI on this page: https://compound.finance/developers#networks.

Order of Operations

  • You transfer DAI from your wallet to your custom contract. This is not done in Solidity, but instead with Web3.js and JSON RPC.

  • You call your custom contract's function for supplying.

  • Your custom contract's function calls the approve function from the original ERC20 token contract. This allows an amount of the token to be withdrawn by cToken from your custom contract's token balance.

  • Your custom contract's function calls the mint function in the Compound cToken contract.

  • Finally, we call your custom contract's function for redeeming, to get the ERC20 token back.

Let's get started. First we'll walk through the code in our Solidity file, MyContracts.sol.


pragma solidity ^0.5.12;


interface Erc20 {
    function approve(address, uint256) external returns (bool);

    function transfer(address, uint256) external returns (bool);
}


interface CErc20 {
    function mint(uint256) external returns (uint256);

    function exchangeRateCurrent() external returns (uint256);

    function supplyRatePerBlock() external returns (uint256);

    function redeem(uint) external returns (uint);

    function redeemUnderlying(uint) external returns (uint);
}

We added contract interfaces. The first is for our ERC20 token contract, and the second is for Compound's corresponding cToken contract.

We'll be able to call the production versions of the 3rd party contracts using these definitions. We need to initialize them with the production address of the deployed contracts, which we pass to each of the functions in MyContract.

contract MyContract {
    event MyLog(string, uint256);

    function supplyErc20ToCompound(
        address _erc20Contract,
        address _cErc20Contract,
        uint256 _numTokensToSupply
    ) public returns (uint) {
        // Create a reference to the underlying asset contract, like DAI.
        Erc20 underlying = Erc20(_erc20Contract);

        // Create a reference to the corresponding cToken contract, like cDAI
        CErc20 cToken = CErc20(_cErc20Contract);

        // Amount of current exchange rate from cToken to underlying
        uint256 exchangeRateMantissa = cToken.exchangeRateCurrent();
        emit MyLog("Exchange Rate (scaled up by 1e18): ", exchangeRateMantissa);

        // Amount added to you supply balance this block
        uint256 supplyRateMantissa = cToken.supplyRatePerBlock();
        emit MyLog("Supply Rate: (scaled up by 1e18)", supplyRateMantissa);

        // Approve transfer on the ERC20 contract
        underlying.approve(_cErc20Contract, _numTokensToSupply);

        // Mint cTokens
        uint mintResult = cToken.mint(_numTokensToSupply);
        return mintResult;
    }

The first function in MyContract allows the caller to supply an ERC20 token to the Compound protocol. We will need to pass the underlying contract address, the cToken contract address, and the number of tokens we want to supply.

The function first creates references to the production instances of DAI and cDAI contracts using our abstract contract definitions.

Then the function logs the exchange rate and the supply rate. These calls are not necessary for supplying. They are there for illustration. You can see the amounts in the "events" output later in JavaScript.

Next, our function approves the transfer of ERC20 token from our contract's address to Compound's cToken contract using the approve method.

Finally, our contract calls the cToken contract mint function. This sends some DAI to Compound, and gives our custom contract a balance of cDAI.

After we have supplied some DAI, we can redeem it at any time. The following function shows how we can accomplish that in Solidity.

    function redeemCErc20Tokens(
        uint256 amount,
        bool redeemType,
        address _cErc20Contract
    ) public returns (bool) {
        // Create a reference to the corresponding cToken contract, like cDAI
        CErc20 cToken = CErc20(_cErc20Contract);

        // `amount` is scaled up by 1e18 to avoid decimals

        uint256 redeemResult;

        if (redeemType == true) {
            // Retrieve your asset based on a cToken amount
            redeemResult = cToken.redeem(amount);
        } else {
            // Retrieve your asset based on an amount of the asset
            redeemResult = cToken.redeemUnderlying(amount);
        }

        // Error codes are listed here:
        // https://compound.finance/developers/ctokens#ctoken-error-codes
        emit MyLog("If this is not 0, there was an error", redeemResult);
        require(redeemResult == 0, "redeemResult error");

        return true;
    }
}

The redeemCErc20Tokens function allows the caller to redeem based on the amount of underlying or the amount of cTokens. This is indicated by calling the function with a boolean for redeem type; True for cToken, and false for underlying amount.

If there is an error with redeeming, the error code is logged using MyLog. Error codes for Compound protocol functions are described here: https://compound.finance/developers/ctokens#ctoken-error-codes

Now that we have our code written, let's run it!

Compiling

I have written a script that compiles the Solidity code based on the version we indicated at the top of the file. If you changed the solidity version, the compile script might not work.

If you cloned the GitHub repository, be sure to run npm install in the root directory of the project before you try to run the compile script.

Compile your contract with the command below. The bytecode and ABI will be waiting in the .build/ folder.

node compile-smart-contracts.js

Deploying

Once you have deployed your contract, the script will log the new MyContract address.

node deploy-smart-contracts.js

> Your contract was successfully deployed!
> The contract can be interfaced with at this address:
> 0x9C5Dd70D98e9B321217e8232235e25E64E78C595

Copy this and save it for later. We'll need it to call the smart contract's function to supply DAI to Compound.

Executing

The Web3.js code that will invoke our custom smart contract can be found in the solidity-examples/ folder. Let's run through the supply-erc20-via-solidity.js script.

First, the script makes a Web3 object and points it to the blockchain network that we want to use to supply to Compound. Next, we make some references to MyContract, the DAI contract, and also the Compound cDAI contract.

Remember, the cToken contract addresses and ABIs can be found here: https://compound.finance/developers#networks, and MyContract's address was logged when we deployed the contract.

See the Minting Test Net DAI section to find the newest DAI contract address.

Next, we make a reference to our Ethereum wallet private key. This should be a wallet that has some ETH (for gas) and also DAI (to supply to Compound). Our script's main function first transfers DAI from our wallet to MyContract.

/**
 * Executes our contract's `supplyErc20ToCompound` function
 * 
 * Remember to run your local ganache-cli with the mnemonic so you have accounts
 * with ETH in your local Ethereum environment. Don't use the keys outside of 
 * your local test environment.
 * 
 * ./node_modules/.bin/ganache-cli \
 *     -f https://mainnet.infura.io/v3/<YOUR INFURA API KEY HERE> \
 *     -m "clutch captain shoe salt awake harvest setup primary inmate ugly among become"
 *     -i 999 \
 *     -u 0x9759A6Ac90977b93B58547b4A71c78317f391A28
 */
const Web3 = require('web3');
const web3 = new Web3('http://127.0.0.1:8545');

// `myContractAddress` is logged when running the deploy script in the root
// directory of the project. Run the deploy script prior to running this one.
const myContractAddress = '0x9C5Dd70D98e9B321217e8232235e25E64E78C595';
const myAbi = require('../.build/abi.json');
const myContract = new web3.eth.Contract(myAbi, myContractAddress);

// Main net address of DAI contract
// https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f
const daiMainNetAddress = '0x6b175474e89094c44da98b954eedeac495271d0f';
const daiAbi = [{"inputs":[{"internalType":"uint256","name":"chainId_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"guy","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"bool","name":"allowed","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"pull","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"push","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}];
const daiContract = new web3.eth.Contract(daiAbi, daiMainNetAddress);

// Main net contract address and ABI for cDAI, which can be found in the mainnet
// tab on this page: https://compound.finance/developers
const compoundCDaiContractAddress = '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643';
const compoundCDaiContractAbi = [{"inputs":[{"internalType":"address","name":"underlying_","type":"address"},{"internalType":"contractComptrollerInterface","name":"comptroller_","type":"address"},{"internalType":"contractInterestRateModel","name":"interestRateModel_","type":"address"},{"internalType":"uint256","name":"initialExchangeRateMantissa_","type":"uint256"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint8","name":"decimals_","type":"uint8"},{"internalType":"addresspayable","name":"admin_","type":"address"},{"internalType":"address","name":"implementation_","type":"address"},{"internalType":"bytes","name":"becomeImplementationData","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"constructor","signature":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"cashPrior","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"interestAccumulated","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"borrowIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalBorrows","type":"uint256"}],"name":"AccrueInterest","type":"event","signature":"0x4dec04e750ca11537cabcd8a9eab06494de08da3735bc8871cd41250e190bc04"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"borrowAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accountBorrows","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalBorrows","type":"uint256"}],"name":"Borrow","type":"event","signature":"0x13ed6866d4e1ee6da46f845c46d7e54120883d75c5ea9a2dacc1c4ca8984ab80"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"error","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"info","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"detail","type":"uint256"}],"name":"Failure","type":"event","signature":"0x45b96fe442630264581b197e84bbada861235052c5a1aadfff9ea4e40a969aa0"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"liquidator","type":"address"},{"indexed":false,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"repayAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"cTokenCollateral","type":"address"},{"indexed":false,"internalType":"uint256","name":"seizeTokens","type":"uint256"}],"name":"LiquidateBorrow","type":"event","signature":"0x298637f684da70674f26509b10f07ec2fbc77a335ab1e7d6215a4b2484d8bb52"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"minter","type":"address"},{"indexed":false,"internalType":"uint256","name":"mintAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"mintTokens","type":"uint256"}],"name":"Mint","type":"event","signature":"0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event","signature":"0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contractComptrollerInterface","name":"oldComptroller","type":"address"},{"indexed":false,"internalType":"contractComptrollerInterface","name":"newComptroller","type":"address"}],"name":"NewComptroller","type":"event","signature":"0x7ac369dbd14fa5ea3f473ed67cc9d598964a77501540ba6751eb0b3decf5870d"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldImplementation","type":"address"},{"indexed":false,"internalType":"address","name":"newImplementation","type":"address"}],"name":"NewImplementation","type":"event","signature":"0xd604de94d45953f9138079ec1b82d533cb2160c906d1076d1f7ed54befbca97a"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contractInterestRateModel","name":"oldInterestRateModel","type":"address"},{"indexed":false,"internalType":"contractInterestRateModel","name":"newInterestRateModel","type":"address"}],"name":"NewMarketInterestRateModel","type":"event","signature":"0xedffc32e068c7c95dfd4bdfd5c4d939a084d6b11c4199eac8436ed234d72f926"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldPendingAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event","signature":"0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldReserveFactorMantissa","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newReserveFactorMantissa","type":"uint256"}],"name":"NewReserveFactor","type":"event","signature":"0xaaa68312e2ea9d50e16af5068410ab56e1a1fd06037b1a35664812c30f821460"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"redeemer","type":"address"},{"indexed":false,"internalType":"uint256","name":"redeemAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"redeemTokens","type":"uint256"}],"name":"Redeem","type":"event","signature":"0xe5b754fb1abb7f01b499791d0b820ae3b6af3424ac1c59768edb53f4ec31a929"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"payer","type":"address"},{"indexed":false,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"repayAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accountBorrows","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalBorrows","type":"uint256"}],"name":"RepayBorrow","type":"event","signature":"0x1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a1"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"benefactor","type":"address"},{"indexed":false,"internalType":"uint256","name":"addAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTotalReserves","type":"uint256"}],"name":"ReservesAdded","type":"event","signature":"0xa91e67c5ea634cd43a12c5a482724b03de01e85ca68702a53d0c2f45cb7c1dc5"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"admin","type":"address"},{"indexed":false,"internalType":"uint256","name":"reduceAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTotalReserves","type":"uint256"}],"name":"ReservesReduced","type":"event","signature":"0x3bad0c59cf2f06e7314077049f48a93578cd16f5ef92329f1dab1420a99c177e"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"constant":false,"inputs":[],"name":"_acceptAdmin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xe9c714f2"},{"constant":false,"inputs":[{"internalType":"uint256","name":"addAmount","type":"uint256"}],"name":"_addReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x3e941010"},{"constant":false,"inputs":[{"internalType":"uint256","name":"reduceAmount","type":"uint256"}],"name":"_reduceReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x601a0bf1"},{"constant":false,"inputs":[{"internalType":"contractComptrollerInterface","name":"newComptroller","type":"address"}],"name":"_setComptroller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x4576b5db"},{"constant":false,"inputs":[{"internalType":"address","name":"implementation_","type":"address"},{"internalType":"bool","name":"allowResign","type":"bool"},{"internalType":"bytes","name":"becomeImplementationData","type":"bytes"}],"name":"_setImplementation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x555bcc40"},{"constant":false,"inputs":[{"internalType":"contractInterestRateModel","name":"newInterestRateModel","type":"address"}],"name":"_setInterestRateModel","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xf2b3abbd"},{"constant":false,"inputs":[{"internalType":"addresspayable","name":"newPendingAdmin","type":"address"}],"name":"_setPendingAdmin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xb71d1a0c"},{"constant":false,"inputs":[{"internalType":"uint256","name":"newReserveFactorMantissa","type":"uint256"}],"name":"_setReserveFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xfca7820b"},{"constant":true,"inputs":[],"name":"accrualBlockNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x6c540baf"},{"constant":false,"inputs":[],"name":"accrueInterest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa6afed95"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"internalType":"addresspayable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xf851a440"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xdd62ed3e"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x095ea7b3"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x70a08231"},{"constant":false,"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x3af9e669"},{"constant":false,"inputs":[{"internalType":"uint256","name":"borrowAmount","type":"uint256"}],"name":"borrow","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xc5ebeaec"},{"constant":false,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"borrowBalanceCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x17bfdfbc"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"borrowBalanceStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x95dd9193"},{"constant":true,"inputs":[],"name":"borrowIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xaa5af0fd"},{"constant":true,"inputs":[],"name":"borrowRatePerBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xf8f9da28"},{"constant":true,"inputs":[],"name":"comptroller","outputs":[{"internalType":"contractComptrollerInterface","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x5fe3b567"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x313ce567"},{"constant":false,"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"delegateToImplementation","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x0933c1ed"},{"constant":true,"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"delegateToViewImplementation","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x4487152f"},{"constant":false,"inputs":[],"name":"exchangeRateCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xbd6d894d"},{"constant":true,"inputs":[],"name":"exchangeRateStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x182df0f5"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getAccountSnapshot","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xc37f68e2"},{"constant":true,"inputs":[],"name":"getCash","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x3b1d21a2"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x5c60da1b"},{"constant":true,"inputs":[],"name":"interestRateModel","outputs":[{"internalType":"contractInterestRateModel","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xf3fdb15a"},{"constant":true,"inputs":[],"name":"isCToken","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xfe9c44ae"},{"constant":false,"inputs":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"repayAmount","type":"uint256"},{"internalType":"contractCTokenInterface","name":"cTokenCollateral","type":"address"}],"name":"liquidateBorrow","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xf5e3c462"},{"constant":false,"inputs":[{"internalType":"uint256","name":"mintAmount","type":"uint256"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa0712d68"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x06fdde03"},{"constant":true,"inputs":[],"name":"pendingAdmin","outputs":[{"internalType":"addresspayable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x26782247"},{"constant":false,"inputs":[{"internalType":"uint256","name":"redeemTokens","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xdb006a75"},{"constant":false,"inputs":[{"internalType":"uint256","name":"redeemAmount","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x852a12e3"},{"constant":false,"inputs":[{"internalType":"uint256","name":"repayAmount","type":"uint256"}],"name":"repayBorrow","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x0e752702"},{"constant":false,"inputs":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"repayAmount","type":"uint256"}],"name":"repayBorrowBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x2608f818"},{"constant":true,"inputs":[],"name":"reserveFactorMantissa","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x173b9904"},{"constant":false,"inputs":[{"internalType":"address","name":"liquidator","type":"address"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"seizeTokens","type":"uint256"}],"name":"seize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xb2a02ff1"},{"constant":true,"inputs":[],"name":"supplyRatePerBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xae9d70b0"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x95d89b41"},{"constant":true,"inputs":[],"name":"totalBorrows","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x47bd3718"},{"constant":false,"inputs":[],"name":"totalBorrowsCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x73acee98"},{"constant":true,"inputs":[],"name":"totalReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x8f840ddd"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x18160ddd"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa9059cbb"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x23b872dd"},{"constant":true,"inputs":[],"name":"underlying","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x6f307dc3"}];
const compoundCDaiContract = new web3.eth.Contract(compoundCDaiContractAbi, compoundCDaiContractAddress);

// Set up a wallet using one of Ganache's key pairs.
// Don't use this key outside of your local test environment.
const privateKey = '0xb8c1b5c1d81f9475fdf2e334517d29f733bdfa40682207571b12fc1142cbf329';

// Add your Ethereum wallet to the Web3 object
web3.eth.accounts.wallet.add(privateKey);
const myWalletAddress = web3.eth.accounts.wallet[0].address;

Finally, we call our main function, which first transfers DAI from our wallet to MyContract.

const main = async function() {
  console.log('Now transferring DAI from my wallet to MyContract...');

  let transferResult = await daiContract.methods.transfer(
    myContractAddress,
    web3.utils.toHex(10e18) // 10 DAI to send to MyContract
  ).send({
    from: myWalletAddress,
    gasLimit: web3.utils.toHex(150000),      // posted at compound.finance/developers#gas-costs
    gasPrice: web3.utils.toHex(20000000000), // use ethgasstation.info (mainnet only)
  });

  console.log('MyContract now has DAI to supply to the Compound Protocol.');

Next we call the supplyErc20ToCompound function in MyContract, which sends 10 DAI to Compound in exchange for cDAI.

  // Mint some cDAI by sending DAI to the Compound Protocol
  console.log('MyContract is now minting cDAI...');
  let supplyResult = await myContract.methods.supplyErc20ToCompound(
    daiMainNetAddress,
    compoundCDaiContractAddress,
    web3.utils.toHex(10e18) // 10 DAI to supply
  ).send({
    from: myWalletAddress,
    gasLimit: web3.utils.toHex(5000000),      // posted at compound.finance/developers#gas-costs
    gasPrice: web3.utils.toHex(20000000000), // use ethgasstation.info (mainnet only)
  });

  console.log('Supplied DAI to Compound via MyContract');
  // Uncomment this to see the solidity logs
  // console.log(supplyResult.events.MyLog);

The next 2 function calls are not necessary for supplying. They illustrate how to get the balance of underlying ERC20 asset in Compound and the amount of cTokens that MyContract now holds.

  let balanceOfUnderlying = await compoundCDaiContract.methods
    .balanceOfUnderlying(myContractAddress).call();
  const balanceOfUnderlyingDai = web3.utils.fromWei(balanceOfUnderlying);
  console.log("DAI supplied to the Compound Protocol:", balanceOfUnderlyingDai);

  let cTokenBalance = await compoundCDaiContract.methods.balanceOf(myContractAddress).call();
  cTokenBalance = cTokenBalance / 1e8;
  console.log("MyContract's cDAI Token Balance:", cTokenBalance);

Lastly we call the redeemCErc20Tokens function in MyContract to redeem the cDAI for DAI. The example utilizes the redeem method by passing a cToken amount. Under that, there is a redeem underlying amount example, which is commented out.

// Call redeem based on a cToken amount
  const amount = web3.utils.toHex(cTokenBalance * 1e8);
  const redeemType = true; // true for `redeem`

  // Call redeemUnderlying based on an underlying amount
  // const amount = web3.utils.toHex(balanceOfUnderlying);
  // const redeemType = false; //false for `redeemUnderlying`

  // Retrieve your asset by exchanging cTokens
  console.log('Redeeming the cDAI for DAI...');
  let redeemResult = await myContract.methods.redeemCErc20Tokens(
    amount,
    redeemType,
    compoundCDaiContractAddress
  ).send({
    from: myWalletAddress,
    gasLimit: web3.utils.toHex(600000),      // posted at compound.finance/developers#gas-costs
    gasPrice: web3.utils.toHex(20000000000), // use ethgasstation.info (mainnet only)
  });

  if (redeemResult.events.MyLog.returnValues[1] != 0) {
    throw Error('Redeem Error Code: '+redeemResult.events.MyLog.returnValues[1]);
  }

  cTokenBalance = await compoundCDaiContract.methods.balanceOf(myContractAddress).call();
  cTokenBalance = cTokenBalance / 1e8;
  console.log("MyContract's cDAI Token Balance:", cTokenBalance);
}

main().catch((err) => {
  console.error(err);
});

Now we're ready to run!

If you are running on localhost remember to mint test DAI before you run the JavaScript file!

node mint-testnet-dai.js

If you are running this on a public network, you'll need to acquire DAI for that network.

To execute the script, navigate to the project root directory and run:

node solidity-examples/supply-erc20-via-solidity.js

If successful, the output of the script will show something like this:

Now transferring DAI from my wallet to MyContract...
MyContract now has DAI to supply to the Compound Protocol.

MyContract is now minting cDAI...
Supplied DAI to Compound via MyContract

DAI supplied to the Compound Protocol: 9.999999999820570224
MyContract's cDAI Token Balance: 490.54535423

Redeeming the cDAI for DAI...
MyContract's cDAI Token Balance: 0

Remember that this code will work with any of the ERC20 tokens that Compound supports. You will need to swap in the corresponding ERC20 token contract address and ABI into the JavaScript.

Thanks for reading! You are now able to supply assets to the Compound protocol using Solidity or JavaScript. We walked through supplying Ether, and also the supported ERC20 token assets. We can also redeem cTokens for those underlying assets later on. There are 4 different code examples available in the Supply Examples GitHub repository.

The next key concept of developing a DApp with the Compound protocol is borrowing assets. Be sure to check out the next developer quick start guide: Borrowing Assets from the Compound Protocol.

Be sure to subscribe to the Compound Newsletter. Feel free to comment on this post, or get in touch in the #development room on our very active Discord server.

Top comments (0)