Build a DeFi Yield Farming dApp Using Chainlink Price Feeds

Learn how to build a DeFi application that rewards with your own ERC20 based on Chainlink decentralized oracles.

Introduction

DeFi, yield farming, staking, and governance tokens are all topics receiving a lot more attention due to the massive growth of DeFi. They are relatively simple to spin up as well, and this blog will show you how to build a DeFi yield farming dApp in 10 minutes using our DeFi-Chainlink repo as a starting point.

This project was inspired by Gregory from Dapp University, refer to the following links if you want to check out his project repo and video tutorial.

What is Yield Farming?

Yield farming (or liquidity mining) is when a user supplies liquidity to a protocol in order to gain passive income on their capital in the form of fees, interest, and/or incentives. For example, providing liquidity to a decentralized lending protocol (e.g. Aave) or Automated Market Maker-based decentralized exchange (e.g. Uniswap) in exchange for potential rewards. Yield farming aims to solve the chicken and egg problem around new platforms not having liquidity because there are no users and not having users because there is no liquidity, by creating financial incentives for early liquidity providers.

Protocols incentivize users to provide liquidity by offering them interest on their staked capital, similar to a savings account, and/or allowing them to earn a portion of the platform’s fees. More recently, it’s become popular for platforms to launch their own native governance tokens and use those tokens to fund an initial incentive period where extra rewards are distributed to early liquidity providers. Again, we could liken this to savings accounts, where banks offer cash rewards to new users simply for signing up and depositing funds. This form of yield farming is relatively new and its lasting utility/benefit is still being debated. We don’t aim to solve that debate today, rather present a guide on how the developer community can build dApps that incentivize liquidity providers and generate staking rewards via governance tokens.

What This dApp Does

In this tutorial, we will build a dApp that allows users to stake any supported ERC20 token, as well as automate the issuance of rewards to stakers (liquidity providers) in the form of governance tokens. The yield farming token in this tutorial is a simple ERC20 contract and doesn’t have any of the voting functionality implemented, therefore it has no real usage other than providing this base example.

The amount of tokens that is received by users is an important component of the dApp. We don’t want to send a user some arbitrary amount of tokens, we want to send them token rewards that are proportional to the amount of funds they have staked on the platform. In order to do this, the current market value of the staked asset is needed, which we fetch by using Chainlink’s Decentralized Price Feeds.

Getting price data from decentralized oracles like Chainlink offers a robust way for us to compare the value of all the tokens staked on the platform. We can easily convert the value of any ERC20 token to it’s ETH value in order to calculate the total value of all the tokens staked.

While we don’t include it in this demo, once a user stakes on the platform, you can proceed to use additional money legos to earn further interest on their assets. For example, you could then stake their tokens on platforms like Aave or Synthetix.

Quickstart

Requirements:

Yarn

Nodejs

Truffle

An ETH wallet with a mnemonic (like metamask), and set the MNEMONIC as an environment variable

