Search
⌃K

How To Use Truffle and Ganache to Create DeFi App

Use Truffle and Ganache to create DeFi App

🧭 Table of contents

📰 Overview

​Truffle is a blockchain development environment, which you can use to create and test smart contracts by leveraging an Ethereum Virtual Machine. Ganache is a tool to create a local blockchain for testing your smart contracts. It simulates all the features of a real blockchain network but costs you nothing to deploy and test your code.

What you will learn

In this tutorial, we will build a DeFi Application with Solidity where users can deposit an XRC20 token to the smart contract and it will mint and transfer Farm Tokens to them. The users can later withdraw their XRC20 tokens by burning their Farm Token with a smart contract, and the XRC20 tokens will be transferred back to them.

What you will do

  • Install and setup Truffle and Ganache
  • Create a Truffle project
  • Create an XRC20 Token
  • Compile an XRC20 Token
  • Deploy an XRC20 Token
  • Create FarmToken Smart Contract

📰 About DeFi

Decentralized finance, or DeFi, is a term which describes blockchain-based financial services. DeFi provides virtually any financial service on a blockchain in a permissionless way. You can borrow, lend, stake, trade, and more using DeFi services.

🚀 Setting up the development environment

There are a few technical requirements before we start. Please install the following:
Once we have those installed, we only need one command to install Truffle:
npm install -g truffle
To verify that Truffle is installed properly, type truffle version on a terminal. You should see something like:
Truffle v5.5.27 (core: 5.5.27)
Ganache v7.4.0
Solidity v0.5.16 (solc-js)
Node v16.16.0
Web3.js v1.7.4
If you see an error instead, make sure that your npm modules are added to your path.

⚒ Starting a new Truffle Project

You will start by setting up tour folder. In this example, we are creating a project called FarmToken. You can create a new FarmToken folder by running on the following on terminal:
mkdir FarmToken && cd FarmToken
Then run truffle init. If Truffle is correctly installed on your local environment, you should see the following message:
Starting init...
================
​
> Copying project files to /home/your/path/to/FarmToken
​
Init successful, sweet!
​
Try these scaffold commands to get started:
$ truffle create contract YourContractName # scaffold a contract
$ truffle create test YourTestName # scaffold a test
​
http://trufflesuite.com/docs
Your folder files will look like this:
Step 01

⚒ Configuring XDC Mainnet and Apothem Testnet on Truffle

In order to get started deploying new contracts on XDC Mainnet and/or Apothem, you'll need to install two new dependencies that will be used in the truffle-config.js file. These dependencies are @truffle/hdwallet-provider and dotenv. First, choose your preferred package manager. In this example we are using yarn but you can also use npm.
If you never used yarn before, you might need to install it first. ‼️You can skip this step if you already have yarn installed‼️
npm install --global yarn
Initialize your package manager on your folder and install the required dependencies:
yarn init -y
yarn add @truffle/hdwallet-provider dotenv
You will also need a 24-Word Mnemonic Phrase. To configure your wallet, create a new .env file and write your mnemonic by running:
touch .env
echo MNEMONIC=arm derive cupboard decade course garlic journey blast tribe describe curve obey >> .env
Remember to change the 24-Word Mnemonic above for your own mnemonic. The contents of your .env file should read as follow:
MNEMONIC=arm derive cupboard decade course garlic journey blast tribe describe curve obey
🚨 Do not use the mnemonic in the example above in production or you can risk losing your assets and/or the ownership of your smart contracts! 🚨
Lastly, you can configure the truffle-config.js file for both Apothem and XinFin Networks by writting:
require('dotenv').config();
const { MNEMONIC } = process.env;
const HDWalletProvider = require('@truffle/hdwallet-provider');
​
module.exports = {
​
networks: {
​
xinfin: {
provider: () => new HDWalletProvider(
MNEMONIC,
'https://erpc.xinfin.network'),
network_id: 50,
gasLimit: 6721975,
confirmation: 2,
},
​
apothem: {
provider: () => new HDWalletProvider(
MNEMONIC,
'https://erpc.apothem.network'),
network_id: 51,
gasLimit: 6721975,
confirmation: 2,
}
},
​
mocha: {
},
​
compilers: {
solc: {
version: "0.8.16",
}
},
};

⚒ Adding Testnet XDC to Development Wallet

