Web3 backend and smart contract development for Python developers Musical NFTs part 13: Connecting MetaMask
ilija
Posted on October 29, 2023
In this part we will add ability for crypto users to connect his/her MetaMask and to purches new NFT with our MockUSDC tokens. But before that let's clean a bit our home.html
because it started to look a bit messy. What we can do is to create two new templates for crypto buyers and credit card buyer and to loaded them into home.html
when we check user payment method status. For this to happen we just need to repackage our home.html
and to move code related to crypto buyer to newly created templates/cypto_user.html
and then all HTML code related to credit card buyer to templates/credit_card_buyer.html
. Then in second step we will use Django tag include
to include this two newly created HTML files into our home.html
. With this we will clean up cluttery and make our main home.html
template a bit more readable.
Create cypto_user.html
and credit_card_user.html
files inside root templates
folder.
cypto_user.html
<table class="table table-hover">
<thead class="table-dark">
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Total no. NFTs</th>
<th scope="col">Means of payment</th>
<th scope="col">Buy NFT</th>
</tr>
</thead>
<tbody>
<tr>
<td> {{ customer.first_name }} {{ customer.last_name }} </td>
<td> {{ customer.email }} </td>
<td> {{ customer.total_no_of_nfts }} </td>
<td>
<form action="" method="post">
{%csrf_token%}
{{orderForm.as_p}}
<input type="submit" value="Save" class="btn btn-secondary">
</form>
</td>
<td>
<form action="" method="post">
{%csrf_token%}
{{form.as_p}}
<input type="submit" value="Save" class="btn btn-secondary">
</form>
</td>
</tr>
</tbody>
</table>
{% for card in metadata%}
{% if forloop.counter0|divisibleby:3 %} <div class="row">{% endif %}
<div class="card m-5 p-2" style="width: 18rem;">
{% load static %}
<img src="{% static 'nft/'%}{{card.cover_file_name}}" class="card-img-top" alt="..." width="50" height="200"/>
<div class="card-body">
<h5 class="card-title">{{card.name}}</h5>
<br>
<p class="card-text">{{card.description}}</p>
</div>
</div>
{% if forloop.counter|divisibleby:3 or forloop.last %}</div> {% endif %}
<br>
{% endfor %}
And then in credit_card_user.html
<table class="table table-hover">
<thead class="table-dark">
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Total no. NFTs</th>
<th scope="col">Means of payment</th>
</tr>
</thead>
<tbody>
<tr>
<td> {{ customer.first_name }} {{ customer.last_name }} </td>
<td> {{ customer.email }} </td>
<td> {{ customer.total_no_of_nfts }} </td>
<td><form action="" method="post">
{%csrf_token%}
{{orderForm.as_p}}
<input type="submit" value="Save" class="btn btn-secondary">
</form>
</td>
</tr>
</tbody>
</table>
{% for card in metadata%}
{% if forloop.counter0|divisibleby:3 %} <div class="row">{% endif %}
<div class="card m-5 p-2" style="width: 18rem;">
{% load static %}
<img src="{% static 'nft/'%}{{card.cover_file_name}}" class="card-img-top" alt="..." width="50" height="200"/>
<div class="card-body">
<h5 class="card-title">{{card.name}}</h5>
<br>
<p class="card-text">{{card.description}}</p>
</div>
</div>
{% if forloop.counter|divisibleby:3 or forloop.last %}</div> {% endif %}
<br>
{% endfor %}
<p> credit card </p>
Now what we need to do is to include back into our home.html
this two new files via Django template language tag include
. Syntax should go something like this
{% include "crypto_user.html" %}
And now our home.html
should look a bit shorter and cleaner
{% extends "base.html" %}
{% block content%}
{% if user.is_authenticated %}
{% if customer.type == "CRYPTO"%}
{% include "crypto_user.html" %}
{% else %}
{% include "credit_card_user.html" %}
{% endif %}
{% else %}
<div class="col-md-6 offset-md-3">
<h1> Login </h1>
<br/>
<form method="POST" action="{% url 'home' %}">
{% csrf_token %}
<div class="mb-3">
<input type="text" class="form-control" aria-describedby="emailHelp" placeholder="Username" name="username" required>
</div>
<div class="mb-3">
<input type="password" class="form-control" placeholder="Password" name="password" required>
</div>
<button type="submit" class="btn btn-secondary">Login</button>
</form>
</div>
{% endif %}
{% endblock content%}
Now all things should work exactly the same only thing is that we have more readeble main html document.
Let's move to web3 part. For now our aim is to integrate MetaMask (you can easlly experiment with WalletConnect if you want alternative version of this code). This will allow crypto user to purches new NFT by using MetaMask. For this to happen we will need to use some JavaScript (in next iteration of this app, when we start to use React for our frontend we will integrate WalletConnect instead of MetaMask).
Ones crypto user login into his profile he will see two new buttons: connect and buy NFT. He first need to press connect and MetaMask will pop-up asking him to provide his credentials. Ones he finish login into MetaMask he will be able to use buy NFT button and to pass number of NFTs he would like to buy.
Step 1: In your static folder create new connect_wallet.js
file. And inside that file pass following code (Please check if static parameters are set correctly inside your settings.py
It should look something like this =>
STATIC_URL = "/static/"
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
JavaScript file need to handle connection to MetaMask as well as to update list items to connected
or connect to metamask
status how they change. For this achive we will need few more functions.
//function used to connect to MetaMask wallet
connect = async () => {
const address = await ethereum
.request({
method: 'eth_requestAccounts',
params: [],
})
.then((res) => console.log('request accounts', res))
.catch((e) => console.log('request accounts ERR', e));
document_test = document.getElementById("connect");
document_test.innerHTML = "Connected"
}
// Simple function used to manipulate HTML elements according to connected or user disconnencted status
checkMetaMaskState = async () => {
const account = await window.ethereum.request({method: 'eth_accounts'})
const liElement = document.createElement("li");
liElement.setAttribute("class", "nav-item")
const aElement = document.createElement("a");
aElement.setAttribute("class", "nav-link p-3")
if (typeof account[0] == "undefined") {
aElement.setAttribute("href", "javascript:connect()")
aElement.setAttribute("id", "link")
liElement.setAttribute("id", "connect")
liElement.innerHTML = "Connect to MetaMask"
aElement.appendChild(liElement);
document.getElementById("logout").appendChild(aElement);
} else if (account[0].includes("0x")) {
aElement.setAttribute("id", "link")
liElement.setAttribute("id", "connect")
liElement.innerHTML = "Connected"
aElement.appendChild(liElement);
document.getElementById("logout").appendChild(aElement);
} else {
console.log("There is some problem with MetaMask")
}
}
// Detect change in connect or dissconcet wallet status and updated front-end accordingly
window.ethereum.on('accountsChanged', async () => {
let ilElement = document.getElementById("connect")
const account = await window.ethereum.request({method: 'eth_accounts'})
if (typeof account[0] == "undefined") {
ilElement.innerHTML = "Connect to MetaMask"
} else if (account[0].includes("0x")) {
ilElement.innerHTML = "Connected"
}
});
Then we need to add onload
in body element of our base.html
. And then to give checkMetaMaskState
JS funciton as value. This function is used to generate new HTML document according to MetaMask connect/disconnect status.
Now base.html
should look something like this (bascially the same as beafore just with <body onload="checkMetaMaskState()">
added)
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Muscial NFT</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script type="text/javascript" src="{% static 'connect_wallet.js' %}"></script>
</head>
<body onload="checkMetaMaskState()">
{% include "navbar.html"%}
<div class="container ">
<br/>
<br/>
{% if messages %}
{% for message in messages%}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor%}
{% endif %}
{% block content %}
{% endblock content %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>
Then in second step we will erase one list item element from our navbar.html
template (this place will be populated directly from javascript level according to MetaMask wallet status).
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'home' %}">Musical NFT </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="logout">
{% if user.is_authenticated%}
{% if customer.type == "CRYPTO"%}
{% load static %}
<script type="text/javascript" src="{% static 'connect_wallet.js' %}"></script>
<li class="nav-item" >
<a class="nav-link p-3" href="{% url 'logout'%}" >Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'logout'%}">Logout</a>
</li>
{% endif %}
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'register'%}">Register</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'home'%}">Login</a>
</li>
{% endif%}
</ul>
</li>
</ul>
</div>
</div>
</nav>
If everything went well ones you login Denis user into his account you should be able to see something like this.
Code can be found in this github repo
Posted on October 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.