An RPC_URL from a node provider like [Infura](https://infura.io/) or use your own node. And set an RPC_URL environment variable.

Clone the repo:

git clone https://github.com/PatrickAlphaC/chainlink_defi

cd chainlink_defi

Install the dependencies

yarn 

Deploy your smart contracts to the Kovan testnet, then start the front-end (need to be o

truffle migrate --network live --reset

yarn start

You should see a window that looks like this:

A diagram showing the UI of a DApp Token Farm.

We can now stake some coins on the platform. First, we will be asked to approve, then we can actually send our smart contract the ERC20 token. You’ll need some testnet LINK in your wallet to get started, which you can get Kovan Link here.

A diagram showing the UI of sending a transaction.

Once you hit approve twice, the transactions will begin. After they complete, you can refresh the page and you’ll see your updated staking balance.

A diagram showing the UI of a DApp Token Farm.

Then, we can go back to our terminal, and issue some reward tokens. We can find the current LINK / ETH value here. Our platform should give us an amount of DAPP token equal to the value of all our ERC20s (denominated in ETH). At the time of writing, 1 LINK = 0.03348 ETH, so our platform should receive 0.3348 DAPP. We can issue the tokens with:

truffle exec scripts/issue-token.js --network live

This will run our script to issue the tokens, and you’ll see the amount of DAPP token you have received as a reward.

A diagram showing the UI of a DApp Token Farm.

You can then even switch tokens by clicking the appropriate button. The repo defaults to 3 ERC20s on the Kovan network. LINK, FAU, and DappToken (deployed by you!).

How the Project Works

There are 2 main contracts that do the heavy lifting for us here, and they are each wrapped in a react front end.

DappToken.sol
TokenFarm.sol

_If you’re new to truffle and wondering what the Migrations file is for, be sure to check out the truffle documentation.

Let’s start by looking at the DappToken.sol since it’s the easiest, as it’s just a simple ERC20 token.

We can deploy an ERC20 token with almost no code:

pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract DappToken is ERC20 {
   constructor() public ERC20("DApp Token", "DAPP") {
       _mint(msg.sender, 1000000000000000000000000);
   }
}

We install the openzepplin package and we can use that as our framework for the contract. The ERC20.sol imported contains all the functionality for our DappToken to work as an ERC20. We give it a name and symbol in the constructor, and then deploy it by minting an initial supply. For our demo, we used 1,000,000 DAPP. (Remember, we want to add 18 0s to the end because solidity/Ethereum doesn’t work with decimals).

When we deploy our contracts, we send all 1,000,000 DAPP tokens to our TokenFarm smart contract, so it’s the sole owner of who gets rewarded for using the platform. That is defined in our migrations file 3_deploy_tokenfarm.js.

const tokenFarm = await TokenFarm.deployed();
await dappToken.transfer(tokenFarm.address, "1000000000000000000000000");

The tokenfarm.sol is our more important file, and has all the logic for staking, unstaking, and getting user balances. To track the staking balance of all users and all their ERC20 tokens, we map user addresses to a mapping of token addresses and amounts, and call it stakingBalance. We also keep track of:

  1. A mapping of how many unique tokens users have staked
  2. A mapping of tokens -> Ethereum price feed for that token
  3. A list of the tokens allowed on our platform
  4. A list of users/stakers
mapping(address => mapping(address => uint256)) public stakingBalance;
mapping(address => uint256) public uniqueTokensStaked;
mapping(address => address) public tokenPriceFeedMapping;
address[] allowedTokens;
address[] public stakers;

Users can then call the stakeTokens and unstakeTokens functions to add or subtract from the platform. The more interesting function, however, is the getUserTotalValue function, which uses the decentralized oracles of the Chainlink price feeds to obtain reliable pricing data to calculate the total staked value.

function getUserTotalValue(address user) public view returns (uint256) {
    uint256 totalValue = 0;
    if (uniqueTokensStaked[user] > 0) {
        for (
            uint256 allowedTokensIndex = 0;
            allowedTokensIndex < allowedTokens.length;
            allowedTokensIndex++
        ) {
            totalValue =
                totalValue +
                getUserStakingBalanceEthValue(
                    user,
                    allowedTokens[allowedTokensIndex]
                );
        }
    }
    return totalValue;
}

The getUserStakingBalanceEthValue gets the individual value of 1 user with 1 of their staked tokens:

function getUserStakingBalanceEthValue(address user, address token)
    public
    view
    returns (uint256)
{
    if (uniqueTokensStaked[user] <= 0) {
        return 0;
    }
    return
        (stakingBalance[token][user] * getTokenEthPrice(token)) / (10**18);
}

And then the getTokenEthPrice is where we make the call to the Chainlink price feeds.

function getTokenEthPrice(address token) public view returns (uint256) {
    address priceFeedAddress = tokenPriceFeedMapping[token];
    AggregatorV3Interface priceFeed = AggregatorV3Interface(
        priceFeedAddress
    );
    (
        uint80 roundID,
        int256 price,
        uint256 startedAt,
        uint256 timeStamp,
        uint80 answeredInRound
    ) = priceFeed.latestRoundData();
    return uint256(price);
}

You’ll have to add the mapping of the token -> Eth price feed for the token, in order to get the correct value.

Feel free to fork this repo, make PRs to it, or anything you like! It’s a great jumping off point for smart contract developers waiting to build their own projects with staking reward functionality.

And now you have a DeFi project powered by the decentralized oracles of Chainlink!

Enter Your Project To Win

If you learned something new here, why not try your skills at the Chainlink Hackathon? We are awarding over $50k of prizes and we highly recommend you take part. Defi is a massive focus of the hackathon, and this could be your starting point.

If you’re reading this after the hackathon, be sure to stay up to date on the latest Chainlink events by joining the community on TwitterDiscordYouTube, or Reddit, and hashtagging your repos with #chainlink and #ChainlinkEA.

If you’re developing a product that could benefit from Chainlink oracles or would like to assist in the open-source development of the Chainlink network, visit the developer documentation or join the technical discussion on Discord.

Need Integration Support?
Talk to an expert
Faucets
Get testnet tokens
Read the Docs
Technical documentation