It is possible to list all XDC addresses bound to your mnemonic on truffle by accessing the truffle console:
truffle console --network xinfin
Once the truffle console CLI opens, you can run:
truffle(xinfin)> accounts
And the console should log all accounts bound to your mnemonic phrase as follow:
[
'0xA4e66f4Cc17752f331eaC6A20C00756156719519',
'0x0431d52FE37F3839895018272dfa3bA189fcE07E',
'0x11A6D9727c16064950473a4c8A92dC294190f7fF',
'0x4464DDF9969E9a8e5CfF02E3706AEB4ccA92A314',
'0xFa73bE6AA126DEC47ce14a22B7BAaF8BAFaB59Fb',
'0xEdFFc4e7476f05f43cA3e6f5784349dE6E6373D5',
'0x07795c732Bb013165FADCE64B884bf9971Bf9636',
'0x5dF551A53bEaAB8bb2307eF459aA5AAFbb5F73cc',
'0x910435b01e6Aa66dE22769062998F6AE98566f23',
'0x573b009b2dE9A95531f82DA10BB0D793050329d2'
]
These accounts use the Ethereum standard format starting with 0x, but we can simply switch 0x for xdc. By default, the deployment account is the first account from the list above: xdcA4e66f4Cc17752f331eaC6A20C00756156719519.
With this account in hand, you can head to the Apothem Faucet and claim some TXDC for development purposes:
Step 02

💵 Creating our first DeFi App

The source code for the DeFi App used in this tutorial is available here: FarmToken Contract and MyToken Contract.

💵 Create and Deploy XRC20 Token

Before creating FarmToken contract, you should first create a new XRC20 token. This is going to be a quick guide, but if you want an in-depth understanding of the XRC20 standard, please visit the following tutorials:
We will need to install OpenZeppelin in order to not writing all code by ourself
yarn add @openzeppelin/contracts
or using npm
npm install @openzeppelin/contracts
Now create a file called MyToken.sol in contracts folder with following content:
pragma solidity ^0.8.0;
​
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
​
contract XRC20Token is ERC20 {
constructor(string memory name, string memory symbol) public ERC20(name, symbol){
_mint(msg.sender, 1000e18);
}
}

💵 Create FarmToken contract

Create another smart contract called FarmToken.sol.
pragma solidity ^0.8.0;
​
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
​
contract FarmToken is ERC20 {
using Address for address;
using SafeMath for uint256; // As of Solidity v0.8.0, mathematical operations can be done safely without the need for SafeMath
using SafeERC20 for IERC20;
​
IERC20 public token;
​
constructor(address _token) public ERC20("FarmToken", "FRM") {
token = IERC20(_token);
}
​
function balance() public view returns (uint256) {
return token.balanceOf(address(this));
}
​
function deposit(uint256 _amount) public {
// Amount must be greater than zero
require(_amount > 0, "amount cannot be 0");
​
// Transfer MyToken to smart contract
token.safeTransferFrom(msg.sender, address(this), _amount);
​
// Mint FarmToken to msg sender
_mint(msg.sender, _amount);
}
​
function withdraw(uint256 _amount) public {
// Burn FarmTokens from msg sender
_burn(msg.sender, _amount);
​
// Transfer MyTokens from this smart contract to msg sender
token.safeTransfer(msg.sender, _amount);
}
}
FarmToken is a XRC20 token, but with 3 custom commands. balance shows how much of MyToken our FarmToken holds. deposit transfers MyToken from our account to FarmToken and gives us respective amount of FarmToken. withdraw burns our FarmToken and returns back MyToken to us. The latter prevents you from having both same amounts of FarmToken and MyToken, as you can hold only one of them.

💵 Compiling and Deploying

