Build and Deploy an ERC20 Vault on Shardeum

I am a senior year, CS engineering student, who's determined to learn and deep dive in the world of latest tech.
In this guide, we are going to learn how to build and deploy a customized decentralized vault smart contract on Shardeum. A Vault is a DeFi product where users can stake their tokens. The vault then implements various lending strategies to make a profit and then distribute the profits amongst all the depositors. (Side note: Don’t mistake the topic for Crypto Vault which is a type of wallet)
For this tutorial, we are going to be using Remix IDE, a popular browser-based Solidity IDE.
Vault Smart Contract
The basic strategy behind the vault is that when a user deposits ERC20 tokens into the Vault smart contract, we will mint shares (this represents the ownership of respective tokens this user has deposited into the vault contract) and when a user withdraws a token, we will burn the respective number of shares.
With the strategy in place, let’s start 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 above code, we are importing the IERC20 (Interface for ERC20 token which provides us the functions and events needed for the ERC20 token standard).
- Create a new file named IERC20.sol in the Remix Workspace and copy the entire contract from Openzeppelin IERC20.
Next, we will add two internal functions mint() and burn(). The mint() function takes input of the address where the shares will be minted and the amount of how much will be minted.
Similarly, the burn() function takes the input of the address whose shares need to be burned.
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 write the function to deposit and withdraw from the Vault. In deposit(), we are handling the case when totalSupply==0 separately because we don’t want the function to divide by zero. This function calculates the amount of shares that need to be minted based on the number of tokens being deposited.
In withdraw(), we are doing a similar calculation for calculating the number of shares to be burned and sending back 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);
}
Create and Deploy an ERC20 Token
Now, let’s create a standard ERC20 token with the name “VAULT” and token symbol “VLT” which we will use to deposit in 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 these on the Shardeum testnet.
- Configure your Metamask Wallet with the Shardeum Sphinx testnet and claim some testnet SHM from the faucet.
- Compile all three smart contracts ERC20.sol, IERC20.sol and Vault.sol from the solidity Compile section.
Next, go to the Deploy and Run transactions section of Remix, and change the Environment from Remix VM to Injected Web3. If your metamask is properly configured, you should see ‘Custom (8082) Network’. Now you are all set!

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 can spend it in our Vault contract.
Now, Deposit 100 tokens into the Vault contract using the deposit() function.
Now, if you call the balanceOf() function in Vault by 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 call the withdraw() function and check your balance on your ERC20 token contract, you will see 200 tokens.
That’s it!. We just created a vault, then deposited 100 tokens, and eventually withdrew 200 tokens with the profit assumption.
This was a very abstracted and high level implementation of DeFi Vaults. In actual products, there are a lot more factors that will be baked with an optimal business logic to help create profit using various trading/lending strategies.




