Build more with QuickNode - New pricing plans and a free tier! Read the press release

Creating a RESTful API for Compound Finance Smart Contracts

April 12, 2022

Overview

Compound finance are early pioneers in the decentralized finance space, as one of the first defi lenders. Compound offers a way to earn interest on several tokens: ETH, BAT, DAI, REP, WBTC, USDC & a few others. Compound makes this possible by locking your assets in a smart contract on the Ethereum chain that pays out interest every single time a block is mined (about once every 15 seconds). 

If you are an exchange or a wallet, you might want to offer your users a way to lock their assets in Compound in order to earn interest while they're not trading. In this guide, we will develop a simple REST API that supports all available compound assets with most Compound smart contract methods that allow you to earn interest - using Python, Flask and Web3.py.

Understanding Compound Lending Platform

There is some Compound specific vocabulary we need to understand before we go about building our API. This is not an all encompassing glossary, if you'd like more in depth explanations, go check out the compound finance documentation on cTokens, but here is the basic terminology to get us started:

cTokens:
So cTokens are basically tokens that you get in exchange for other tokens, like ETH, BAT, WBTC, REP, DAI, USDC, USDT and ZRX. cTokens pay you interest based on the market rate. Compound is constantly adding new cTokens and you can see a list here.

cTokens are technically ERC-20 tokens themselves.

Minting:
Minting is how you loan out your tokens and get cTokens in return. You send your desired asset in, and get cTokens back. For example, you can send USDC to a Compound smart contract and get back cUSDC - which at the time of this writing, earns an APY of 1.24%.

Redeeming:
Redeeming is how you request assets back from your account and get part or all of your original asset back + interest. So for example, if I "minted" 500 cUSDC by sending in 500 USDC and it grew to 550 cUSDC, I could make a call to redeem all of my cUSDC balance and receive 550 USDC back.

Note: For our purposes we will only code an API against viewing balances, viewing interest rates, minting aka supplying and redeeming your tokens. Also, we did not use real cToken exchange rates to keep the examples simple.

Borrowing:
Borrowing is exactly how it sounds, except you can only borrow if you have cTokens. For each cToken type, there is a maximum percent of your collateral that you can borrow. For instance, for cETH, you can borrow up to a maximum of 75% of your cTokens. To make it more practical, imagine if you used 100 ETH to mint cETH. Then you could only borrow 75 ETH or it's equivalent value in assets supported by the Compound protocol / smart contracts. Once you borrow, interest starts accumulating after each block is mined based on the borrowing rates set by Compound.

Repaying:
This is the last piece. This is of course, how you pay back the loan you borrowed. You will need to repay the amount you borrowed plus interest. In order to find out how much you owe at any given moment, you need to ask the Compound smart contract.

Environment set up

Today, we'll be building with Python 3. So, please make sure you have that installed. Let's first create a directory to house all of our work:

environment set up

Copy
$ mkdir compound-rest && cd compound-rest
Now let's set up a lightweight virtual environment to keep our system python clean:

environment set up

Copy
$ python3 -m venv compound_env && source compound_env/bin/activate
You might need to add an extension to your `bin/activate` in the example above if you're using a different shell, like for instance, fish. Go here to find your extension. By now, this is about what your terminal should look like:


Last step will be to add a `requirements.txt` file and install all of the required software, create a new file called `requirements.txt` in your `compound-rest` directory and put this in there:

environment set up

Copy
flask
web3
requests

Now go ahead and install them:

environment set up

Copy
$ pip3 install -r requirements.txt

Booting an Ethereum node

You will need to either install and sync your own Ethereum node, or use a service like QuickNode to get access to the ethereum blockchain. If you'd like to sync your own node, we highly recommend using EthHub as a jumping off point.

If you'd like to skip all that, go grab a launch node and come back here once you've created an endpoint and copied the HTTP Provider like in this screenshot:

A screenshot of the Quicknode Ethereum endpoint on the Getting Started page with an HTTP link and WSS


Important: Save this URL we'll need it later.

Fetching supported tokens

Next let's get a small script going to fetch all the supported currencies you can earn interest on from Compound. They have this handy endpoint we can use: https://api.compound.finance/api/v2/ctoken

Here is the script to start, I put it in `supported_tokens.py`:

fetching supported tokens

Copy
import requests
import pprint
res = requests.get("https://api.compound.finance/api/v2/ctoken")
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(res.json())

Go ahead and run this:

fetching supported tokens

Copy
$ python3 supported_tokens.py

If you see a bunch of output that contains `cToken` - you should be good. It should look like this:


We will use this to make sure our REST API dynamically supports new assets as they become supported by Compound without us having to update our code each time. For reference, if you would like official documentation on what we're looking at and using, check out the Compound documentation.

Creating a wallet for our API

Before we proceed, we'll need to piece together a little script to generate an Ethereum wallet, with it's private key and public address. If you have your own, you can use that, but we caution against that. Put the following in a file called `create_address.py`:

creating a wallet for our api

