Embark on a mystical journey to build **Mystic Vault, an enchanting Ethereum Decentralized Application (DApp) that allows users to store and retrieve their secret spells. In this comprehensive guide, we’ll integrate the provided HTML, CSS, JavaScript, and Solidity code into a fully functional DApp. Let’s weave some magic!
Table of Contents
- Introduction
- Prerequisites
- Project Structure
- Setting Up the Development Environment
- Writing the Smart Contract
- Compiling and Deploying the Smart Contract
- Building the Frontend
- Connecting Frontend to Smart Contract
- Testing the DApp
- Conclusion
- Clone the Repository
Introduction
Decentralized Applications (DApps) harness the power of blockchain technology to create secure and transparent platforms. Mystic Vault is a simple yet magical DApp that allows users to store and retrieve their secret spells on the Ethereum blockchain. This guide will walk you through every step, from writing the smart contract to building an interactive frontend.
Ready to conjure up some blockchain magic? Let’s get started! 🪄
Prerequisites
Before we begin, ensure you have the following installed:
- Node.js and npm: Download Node.js
- Git: Download Git
- Ethereum Wallet Extension: Install MetaMask
- Code Editor: Such as Visual Studio Code
With these tools in place, you’re all set to begin your mystical development journey!
Project Structure
Here’s an overview of the project structure we’ll be working with:
MysticVault/
├── contracts/
│ └── SimpleStorage.sol
├── frontend/
│ ├── index.html
│ ├── app.js
│ ├── styles.css
├── hardhat.config.js
├── package.json
└── artifacts/
Setting Up the Development Environment
1. Create and Navigate to the Project Directory
mkdir MysticVault
cd MysticVault
2. Initialize npm and Install Dependencies
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox web3
3. Initialize Hardhat Project
npx hardhat init
- Select “Create a JavaScript project” when prompted.
- Accept default settings and install dependencies.
4. Update hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-ignition");
module.exports = {
solidity: "^0.8.27",
networks: {
hardhat: {
chainId: 1337,
},
},
};
Writing the Smart Contract
Create SimpleStorage.sol
in the contracts
Directory
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
contract SimpleStorage {
string private data;
// Function to set the secret spell
function setSecretSpell(string memory _data) public {
data = _data;
}
// Function to get the secret spell
function getSecretSpell() public view returns (string memory) {
return data;
}
}
Explanation:
-
State Variable:
data
stores the secret spell as a private string. -
setSecretSpell
Function: Allows users to store a new secret spell. -
getSecretSpell
Function: Retrieves the stored secret spell.
Compiling and Deploying the Smart Contract
1. Compile the Contract
npx hardhat compile
2. Deploy the Contract Using Hardhat
Add the Deployment Code
Open ignition/modules/SimpleStorage.js
and add the following code:
// This setup uses Hardhat Ignition to manage smart contract deployments.
// Learn more about it at https://hardhat.org/ignition
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
module.exports = buildModule("SimpleStorageModule", (m) => {
const simpleStorage = m.contract("SimpleStorage", []);
return { simpleStorage };
});
Explanation:
-
Import
buildModule
: This function helps define deployment modules. -
Define Module Name:
"SimpleStorageModule"
is an arbitrary name for the module. -
Deploy Contract:
m.contract("SimpleStorage", [])
deploys theSimpleStorage
contract without constructor arguments.
(Insert this image screenshot here)
Deploying the Smart Contract Using Hardhat Ignition
-
Start the Hardhat Local Node
In one terminal window, run:
npx hardhat node
This starts a local Ethereum node provided by Hardhat, running on
http://127.0.0.1:8545
.
-
Deploy Using Ignition
Open a new terminal window and execute the deployment command:
npx hardhat ignition deploy ./ignition/modules/SimpleStorage.js --network localhost
then press y and wait for output similar to this
Interacting with the Smart Contract
Now that our smart contract is deployed, let’s interact with it to ensure it’s functioning correctly.
-
Connect to the Hardhat Network
Since Hardhat provides JSON-RPC endpoints, we can use ethers.js in a script or use the Hardhat console.
-
Using Hardhat Console
Run the Hardhat console:
npx hardhat console --network localhost
(Insert this image screenshot here)
-
Interact with
SimpleStorage
a. Retrieve the Contract Instance
const SimpleStorage = await ethers.getContractFactory("SimpleStorage"); const simpleStorage = await SimpleStorage.attach("0xYourContractAddress");
Replace
0xYourContractAddress
with the actual address output from the deployment step.b. Get Initial Data
const initialData = await simpleStorage.getSecretSpell(); console.log(initialData); // Should output an empty string
c. Set New Data
await simpleStorage.setSecretSpell("Abra Kadabra");
d. Retrieve Updated Data
const updatedData = await simpleStorage.getSecretSpell(); console.log(updatedData); // Should output "Abra Kadabra"
(Insert this image screenshot here)
This confirms that our smart contract can store and retrieve data as intended.
-
Exit Hardhat Console
.exit
Create a folder ‘test’ and create a file with name ‘debugInteract.js’ and past the following code
import Web3 from 'web3';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// Resolve __dirname in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Load ABI from the JSON file
const abiPath = path.resolve(__dirname, '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json');
const abiFile = JSON.parse(fs.readFileSync(abiPath, 'utf-8'));
const contractABI = abiFile.abi;
// Contract address (replace with your deployed address)
const contractAddress = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
const main = async () => {
try {
console.log("Setting up Web3 provider...");
// Initialize Web3 with your provider (e.g., local node)
const web3 = new Web3('http://localhost:8545'); // Update if using a different provider
console.log("Fetching accounts...");
const accounts = await web3.eth.getAccounts();
console.log("Available Accounts:", accounts);
if (accounts.length === 0) {
throw new Error("No accounts available. Ensure your node has unlocked accounts.");
}
console.log("Creating contract instance...");
const contract = new web3.eth.Contract(contractABI, contractAddress);
console.log(`Contract deployed at: ${contract.options.address}`);
console.log("Calling getData()...");
const data = await contract.methods.getSecretSpell().call({ from: accounts[0] });
console.log("Data retrieved from contract:", data);
// Example: Setting new data (uncomment if needed)
/*
const newData = "Hello, Mystic Vault!";
console.log(`Sending transaction to setData("${newData}")...`);
const receipt = await contract.methods.setData(newData).send({ from: accounts[0], gas: 300000 });
console.log("Transaction successful with receipt:", receipt);
// Verify the update
const updatedData = await contract.methods.getData().call({ from: accounts[0] });
console.log("Updated Data:", updatedData);
*/
} catch (error) {
console.error("An error occurred during contract interaction:", error);
}
};
main();
add { ... "type": "module", ... }
inside package.json then run
node test/debugInteract.js
Building the Frontend
1. Create the Frontend Directory and Files
Navigate to the project root and create the frontend
directory:
mkdir frontend
cd frontend
Create the following files inside frontend
:
index.html
app.js
styles.css
2. Write the HTML Structure (index.html
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Mystic Vault</title>
<!-- Include Magic Animations CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/magic/1.1.0/magic.css">
<!-- Include Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;700&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<!-- Include Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Link to External CSS -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Sparkles Background -->
<div class="sparkles"></div>
<!-- Header with Padlock and Sparkles Icons -->
<h1 class="magictime puffIn">
<i class="fas fa-lock icon" aria-label="Padlock"></i>
Mystic Vault
<i class="fas fa-magic icon" aria-label="Sparkles"></i>
</h1>
<!-- Store Secret Spell Section with Wand Icon -->
<div class="store-container">
<input type="text" id="inputData" placeholder="Enter your secret spell here..." aria-label="Secret Spell Input" />
<button id="setData" class="magictime puffIn" aria-label="Store Secret Spell">
<i class="fas fa-wand-magic icon"></i>Store Spell
</button>
</div>
<!-- Retrieve Secret Spell Section with Orb Icon -->
<div class="retrieve-container">
<button id="getData" class="magictime puffIn" aria-label="Retrieve Secret Spell">
<i class="fas fa-circle-notch icon"></i>Retrieve Secret Spell
</button>
<div id="displayData" class="magictime fadeIn" aria-live="polite">
<i class="fas fa-magic"></i> Your secret spell will appear here...
</div>
<button id="clearDisplay" aria-label="Clear Retrieved Spell">Clear</button>
</div>
<!-- Loading Spinner -->
<div id="loading" style="display: none;" class="magictime puffIn" aria-live="assertive">
<i class="fas fa-spinner fa-spin"></i> Processing...
</div>
<!-- Notification Message -->
<div id="notification" class="magictime fadeIn" role="alert" aria-live="assertive"></div>
<!-- Include Web3.js -->
<script src="https://cdn.jsdelivr.net/npm/web3/dist/web3.min.js"></script>
<!-- Link to External JavaScript -->
<script src="app.js"></script>
</body>
</html>
Connecting Frontend to Smart Contract
1. Update the ABI in app.js
First, extract the ABI from the compiled contract:
- Locate the ABI in
artifacts/contracts/SimpleStorage.sol/SimpleStorage.json
. - Copy the contents of the
"abi"
field.
Updated ABI:
const contractABI = [
{
"inputs": [
{
"internalType": "string",
"name": "_data",
"type": "string"
}
],
"name": "setSecretSpell",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getSecretSpell",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
];
2. Update the Contract Address in app.js
Replace '0xYourContractAddress'
with the actual contract address from the deployment output.
const contractAddress = '0xYourContractAddress'; // Replace with your contract's address
3. Write the JavaScript Logic (app.js
)
// Function to display notifications
function showNotification(message, isSuccess = true) {
const notification = document.getElementById('notification');
notification.innerText = message;
notification.style.backgroundColor = isSuccess ? 'rgba(46, 204, 113, 0.9)' : 'rgba(231, 76, 60, 0.9)';
notification.classList.add('show');
// Animate the notification
animateCSS('#notification', 'fadeIn');
// Hide after 3 seconds
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// Function to show or hide the loading spinner
function showLoading(show) {
const loading = document.getElementById('loading');
if (show) {
loading.style.display = 'flex';
} else {
loading.style.display = 'none';
}
}
// Function to add and remove animation classes
function animateCSS(element, animationName, callback) {
const node = document.querySelector(element);
node.classList.add('magictime', animationName);
function handleAnimationEnd() {
node.classList.remove('magictime', animationName);
node.removeEventListener('animationend', handleAnimationEnd);
if (typeof callback === 'function') callback();
}
node.addEventListener('animationend', handleAnimationEnd);
}
// Initialize the DApp
window.addEventListener('load', async () => {
let web3;
let accounts;
let simpleStorage;
// Modern dapp browsers...
if (window.ethereum) {
web3 = new Web3(window.ethereum);
try {
// Request account access
await window.ethereum.request({ method: 'eth_requestAccounts' });
} catch (error) {
console.error("User denied account access");
alert("Please allow access to your Ethereum wallet to use this DApp.");
return;
}
}
// Legacy dapp browsers...
else if (window.web3) {
web3 = new Web3(web3.currentProvider);
}
// Non-dapp browsers...
else {
alert('No Ethereum wallet detected. Please install MetaMask, Brave Wallet, or use a compatible browser.');
return;
}
// Instantiate the contract
simpleStorage = new web3.eth.Contract(contractABI, contractAddress);
// Get the user's accounts
accounts = await web3.eth.getAccounts();
// Handle Store Secret Spell
document.getElementById('setData').onclick = async () => {
const data = document.getElementById('inputData').value;
if (data.trim() === "") {
showNotification("Please enter a secret spell before storing.", false);
return;
}
showLoading(true);
try {
await simpleStorage.methods.setSecretSpell(data).send({ from: accounts[0] });
showLoading(false);
showNotification('✨ Secret Spell Stored Successfully! ✨', true);
} catch (error) {
console.error(error);
showLoading(false);
showNotification('❌ Failed to Store Secret Spell.', false);
}
};
// Handle Retrieve Secret Spell
document.getElementById('getData').onclick = async () => {
showLoading(true);
try {
const result = await simpleStorage.methods.getSecretSpell().call();
document.getElementById('displayData').innerHTML = `<i class="fas fa-magic"></i> ${result}`;
showLoading(false);
showNotification('🔮 Secret Spell Retrieved Successfully! 🔮', true);
} catch (error) {
console.error(error);
showLoading(false);
showNotification('❌ Failed to Retrieve Secret Spell.', false);
}
};
// Handle Clear Retrieved Spell
document.getElementById('clearDisplay').onclick = () => {
document.getElementById('displayData').innerHTML = `<i class="fas fa-magic"></i> Your secret spell will appear here...`;
showNotification('🧹 Retrieved spell cleared.', true);
};
});
Styling the Frontend (styles.css
)
/* 1. General Styles */
body {
background: linear-gradient(135deg, #141E30, #243B55);
color: #ecf0f1;
font-family: 'Roboto', sans-serif;
text-align: center;
padding: 50px;
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
}
/* 2. Header with Padlock and Sparkles Icons */
h1 {
font-family: 'Roboto Slab', serif;
font-size: 3em;
margin-bottom: 40px;
color: #fff;
text-shadow: 2px 2px 8px rgba(0,0,0,0.5);
animation: fadeInDown 1s ease forwards;
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
/* 3. Adding Sparkles in the Background */
.sparkles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(255,255,255,0.05) 1px, transparent 1px),
radial-gradient(circle, rgba(255,255,255,0.05) 1px, transparent 1px);
background-position: 0 0, 25px 25px;
background-size: 50px 50px;
z-index: 0;
}
/* 4. Container for Store Secret Spell */
.store-container {
display: flex;
align-items: center;
margin-bottom: 30px;
width: 400px;
position: relative;
z-index: 1;
}
.store-container input[type="text"] {
flex: 1;
padding: 12px 20px;
border: 2px solid #6c5ce7;
border-right: none;
border-radius: 5px 0 0 5px;
font-size: 1em;
background-color: #34495e;
color: #ecf0f1;
transition: border-color 0.3s;
}
.store-container input[type="text"]::placeholder {
color: #bdc3c7;
}
.store-container input[type="text"]:focus {
outline: none;
border-color: #a29bfe;
}
.store-container button {
padding: 12px 20px;
border: 2px solid #6c5ce7;
border-left: none;
border-radius: 0 5px 5px 0;
background-color: #6c5ce7;
color: #ecf0f1;
font-size: 1em;
cursor: pointer;
transition: background-color 0.3s, transform 0.3s;
display: flex;
align-items: center;
justify-content: center;
}
.store-container button:hover {
background-color: #a29bfe;
transform: scale(1.05);
}
/* 5. Retrieve and Display Section */
.retrieve-container {
display: flex;
flex-direction: column;
align-items: center;
width: 400px;
position: relative;
z-index: 1;
}
.retrieve-container button {
background-color: #e67e22;
border: none;
padding: 12px 24px;
border-radius: 5px;
color: #ecf0f1;
font-size: 1em;
cursor: pointer;
transition: background-color 0.3s, transform 0.3s;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.retrieve-container button:hover {
background-color: #f39c12;
transform: scale(1.05);
}
#displayData {
background-color: #34495e;
padding: 20px;
border-radius: 5px;
width: 100%;
font-size: 1.2em;
color: #ecf0f1;
min-height: 50px;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
animation: fadeIn 1s ease forwards;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
/* 6. Magic Animations Overrides */
.magictime.puffIn {
animation-duration: 1s;
animation-fill-mode: both;
}
/* 7. Custom Animations */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 8. Responsive Design */
@media (max-width: 500px) {
.store-container, .retrieve-container {
width: 90%;
}
h1 {
font-size: 2.5em;
}
}
/* 9. Decorative Icons Styles */
.icon {
margin: 0 8px;
}
/* 10. Loading Spinner Styles */
#loading {
position: fixed;
top: 20px;
right: 20px;
background-color: rgba(52, 73, 94, 0.9);
padding: 10px 20px;
border-radius: 5px;
color: #ecf0f1;
font-size: 1em;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
z-index: 2;
display: flex;
align-items: center;
}
/* 11. Notification Message Styles */
#notification {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(52, 73, 94, 0.9); /* Default background color */
padding: 10px 20px;
border-radius: 5px;
color: #ecf0f1;
font-size: 1em;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
z-index: 2;
display: none; /* Hidden by default */
opacity: 0;
transition: opacity 0.5s ease, visibility 0.5s ease;
}
#notification.show {
display: flex;
opacity: 1;
visibility: visible;
}
/* 12. Clear Button for Retrieved Message */
#clearDisplay {
margin-top: 10px;
background-color: #95a5a6;
border: none;
padding: 8px 16px;
border-radius: 5px;
color: #ecf0f1;
font-size: 0.9em;
cursor: pointer;
transition: background-color 0.3s, transform 0.3s;
}
#clearDisplay:hover {
background-color: #7f8c8d;
transform: scale(1.05);
}
Testing the DApp
1. Serve the Frontend
In the frontend
directory, start a simple HTTP server:
cd frontend
npx http-server . -c-1 -p 8080
press ‘y’ for package installation if asked
Navigate to http://localhost:8080
in your browser.
2. Connect Your Wallet
- Ensure your wallet (MetaMask, Brave Wallet) is connected to the
Hardhat
network. -
Network Details:
- Network Name: Hardhat Localhost 8545
- RPC URL: http://localhost:8545
- Chain ID: 31337
3. Import an Account
- From the Hardhat node output, copy one of the private keys.
- Import it into your wallet.
4. Interact with the DApp
-
Store a Secret Spell:
- Enter a spell in the input field.
- Click “Store Spell”.
- Confirm the transaction in your wallet.
- A notification will confirm success.
-
Retrieve the Secret Spell:
- Click “Retrieve Secret Spell”.
- The stored spell will appear.
- A notification will confirm success.
-
Clear the Retrieved Spell:
- Click “Clear”.
- The display resets.
- A notification will confirm the action.
(Insert screenshots of each interaction step here)
Conclusion
🎉 Congratulations! You’ve successfully built Mystic Vault, a magical Ethereum DApp that allows users to store and retrieve their secret spells. This journey has taken you through smart contract development, frontend integration, and blockchain interaction.
Key Learnings:
- Smart Contract Development: Writing and deploying Solidity contracts.
- Blockchain Interaction: Using Web3.js to interact with smart contracts.
- Frontend Development: Building an interactive and animated user interface.
- Troubleshooting: Debugging common issues in DApp development.
With these skills, you’re ready to explore more complex and exciting blockchain projects. The possibilities are endless!
Clone the Repository
Ready to explore the code and see Mystic Vault in action? Clone the repository from GitHub and get started!
git clone https://github.com/KarthiDreamr/MysticVault-Dapp-Simple-Storage.git
Feel free to customize and expand upon this project to suit your needs. Happy coding! 🪄✨
If you found this guide helpful, share it with fellow blockchain enthusiasts and stay tuned for more tutorials on decentralized application development!
🔗 Connect with me on GitHub | Linkedin | Twitter
Happy Coding! 🪄✨
By following this guide, you’ve built a fully functional Ethereum DApp with an engaging user interface. Keep experimenting and learning—the blockchain world awaits your magical creations! 🚀
Feel free to reach out if you have any questions or need further assistance. Happy coding! 🪄✨
Top comments (0)