We can compile our smart contracts (FarmToken.sol and MyToken.sol) by running:
truffle compile
If everything is correctly configured and there are no errors, you should see the following message on your console:
Compiling your contracts...
===========================
> Compiling ./contracts/FarmToken.sol
> Compiling ./contracts/MyToken.sol
> Compiling @openzeppelin/contracts/token/ERC20/ERC20.sol
> Compiling @openzeppelin/contracts/token/ERC20/IERC20.sol
> Compiling @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
> Compiling @openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol
> Compiling @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
> Compiling @openzeppelin/contracts/utils/Address.sol
> Compiling @openzeppelin/contracts/utils/Context.sol
> Compiling @openzeppelin/contracts/utils/math/SafeMath.sol
...
And your folder should look like this:
folder_struct
In order to deploy our newly-compiled contract artifacts to the blockchain, you'll need to create a deployment script into the migrations folder:
touch ./migrations/1_defi_migration.js
And write the following migration script to the 1_defi_migration.js file:
const XRC20Token = artifacts.require("XRC20Token");
const FarmToken = artifacts.require("FarmToken")
​
const NAME = "MyToken";
const SYMBOL = "MTK";
​
module.exports = async function (deployer) {
// Deploy XRC20Token
await deployer.deploy(XRC20Token, NAME, SYMBOL);
const myToken = await XRC20Token.deployed()
​
// Deploy FarmToken
await deployer.deploy(FarmToken, myToken.address)
const farmToken = await FarmToken.deployed()
​
console.log('FarmToken deployed:', farmToken.address)
}
Now, you can run it on a ganache local network. First, open the Ganache app and choose the Quickstart option to start your local blockchain network.
ganache_home
Next, deploy contracts by running:
truffle migrate
If everything is fine, you'll have an output like this:
Starting migrations...
======================
> Network name: 'ganache'
> Network id: 5777
> Block gas limit: 6721975 (0x6691b7)
​
​
1_defi_migration.js
===================
​
Deploying 'XRC20Token'
----------------------
> transaction hash: 0x7c05e8cc683f2125a94e76e037191ddc792e4a1fbecb750d7f299e3f000a1b9d
> Blocks: 0 Seconds: 0
> contract address: 0xD63Ab469Ebc2acf0332B7d01028d02E3b363f076
> block number: 1
> block timestamp: 1664390940
> account: 0x7EF6aCFd4F0B8B6828Bf25D440Bf46f1Eb28c6A6
> balance: 99.97646966
> gas used: 1176517 (0x11f3c5)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.02353034 ETH
​
​
Deploying 'FarmToken'
---------------------
> transaction hash: 0x3cb3cf3c959ce40cdc79430b9f78edb2d8f44df4eb25ce5cd1e26631e173aa49
> Blocks: 0 Seconds: 0
> contract address: 0xDFe652110afBc0f32060De436F24eDD8E856A82d
> block number: 2
> block timestamp: 1664390941
> account: 0x7EF6aCFd4F0B8B6828Bf25D440Bf46f1Eb28c6A6
> balance: 99.93702634
> gas used: 1972166 (0x1e17c6)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.03944332 ETH
​
FarmToken deployed: 0xDFe652110afBc0f32060De436F24eDD8E856A82d
> Saving artifacts
-------------------------------------
> Total cost: 0.06297366 ETH
​
Summary
=======
> Total deployments: 2
> Final cost: 0.06297366 ETH
This is what you will see from Ganache app.
ganache_0
​
ganache_1

💵 Testing FarmToken contract