Copy
from web3.auto import w3
acct = w3.eth.account.create()
print("Private Key **SAVE THIS**: {}".format(acct.privateKey.hex()))
print("ETH address: {}".format(acct.address))
Save the private key and address we will need them in our next step.

Creating a REST API for Compound with Flask

So here is the meat of our app, all of our code here will go into a file called `app.py` in the `compound-rest` folder we created earlier:

creating a rest api for compound with flask

Copy
import os
import json
import requests
from decimal import *
from flask import Flask, request
from web3 import Web3
app = Flask(__name__)

ETH_PROVIDER = os.environ.get('ETH_PROVIDER')
ETH_ACCT_KEY = os.environ.get('ETH_ACCT_KEY')
ETH_ADDRESS = os.environ.get('ETH_ADDR')
w3 = Web3(Web3.HTTPProvider(ETH_PROVIDER))

@app.route('/ctokens')
def ctokens():
    """
      Returns all ctokens:
      - symbol
      - symbol on compound
      - interest rate
    """
    res = requests.get("https://api.compound.finance/api/v2/ctoken")
    response = []
    for t in res.json()['cToken']:
        token = {
            'symbol': t['underlying_symbol'],
            'compound_symbol': t['symbol'],
            'rate': '{}%'.format(round(Decimal(t['supply_rate']['value'])*100, 2))
        } 
        response.append(token)
    return ({'supported': response}, {'Content-Type': 'application/json'})

@app.route('/ctokens/<symbol>')
def ctokens_detail(symbol):
    """
      Returns particular ctoken for underlying:
      - symbol
      - interest accrued
      - principal
      - current ctoken balance
    """
    symbol = symbol.upper()
    tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
    balances = requests.get("https://api.compound.finance/api/v2/account?addresses[]={}".format(ETH_ADDRESS))

    response = {
        'principal': '0.0',
        'current_balance': '0.0',
        'interest_accrued': '0.0',
        'ctoken_balance': '0.0'
    }
    for t in tokens.json()['cToken']:
        if t['underlying_symbol'] == symbol:
            response['symbol'] = t['underlying_symbol']
            accts = balances.json()['accounts']
            if len(accts) > 0:
                for acct in accts[0]['tokens']:
                    if acct['symbol'] == t['symbol']:
                        abi_url = "https://raw.githubusercontent.com/compound-finance/compound-protocol/master/networks/mainnet-abi.json"
                        abi = requests.get(abi_url)
                        tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
                        contract_address = [t['token_address'] for t in tokens.json()['cToken'] if t['underlying_symbol'] == symbol][0]
                        compound_token_contract = w3.eth.contract(abi=abi.json()["c{}".format(symbol)], address=Web3.toChecksumAddress(contract_address)) 
                        nonce = w3.eth.getTransactionCount(ETH_ADDRESS)
                        total = acct['safe_withdraw_amount_underlying']['value']
                        interest = acct['lifetime_supply_interest_accrued']['value']
                        response['principal'] = "{}".format(Decimal(total) - Decimal(interest))
                        response['current_balance'] = total
                        response['interest_accrued'] = interest
                        response['ctoken_balance'] = str(compound_token_contract.functions.balanceOf(ETH_ADDRESS).call())
    return (response, {'Content-Type': 'application/json'})

@app.route('/ctokens/<symbol>/mint', methods=['POST'])
def ctokens_mint(symbol):
    """
      Mints particular ctoken based on:
      - symbol
      - amount
      and returns:
      - symbol
      - amount minted
      - tx id
    """
    symbol = symbol.upper()
    amt = request.form['amount']
    abi_url = "https://raw.githubusercontent.com/compound-finance/compound-protocol/master/networks/mainnet-abi.json"
    abi = requests.get(abi_url)
    tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
    contract_address = [t['token_address'] for t in tokens.json()['cToken'] if t['underlying_symbol'] == symbol][0]
    amount = w3.toWei(amt, 'ether')
    compound_token_contract = w3.eth.contract(abi=abi.json()["c{}".format(symbol)], address=Web3.toChecksumAddress(contract_address)) 
    nonce = w3.eth.getTransactionCount(ETH_ADDRESS)
    mint_tx = compound_token_contract.functions.mint().buildTransaction({
        'chainId': 1,
        'gas': 500000,
        'gasPrice': w3.toWei('20', 'gwei'),
        'nonce': nonce,
        'value': int(amount)
    })
    signed_txn = w3.eth.account.sign_transaction(mint_tx, ETH_ACCT_KEY)
    try:
        tx = w3.eth.sendRawTransaction(signed_txn.rawTransaction) 
    except ValueError as err:
        return (json.loads(str(err).replace("'", '"')), 402, {'Content-Type': 'application/json'})

    response = {
        'symbol': symbol,
        'amount': amt,
        'tx_id': tx.hex()
    }
    return (response, {'Content-Type': 'application/json'})

