Create and Launch an ERC20 Vault on Shardeum
In this guide, we're going to learn how to create and deploy a customized decentralized vault smart contract on Shardeum. A vault is a DeFi product that allows users to deposit their tokens. The vault then employs various lending strategies to generate profits, which are later distributed among all the depositors. (Note: Please don't confuse this with Crypto Vault, which is a type of wallet.)
For this tutorial, we'll be using Remix IDE, a popular web-based Solidity IDE.
Vault Smart Contract
The fundamental strategy behind the vault is that when a user deposits ERC20 tokens into the Vault smart contract, we will create shares (which represent the ownership of the tokens that the user has deposited into the vault contract). When a user withdraws a token, we will destroy the corresponding number of shares.
With this strategy in mind, let's begin with the basic structure of the smart contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./IERC20.sol";
contract Vault{
IERC20 public immutable token;
uint public totalSupply;
mapping(address => uint) public balanceOf;
constructor(address _token){
token = IERC20(_token);
}
}
In the code above, we are importing the IERC20 (Interface for ERC20 token, which gives us the functions and events required for the ERC20 token standard).
To get started, create a new file named IERC20.sol in the Remix Workspace and paste the entire contract from OpenZeppelin's IERC20.
Next, we'll include two internal functions: mint() and burn(). The mint() function requires an input for the address where the shares will be created and the amount to be minted.
Likewise, the burn() function needs an input for the address whose shares need to be destroyed.
function _mint(address _to,uint _amount) private{
totalSupply += _amount;
balanceOf[_to] += _amount;
}
function _burn(address from,uint _amount) private{
totalSupply -= _amount;
balanceOf[from] -= _amount;
}
Now, let's create the function to deposit and withdraw from the Vault. In the deposit() function, we handle the case where totalSupply is equal to zero separately because we don't want the function to divide by zero. This function essentially calculates the amount of shares that need to be created based on the number of tokens being deposited.
In the withdraw() function, we perform a similar calculation to determine the number of shares to be destroyed and return the tokens to the user.
function deposit(uint _amount) external {
uint shares;
if(totalSupply== 0){
shares = _amount;
}
else{
shares = _amount*totalSupply /token.balanceOf(address(this));
}
_mint(msg.sender, shares);
token.transferFrom(msg.sender,address(this), _amount);
}
function withdraw(uint _shares) external {
uint amount = (_shares*token.balanceOf(address(this)))/ totalSupply;
_burn(msg.sender, _shares);
token.transfer(msg.sender,amount);
}
Develop and Launch an ERC20 Token
Now, let's create a standard ERC20 token with the name "VAULT" and the token symbol "VLT," which we will use to deposit into our vault.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./IERC20.sol";
contract ERC20 is IERC20 {
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
string public name = "VAULT";
string public symbol = "VLT";
uint8 public decimals = 18;
function transfer(address recipient, uint amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function mint(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
function burn(uint amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
Now, it’s Time to Deploy!
Now that we have all the smart contracts, let's start deploying them on the Shardeum testnet.
Configure your Metamask Wallet with the Shardeum Sphinx testnet and request some testnet SHM from the faucet.
Compile all three smart contracts: ERC20.sol, IERC20.sol, and Vault.sol, in the Solidity Compilation section.
Next, navigate to the Deploy and Run Transactions section in Remix, and switch the Environment from Remix VM to Injected Web3. If your Metamask is set up correctly, you should see 'Custom (8082) Network.' You're all ready to go!
Now, it's time to test the real-world performance of what we just constructed.
Deploy ERC20.sol and copy the token address once it’s deployed.
Select Vault.sol and paste the previously copied address beside the deploy button and deploy the contract.
Next, mint 100 VLT tokens from the ERC20 token using the mint function.
Paste your wallet address and the number of tokens beside the approve() function so that we are able to spend it in our Vault contract.
Now, Deposit 100 tokens into the Vault contract using deposit() function.
Now, if you call the balanceOf() function in Vault passing your wallet address, you will see 100 tokens.
For this example, you can send 100 tokens directly to the Vault contract, assuming that the Vault made some profit.
Now, if you invoke the withdraw() function and inspect your balance in your ERC20 token contract, you will observe that you have 200 tokens.
That's all there is to it! We've just established a secure storage, deposited 100 tokens, and eventually withdrew 200 tokens with the assumption of making a profit.
This was a simplified and high-level demonstration of DeFi Vaults. In real-world applications, many more considerations come into play, and an efficient business logic is implemented to generate profit through different trading and lending strategies.
About the author: Sandipan Kundu is the Developer Relations Engineer at Shardeum. He has been an early contributor to the Web3 ecosystem since 2017 and has also contributed to the growth of the Polygon developer relations team in the past.
His main focus has been on building strong developer evangelism programs using hackathons, workshops, technical content, and more to promote and spread the word about Web3 and decentralization.
Author's Social Links:
Twitter: twitter.com/SandipanKundu42
Popular Searches
DAO Guide | Blockchain Beyond Crypto | What is Chainlink | What are Cryptopunks | Advantages and Disadvantages of Decentralization | Peer to Peer Transaction | Web3 Training | Blockchain Proof of Work Vs Proof of Stake | What is DeFi 2.0 | What is Phishing and How to Prevent it | Main Features of Web 3.0 | Blockchain Layers Explained | Cryptocurrency Liquidity Provider | EVM Wallet Address | EVM Blockchains Add EVM Network | Custodial Wallets Vs Non-Custodial Wallets | Decentralized Identifiers | Cryptocurrency Career Opportunities | What is Consortium Blockchain | Major Components of Blockchain