For those who have no idea what the title says, I offer my apology here. Finance by itself is a deep topic with many trade-offs, similar to the world of engineering. According to a greate economists Thomas Sowell
There are no solutions, only trade-offs
In the DeFi (Decentralized Finance) sense, as much as we all love the high interest rates, the trade-offs aren't always clear in this nascent industry. The underlying concept is really simple and sound. It goes something like this:
- For borrowers of USD: They deposit their crypto-currencies as a collateral to borrow USD, and pay interests
- For lenders of USD: They deposit their USD to lend to borrowers, and charge interests
High interest rates and a sound model feel super good. Now, let's talk about the trade-offs, well, they are many of them. This post by Ameen did a better job than I can ever do in laying out the risks. I highly recommend reading it fully if you are interested in DeFi and dreaming about having a passive income to live off in Hawaii. This post is all about one of the risks, known as bank run risk.
So what's it exactly? In short, it's when there isn't enough USD in the system for lenders to withdraw the desired amount fully. They will need to wait for the borrowers to repay or new lenders to inject fresh USD into the system. This can happen with traditional banks and it can happen in DeFi. For lenders, the best way to protect themselves is keeping a close eye on the available amount of USD in the system. This is where DeFi kicks the ass of the traditional banking system. EVERYTHING is public, programmer friendly and well defined. I don't know about you, but I'm a better reader in code than legal or finance gribbish. Again, the trade-off is without centralization, it's really up to us to manage our own risks.
Now, I hope you have some basic idea on how this post is going to be about. Let's dive into the technical part of it.
This post focuses on one particular lending protocol, known as Compound, and one market only, which is USD Coin. Compound offers the highest liquidity (most available USD Coins) at the time of this writing, and their smart contracts are audited and verified by 3rd party companies. It also scores the highest in DeFiS Score at the time of this writing.
Same goes for USD Coin, aka USDC, as the stablecoin. It's launched by Coinbase and Circle that have a good reputable in the industry. Personally I feel they offer the best fiat currency on-ramp / off-ramp experience.
Please DYOR in picking a lending protocol and stablecoin. This post is purely for educational purpose on blockchain technology, not investment.
All I wanted is a simple alert that sends me an email when the number available USDC is low, or when more than 65% of deposited USDCs are borrowed, leaving only 35% available for lenders to withdraw. I firmly believe the best code is no code at all. Like I promised in the title, let's see if we can get away with as few lines as possible.
Simple, we only need 2 packages
web3~=5.0
requests~=2.0
The web3 package is for communicating with smart contracts on the Ethereum blockchain, so we can obtain the numbers needed for the alert. This post only uses a small part of it. If you want to learn more about blockchain programming, I highly recommended this post for a start.
Now register a free account on Infura. Trust me, it's a lot easier to have someone to run a full node and provide the API for you. Running a full Ethereum node locally isn't super fun. Once you have created a project in Infura you will be given a project ID that can be used to create a w3 object.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/<YOUR PROJECT ID>'))
To talk to a smart contract you will need its address. Here is how to get it
CONTRACT_ADDRESS = '0x39aa39c021dfbae8fac545936693ac917d5e7563'
checksummed_address = w3.toChecksumAddress(CONTRACT_ADDRESS)
The contract address points to the Compound USDC market. This is the contract we need to interact with.
Okay, time to set up a contract object and access its data. To do it, we will need something call ABI that specifies what a contract can do. Getting it as as easy as making a request, and passing it on.
import requests
r = requests.get(f'https://api.etherscan.io/api?module=contract&action=getabi&address={CONTRACT_ADDRESS}')
contract = w3.eth.contract(address=checksummed_address, abi=r.json()['result'])
Now we are done with all the set up. Let's start the party by getting all the needed numbers.
liquidity = contract.functions.getCash().call()
total_borrow = contract.functions.totalBorrows().call()
total_supply = liquidity + total_borrow
- liquidity: The amount of available USDCs left
- total_borrow: The amount of USDC borrowed
- total_supply: Currently available plus lent out USDCs
I used Mailgun, again, for the sake of simplicity. Sending out an email with one post call is good enough for me. It's also free to use for the purpose of this post.
if (total_borrow / total_supply) > 0.65 or liquidity < 10000000000000:
r = requests.post(
'https://api.mailgun.net/v3/<YOUR DOMAIN>/messages',
auth=('api', '<YOUR API KEY>'),
data={'from': 'DeFi Alerts <[email protected]>',
'to': ['<YOUR EMAIL ADDRESS>'],
'subject': "High utilization or low liquidity",
'text': f"Current rate {total_borrow / total_supply}\n" +
f"Current liquidity {liquidity / 1000000}"})
For testing purpose I set the threshold to 30% to trigger an alert. This is what you will get in the email.
Now all you need is to deploy the code on a linux machine and run it with crontab. I will leave this part to you. If you need any help please hit me up on Twitter.
Here is the full Python script. It turned out to be only 28 lines :D
from web3 import Web3
import requests
CONTRACT_ADDRESS = '0x39aa39c021dfbae8fac545936693ac917d5e7563'
def main():
w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/<YOUR PROJECT ID>'))
r = requests.get(f'https://api.etherscan.io/api?module=contract&action=getabi&address={CONTRACT_ADDRESS}')
checksummed_address = w3.toChecksumAddress(CONTRACT_ADDRESS)
contract = w3.eth.contract(address=checksummed_address, abi=r.json()['result'])
liquidity = contract.functions.getCash().call()
total_borrow = contract.functions.totalBorrows().call()
total_supply = liquidity + total_borrow
if (total_borrow / total_supply) > 0.65 or liquidity < 10000000000000:
r = requests.post(
'https://api.mailgun.net/v3/<YOUR DOMAIN>/messages',
auth=('api', '<YOUR API KEY>'),
data={'from': 'DeFi Alerts <[email protected]>',
'to': ['<YOUR EMAIL ADDRESS>'],
'subject': "High utilization or low liquidity",
'text': f"Current rate {total_borrow / total_supply}\n" +
f"Current liquidity {liquidity / 1000000}"})
if __name__ == '__main__':
main()