@app.route('/ctokens/<symbol>/redeem', methods=['POST'])
def ctokens_redeem(symbol):
    """
      Redeems particular ctoken based on:
      - symbol
      - amount
      and returns:
      - symbol
      - tx_id
    """
    symbol = symbol.upper()
    amt = request.form['amount']
    abi_url = "https://raw.githubusercontent.com/compound-finance/compound-protocol/master/networks/mainnet-abi.json"
    abi = requests.get(abi_url)
    tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
    contract_address = [t['token_address'] for t in tokens.json()['cToken'] if t['underlying_symbol'] == symbol][0]
    compound_token_contract = w3.eth.contract(abi=abi.json()["c{}".format(symbol)], address=Web3.toChecksumAddress(contract_address)) 
    nonce = w3.eth.getTransactionCount(ETH_ADDRESS)
    redeem_tx = compound_token_contract.functions.redeem(int(amt)).buildTransaction({
        'chainId': 1,
        'gas': 500000,
        'gasPrice': w3.toWei('20', 'gwei'),
        'nonce': nonce
    })
    signed_txn = w3.eth.account.sign_transaction(redeem_tx, ETH_ACCT_KEY)
    try:
        tx = w3.eth.sendRawTransaction(signed_txn.rawTransaction) 
    except ValueError as err:
        return (json.loads(str(err).replace("'", '"')), 402, {'Content-Type': 'application/json'})

    response = {
        'symbol': symbol,
        'tx_id': tx.hex()
    }
    return (response, {'Content-Type': 'application/json'})

Save that file. Now let's run our server:

creating a rest api for compound with flask

Copy
$ ETH_ADDR=<WALLET_ADDRESS> ETH_ACCT_KEY=<WALLET_PRIVATE_KEY> ETH_PROVIDER=<ETHEREUM_NODE_PROVIDER_HERE> FLASK_APP=app.py flask run

Remember to replace the parts that say "<WALLET_ADDRESS>", "<WALLET_PRIVATE_KEY>" and "<ETHEREUM_NODE_PROVIDER_HERE>" with the appropriate values from earlier sections. Once you have your server running, go ahead and fund that wallet with the assets of your choice, plus some ETH for transaction costs and you're good to go. Here are a few screenshots of cURL requests to localhost:
Listing all supported Compound assets via API

Minting Compound cETH via API

Checking your compound balance via API

Deploying

So for our guide, we won't deploy this app to production, but we highly recommend this Digital Ocean tutorial on deploying flask apps to production and this other tutorial on securing apps with Lets Encrypt, the free SSL provider for the web.

Want us to get this live and maintain it for you?
If you'd like to set up a hosted version of this, we're currently testing a beta, please reach out to [email protected]

Related articles 12

How to Interact with Uniswap using Javascript
Apr 12, 2022

Uniswap is one of the most discussed and important projects in the DeFi space. It’s a pretty popular project for many reasons - in this guide, we will learn how to interact with the Uniswap smart contracts using a JavaScript library called

Continue reading
How to setup a Chainlink node
Apr 12, 2022

Smart-contracts are the heart and soul of all the development happening on the Ethereum blockchain, and as more and more people develop on Ethereum, smart contracts are becoming more complex.Sometimes a smart contract wants information about the real world, like...

Continue reading
How to Access Bitcoin Mempool
Apr 12, 2022

Bitcoin is the father of blockchain technology. With Bitcoin started a new era of blockchain and decentralization. Bitcoin enabled everyone to make the peer-to-peer transactions they enjoy today; this guide will teach you how to get these transactions from the Bitcoin...

Continue reading
How to Make a Flash Loan using Aave
Dec 27, 2021

Aave, previously known as ETHLender, has catapulted to the forefront of the DeFi space. Aave was the first in the space to come up with the idea of a Flash Loan. Before flash loans, you would have to stake an over-collateralized...

Continue reading
Cómo realizar un Préstamo Flash en Aave
Jan 10, 2022

Aave, anteriormente conocido como ETHLender, se ha catapultado hacia la delantera en el espacio DeFi. Aave fue el primero de todos en aparecer con la idea de los Prestamos Flash. Antes de los Préstamos Flash, tenías que tener...

Continue reading
Interacting with 0x API using JavaScript
Mar 23, 2022

We have seen tremendous growth in trade volume in DEXs. With many of these coming to the market, it is tough to decide which DEX to choose when you want to swap your token for another. That's where 0x and its APIs come into the picture. It helps us to fetch the DEX with...

Continue reading
How to access Ethereum Mempool
Apr 12, 2022

On Ethereum, when a transaction is sent, before being added to a block, it resides in what is called a Mempool. To receive information about this transaction, the Mempool must be queried. This guide will demonstrate how to query a node’s mempool using QuickNode Ethereum...

Continue reading
How to Listen For Newly Minted Tokens on PancakeSwap
Apr 12, 2022

Hello reader! Today we will be diving into details about making a bot that buys newly minted tokens on the PancakeSwap DEX. Today may be your first time making a trading bot or using the BSC network, and that is okay! By the time you have read through and completed this...

Continue reading
How to Swap Tokens on Uniswap with Ethers.js
Apr 12, 2022

Not all users interact via the front-end UI when trading tokens on a decentralized exchange. Some users (or entities) trade programmatically via a smart contract or server-side scripts. This guide will demonstrate how to swap tokens on Uniswap using Javascript and the...

Continue reading