Step-by-step web3 DApp built with Solidity and Vue.js (part 1)
Published
Table of contents
You can find the second part of this series here, and the third one here
Project overview
In this article we're going to build a basic decentralized web app that will store messages from users.
The application is live and you can play with it here. Just make sure you're using Metamask with the Rinkeby network 😉
Web 2.0 / 3.0 differences
The main difference between how I'd build this app as a common web application (or web 2.0) vs a decentralized app (web 3.0) are going to be the authentication, data storage and business logic layer:
-
Instead of building an authentication system, with user and password or an OAuth provider (like logging in with Google or Auth0), I'll use Metamask and use the user's wallet address to identify every user.
-
Instead of storing the messages from the users in a normal database like MongoDB or PostgreSQL, the app will store them in the blockchain
-
Typical web 2.0 applications use APIs as a bridge between the front end and the database. In this web 3.0 project I'll replace the API for a smart contract written in Solidity.
There is one similarity between a web 2.0 and 3.0, and that's the front end. In both cases, a web is, well... a website so any front end framework like React or Vue.js can be used.
Features
As mentioned, this is going to be a very basic project but it'll be enough to cover a lot of concepts needed to understand how to build decentralized apps. The main features will be:
- Users can access the app after connecting their wallet.
- Users can view messages posted by other users
- Users can post a message, which will require a small fee.
Tech stack
Requirements
- Node.js & NPM
- Vue CLI
Front end:
- Vue.js v3 + TypeScript
- TailwindCSS (styling, optional)
- Metamask
Smart Contract:
- Solidity (Hardhat)
- Rinkeby (Ethereum TestNet)
Creating the smart contract with Hardhat
I'll use my web3 template project as a starting point
Create a folder for your project and navigate to it with your terminal. Then run npx hardhat
and choose the option "Create sample project". Also select the option to install any required dependencies.
By default, Hardhat will generate some files in the /contracts
and /scripts
folders. I prefer to have the smart contracts source files in /solidity/contracts
and the compiled files in /src/artifacts
so for that, I created the folders manually and changed the hardhat.config.js
file as follows:
// .... some tasks above this
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: '0.8.4',
paths: {
sources: './solidity/contracts',
artifacts: './src/artifacts',
},
};
Once the configuration is completed, I created the file /solidity/contracts/MessagePortal.sol
, which is the smart contract that will be used to store and return the user's messages to the front end.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "hardhat/console.sol";
contract MessagePortal {
// number of total messages
uint256 totalMessages;
// struct definition of a message
struct Message {
string text;
address from;
uint256 datetime;
}
// stores all messages
Message[] allMessages;
// counter of messages per user
mapping(address => uint256) messagesPerUser;
// event that it's sent to the front end
event NewMessage(address indexed from, uint256 timestamp, string message);
constructor() {
console.log("Yayyy");
}
// saves message in the blockchain and updates counters
function sendMessage(string memory _newMessage) public {
console.log("%s has sent a message!", msg.sender);
// saves message in array
allMessages.push(Message(_newMessage, msg.sender, block.timestamp));
// update counters
totalMessages += 1;
messagesPerUser[msg.sender] += 1;
console.log(
"Number of messages of %a %s >> ",
msg.sender,
messagesPerUser[msg.sender]
);
// emit event for web app
emit NewMessage(msg.sender, block.timestamp, _newMessage);
console.log("AllMessages updated");
}
// returns all messages
function getAllMessages() public view returns (Message[] memory) {
return allMessages;
}
// returns number of messages
function getTotalMessages() public view returns (uint256) {
console.log("We have %d total messages!", totalMessages);
return totalMessages;
}
}
It's a pretty big contract but let's review the most important parts:
struct Message {...}
: this is a definition of the information that will be stored for each message, which will be the message itself, the address of the user that sent it and the timestamp.Message[] allMessages;
an array of the struct declared above in which I'll store all messagesevent NewMessage(...)
: we'll use events to notify the front end that a new message was saved in the blockchainfunction sendMessage( ... )
: this is the function that stores the message in the blockchain. As you can see it adds the message to theallMessages
array using thepush()
method, updates the counters and emits the event.getAllMessages()
andgetTotalMessages()
: this methods return the all the messages and the total number of messages.
You learn more about Solidity structs and arrays in my article about complex data types
Compile and deploy smart contract locally
Before being able to use the smart contract from our web application, we need to compile and deploy it, and for that, we need to write a script (remember the /scripts
folder that hardhat created?). Create a file in /solidity/scripts/deploy.js
with the following content:
const main = async () => {
// name of the contract should match with the file name!!
const msgContractFactory = await hre.ethers.getContractFactory(
'MessagePortal'
);
const msgContract = await msgContractFactory.deploy({});
await msgContract.deployed();
console.log('msgPortal address: ', msgContract.address);
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.error(error);
process.exit(1);
}
};
runMain();
As we modified the hardhat.config.js
file, Hardhat already knows where to find our contract's source code. This script is just compiling and deploy our contracts using Ethers' getContractFactory
and deploy
methods. After the contract is deployed, Hardhat will print a message with the blockchain address it was deployed to.
If there are errors in our contract, the compiler will throw an error and stop the script.
But where is the script going to deploy the contract to? For now, let's target a local blockchain 😉
To start an ethereum node locally, we'll run npx run node
on a new terminal window (check this article on how to run an ethereum node). Once our local node is running, we can run the deploy script with npx hardhat run ./solidity/scripts/deploy.js --network localhost
to compile and deploy our contract locally. Notice the --network
parameter in which we can indicate the target blockchain.
We'll something more to deploy it to a test network or the Ethereum main chain, but I'll explain that in more detail later on 😉
Conclusion
In the next part of this series, I'll explain how to create the frontend using Vue.js and ethers.js, which will allow our web application to interact with the smart contract we just created. We'll also deploy the contract to the Rinkeby test network and our web app to a hosting provider to test it in a production-like environment 💪
TAGS