Integrating Chai into your Dapp to earn interest from Dai Savings Rate.
A day after chai (wrapped version of Dai in the DSC contract) was deployed to Mainnet, I received a message from Lev, one of the creators.
Luckily, I was just about to organise my codeup (a meetup for coders to talk about Ethereum coding), so we met a few hours early to actually integrate into Kickback and did code walkthrough during the event.
We had great guests. Not only Lev knew Chai inside out, but we also had Fran (co-creator of rDAI, wrapped version of Compound’s cDAI), Ricardo from Provable (a partner of Eidoo which integrated with Chai within 6 hours of the its release). We also had three people from Monolith (formerly TokenCard, which offers one of the first non-custodial wallet with credit card for crypto off ramp) who offered drinks, food, and the great space to spend almost 4 hours of intensive discussion.
In this blog post, we will try to replicate the conversation of the night by going through the the experimental branch we worked on that night.
- Integrating Chai, the easy way
- Dapp earning interest
- Including Chai into your codebase
- Chai vs rDAI
Integrating Chai, the easy way
If you just simply want to let users stake Chai as a commitment, you actually don’t have to change your smart contract at all. Our smart contract already allows any ERC20 tokens so all you need to do is to let event organiser choose Chai as a default currency.
If I understand correctly the support Eidoo made was to simply allow users to convert Dai to Chai.
Dapp earning interests
What we want to do however is for our dapp to earn interest. To do so, you have to separate interest from Chai and send the only principles back to attendees while sending the interest to a beneficiary, like the event organiser or the dapp creator. Let me show you how it looks like in our test code.
this.register (line 89) is a function that allows you to commit when you RSVP.
When the event organiser (
finalize(line 90), it decides who checked in and who is not.
After that, the participant (in this case only the organiser) can call
withdraw (line 91) his/her portion.
At this moment, the event contract will be empty, but thanks to the power of Chai,
conference.totalDaiBalance() (line 95) shows that it earned something extra, hence
If our smartcontract keeps the interest forever, no one can get hold of the earned interest. In this example, we modified
clear (line 97) function so that the event organiser can withdraw the Dai (after one week cooling period). In reality, we will probably add a beneficiary account which could be either just a multisig owned by Kickback, or it could be to a DAO if we want to let the community decide how to spend that money.
Chai itself is a single solidity file with no dependencies so you can just copy and paste it into your local directory. The problem when testing is that the Chai depends on DSR contracts (the infamous short names
Gem) so the actual Chai contract won’t return any interests unless you also include the rest of MakerDAO’s codebase. In this example, we created a fake Chai called
MyChai.sol which mimics the behavior of Chai. When we put this code into production, we should define the interface of
ChaiInterface.sol and should import that instead of
This fake chai contract shows you which functions you need to call from your contract.
We set an arbitrary 2 % interest rate (line 14). You can send a transaction to
dai() (line 39) to enquire about the latest amount the
usr address owns. It is important to know that this is a state-changing activity hence you cannot just call to get the latest state.
join(dai) (line 26) will convert Dai into Chai, and
draw(wad)(line 32)(wad represents the type of a decimal number with 18 decimals of precision, used for token quantities) will convert chai back to Dai.
Chai has another function called
exit(wad). What is the difference?
The important thing you need to be aware of is that
chai = dai + interest.
If you want to return Dai + interest, then you call
exit(chai). If you want to withdraw the exact amount of Dai you specified when joining (leaving interest part behind), then call
Including Chai into your codebase
AbstractConference.sol which defines generic functions such as
ERC20Conference.sol which implements ERC20 specific functions such as
Let’s see the code diff of
At the constructor, we now pass
chaiAddress. We added
require(chai.daiToken() == token); to make sure that the token address we use is the one Chai references as Dai.
You can see that we call
chai.join at deposit time and
chai.draw at withdraw time. We also created
updateDaiBalance and stores the output into
totalDaiBalance. Bear in mind that this figure includes the amount including interests earned by Chai, which is different from our
Let’s now go to
You can notice that we added
totalDeposits to keep track of the increment and decrement of the staked amount. Previously we were able to track it by just calling
token.balanceOf(address). Now that all the Dai token is converted to Chai and
chai.balanceOf(address) does not necessarily returns the deposit amount, we have to keep track of the balance on our own (we decided to add a new variable called
totalDeposits rather than replacing
totalBalance function for now as changing it will compiler to fail unless we make more changes into the code).
Chai vs rDAI
clear was used to send any leftover of the commitment which have not been withdrawn during the cooling-off period (we are going to add a functionality to send them back rather than the event organiser clear it all).
In this example, however, it is now checking the leftover balance including interests via
updateDaiBalance function and send them back all to the organiser. If we want to add a beneficiary, then we also need to pass
beneficiaryAddress to the constructor. If you want to change the amount or the destination address, you have to code the logic as well.
rDAI provides similar functionalities to Chai but using cDAI of Compound which has different risk profile. The interest rate of Compound is usually higher than DSR but it has additional counter party risk and liquidity risk.
If everyone tries to exchange cDai or rDai for Dai at the same time, there may not enough to do so. You don’t actually lose the money, but your transaction (In our use case
clear ) may fail. This may not be so much of the issue beyond minor inconvenience but some Dapp may have consequence if users cannot withdraw when they want to.
So far I only mentioned the downside of using rDAI (or rather the risk of cDAI), but there are some benefits apart from earning higher interest.
rDAI can simulate the real-time total balance of accumulated DAI value (DAI + interest) without sending transactions. You also do not need to keep track of
totalDeposits at line 79 and 99. In essence, rDAI does the hard work of separating interest and principles.
rDAI also has a notion of Hat which allows you to set the beneficiary addresses and proportion to divides the interest earned. This means I don’t have to code the logic myself.
In the mission of the rToken project, it says that rDAI could support multiple currencies such as ETH 2.0 or USDC so they should be able to create rChai to not only adding different risk profile to cDAI but the Dapps can take advantage of the hat mechanism as well.
As you have seen, the integration of Chai is not so hard. To make it production worthy, we still need to do the following though (we may add this to our list of Gitcoin global communities virtual hackathon bounties).
- Add a beneficiary address.
- Define Chai interface and import it to
ERC20Conferencerather than importing MyChai
DAI*as it now has a dependency on DAI (or do not call chai functions unless
chai.daiToken() == token
- Modify deployment script to point to correct Chai address depending on the deploying network.
More importantly, Chai itself is not audited yet so we have to assess the risk of using unaudited external contracts.
(Thank you very much for Lev and Fran to review my blog post.)