Create a scripts folder where we will put our files for testing.
mkdir scripts
Next, you can create your first script to check balance of FarmToken. Create file getMyTokenBalance.js and paste:
const MyToken = artifacts.require("XRC20Token")
const FarmToken = artifacts.require("FarmToken")
​
module.exports = async function (callback) {
myToken = await MyToken.deployed()
farmToken = await FarmToken.deployed()
balance = await myToken.balanceOf(farmToken.address)
console.log(web3.utils.fromWei(balance.toString()))
callback()
}
After that, you can run a script to check our FarmToken balance: truffle exec ./scripts/getMyTokenBalance.js
Using network 'ganache'.
​
0
We now need a script to deposit our tokens in the FarmToken contract. Create a file depositMyToken.js
const MyToken = artifacts.require("XRC20Token")
const FarmToken = artifacts.require("FarmToken")
​
module.exports = async function (callback) {
const accounts = await new web3.eth.getAccounts()
const myToken = await MyToken.deployed()
const farmToken = await FarmToken.deployed()
​
// Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom.
// This is zero by default.
const allowanceBefore = await myToken.allowance(
accounts[0],
farmToken.address
)
console.log(
"Amount of MyToken FarmToken is allowed to transfer on our behalf Before: " +
allowanceBefore.toString()
)
​
// In order to allow the Smart Contract to transfer to MyToken (ERC-20) on the accounts[0] behalf,
// we must explicitly allow it.
// We allow farmToken to transfer x amount of MyToken on our behalf
await myToken.approve(farmToken.address, web3.utils.toWei("100", "ether"))
​
// Validate that the farmToken can now move x amount of MyToken on our behalf
const allowanceAfter = await myToken.allowance(accounts[0], farmToken.address)
console.log(
"Amount of MyToken FarmToken is allowed to transfer on our behalf After: " +
allowanceAfter.toString()
)
​
// Verify accounts[0] and farmToken balance of MyToken before and after the transfer
balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])
balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)
console.log("*** My Token ***")
console.log(
"Balance MyToken Before accounts[0] " +
web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())
)
console.log(
"Balance MyToken Before TokenFarm " +
web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())
)
​
console.log("*** Farm Token ***")
balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])
balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)
console.log(
"Balance FarmToken Before accounts[0] " +
web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())
)
console.log(
"Balance FarmToken Before TokenFarm " +
web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())
)
// Call Deposit function from FarmToken
console.log("Call Deposit Function")
await farmToken.deposit(web3.utils.toWei("100", "ether"))
console.log("*** My Token ***")
balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])
balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)
console.log(
"Balance MyToken After accounts[0] " +
web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())
)
console.log(
"Balance MyToken After TokenFarm " +
web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())
)
​
console.log("*** Farm Token ***")
balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])
balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)
console.log(
"Balance FarmToken After accounts[0] " +
web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())
)
console.log(
"Balance FarmToken After TokenFarm " +
web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())
)
​
// End function
callback()
}
It's time to deposit some tokens.
truffle exec ./scripts/depositMyToken.js
Using network 'ganache'.
​
Amount of MyToken FarmToken is allowed to transfer on our behalf Before: 0
Amount of MyToken FarmToken is allowed to transfer on our behalf After: 100000000000000000000
*** My Token ***
Balance MyToken Before accounts[0] 1000
Balance MyToken Before TokenFarm 0
*** Farm Token ***
Balance FarmToken Before accounts[0] 0
Balance FarmToken Before TokenFarm 0
Call Deposit Function
*** My Token ***
Balance MyToken After accounts[0] 900
Balance MyToken After TokenFarm 100
*** Farm Token ***
Balance FarmToken After accounts[0] 100
Balance FarmToken After TokenFarm 0
Check your balance again using truffle exec ./scripts/getMyTokenBalance.js
Using network 'ganache'.
​
100
Everything should be working as intended.
Finally, you can check if we can withdraw your tokens by creating a file called withdrawMyToken.js:
const MyToken = artifacts.require("XRC20Token")
const FarmToken = artifacts.require("FarmToken")
​
module.exports = async function (callback) {
const accounts = await new web3.eth.getAccounts()
const myToken = await MyToken.deployed()
const farmToken = await FarmToken.deployed()
​
// Verify accounts[0] and farmToken balance of MyToken before and after the transfer
balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])
balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)
console.log("*** My Token ***")
console.log(
"Balance MyToken Before accounts[0] " +
web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())
)
console.log(
"Balance MyToken Before TokenFarm " +
web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())
)
​
console.log("*** Farm Token ***")
balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])
balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)
console.log(
"Balance FarmToken Before accounts[0] " +
web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())
)
console.log(
"Balance FarmToken Before TokenFarm " +
web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())
)
​
// Call Deposit function from FarmToken
console.log("Call Withdraw Function")
await farmToken.withdraw(web3.utils.toWei("100", "ether"))
​
console.log("*** My Token ***")
balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])
balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)
console.log(
"Balance MyToken After accounts[0] " +
web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())
)
console.log(
"Balance MyToken After TokenFarm " +
web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())
)
​
console.log("*** Farm Token ***")
balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])
balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)
console.log(
"Balance FarmToken After accounts[0] " +
web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())
)
console.log(
"Balance FarmToken After TokenFarm " +
web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())
)
​
// End function
callback()
}
Now, you can run it:
truffle exec ./scripts/withdrawMyToken.js
Using network 'ganache'.
​
*** My Token ***
Balance MyToken Before accounts[0] 900
Balance MyToken Before TokenFarm 100
*** Farm Token ***
Balance FarmToken Before accounts[0] 100
Balance FarmToken Before TokenFarm 0
Call Withdraw Function
*** My Token ***
Balance MyToken After accounts[0] 1000
Balance MyToken After TokenFarm 0
*** Farm Token ***
Balance FarmToken After accounts[0] 0
Balance FarmToken After TokenFarm 0

💵 Deploying on a live network

Finally, you can deploy everything on a XDC Apothem Testnet.
truffle migrate --network apothem
If you want to deploy it on XDC mainet, change network to xinfin.
truffle migrate --network xinfin

🔍 Veryfing Contracts on the Block Explorer

Once you have successfully deployed your smart contract to the blockchain, you might want to verify your contract on XinFin Block Explorer.
Because our contract consists of multiple files, we first need to flatten our contract. For that, install truffle-flattener.
yarn add truffle-flattener -g
Or using npm
npm install truffle-flattener -g
Now, flatten your contract:
truffle-flattener contracts/MyToken.sol > MyToken_flat.sol
Next, open MyToken_flat.sol and remove every line which starts with // SPDX-License-Identifier except the first one. This is important, as block explorer does not accept contracts with mutliple license definition.
Now, lets grab the MyToken.sol address from the deploying step: this address is in the Ethereum standard but we can simply swap the 0x prefix for xdc and search for our newly deployed contract on XinFin Block Explorer:
Verify 01
Click on the Verify And Publish Option.
You will be redirected to the contract verification page where we need to fill out:
  • Contract Name: XRC20Token
  • Compiler: Check your truffle-config.js file for Compiler Version
  • Contract Code: Just paste everything from your MyToken_flat.sol file
Once everything is filled out, press Submit!
Verify 02
If everything was filled out correctly, your contract page on the block explorer should display a new tab called Contract:
Verify 03
Repeat those steps if you want to verify FarmToken.sol, except this time Contract Name will be FarmToken and source code will be from FarmToken_flat.sol
​
For more information about Truffle Suite, Please Visit Truffle Suite Documentation. For more information about XDC Network, Please Visit XDC Community Documentation on GitBook. Resources used during the deployment of the DeFi App can be found at FarmToken Contract Folder.