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:
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:
mkdirFarmToken&&cdFarmToken
Then run truffle init. If Truffle is correctly installed on your local environment, you should see the following message:
Startinginit...================> Copying project files to /home/your/path/to/FarmTokenInitsuccessful,sweet!Trythesescaffoldcommandstogetstarted:$trufflecreatecontractYourContractName# scaffold a contract$trufflecreatetestYourTestName# scaffold a testhttp://trufflesuite.com/docs
Your folder files will look like this:
⚒ 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:
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:
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:
Create another smart contract called FarmToken.sol.
pragmasolidity ^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";contractFarmTokenisERC20 {usingAddressforaddress;usingSafeMathforuint256; // As of Solidity v0.8.0, mathematical operations can be done safely without the need for SafeMathusingSafeERC20forIERC20; IERC20 public token;constructor(address_token) publicERC20("FarmToken", "FRM") { token =IERC20(_token); }functionbalance() publicviewreturns (uint256) {return token.balanceOf(address(this)); }functiondeposit(uint256_amount) public {// Amount must be greater than zerorequire(_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); }functionwithdraw(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:
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
constMyToken=artifacts.require("XRC20Token")constFarmToken=artifacts.require("FarmToken")module.exports=asyncfunction (callback) {constaccounts=awaitnewweb3.eth.getAccounts()constmyToken=awaitMyToken.deployed()constfarmToken=awaitFarmToken.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.constallowanceBefore=awaitmyToken.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 behalfawaitmyToken.approve(farmToken.address,web3.utils.toWei("100","ether"))// Validate that the farmToken can now move x amount of MyToken on our behalfconstallowanceAfter=awaitmyToken.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 =awaitmyToken.balanceOf(accounts[0]) balanceMyTokenBeforeFarmToken =awaitmyToken.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 =awaitfarmToken.balanceOf(accounts[0]) balanceFarmTokenBeforeFarmToken =awaitfarmToken.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 FarmTokenconsole.log("Call Deposit Function")awaitfarmToken.deposit(web3.utils.toWei("100","ether"))console.log("*** My Token ***") balanceMyTokenAfterAccounts0 =awaitmyToken.balanceOf(accounts[0]) balanceMyTokenAfterFarmToken =awaitmyToken.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 =awaitfarmToken.balanceOf(accounts[0]) balanceFarmTokenAfterFarmToken =awaitfarmToken.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 functioncallback()}
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:
constMyToken=artifacts.require("XRC20Token")constFarmToken=artifacts.require("FarmToken")module.exports=asyncfunction (callback) {constaccounts=awaitnewweb3.eth.getAccounts()constmyToken=awaitMyToken.deployed()constfarmToken=awaitFarmToken.deployed()// Verify accounts[0] and farmToken balance of MyToken before and after the transfer balanceMyTokenBeforeAccounts0 =awaitmyToken.balanceOf(accounts[0]) balanceMyTokenBeforeFarmToken =awaitmyToken.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 =awaitfarmToken.balanceOf(accounts[0]) balanceFarmTokenBeforeFarmToken =awaitfarmToken.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 FarmTokenconsole.log("Call Withdraw Function")awaitfarmToken.withdraw(web3.utils.toWei("100","ether"))console.log("*** My Token ***") balanceMyTokenAfterAccounts0 =awaitmyToken.balanceOf(accounts[0]) balanceMyTokenAfterFarmToken =awaitmyToken.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 =awaitfarmToken.balanceOf(accounts[0]) balanceFarmTokenAfterFarmToken =awaitfarmToken.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 functioncallback()}
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.
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:
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 yourtruffle-config.jsfile for Compiler Version
Contract Code: Just paste everything from yourMyToken_flat.solfile
Once everything is filled out, press Submit!
If everything was filled out correctly, your contract page on the block explorer should display a new tab called Contract:
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