Web3 backend and smart contract development for Python developers Musical NFTs part 14: Buy NFT crypto buyer
Tags: python, ethereum, nft, django
Published: 2023-11-09 11:17:00


Now we want to allow crypto user to buy our musical NFTs with MockTokens. For this to happen we will need to do few steps: 1) re-deploy our MusicNFT contract with updated max number of NFTs; 2) set-up desired price per `MusicNFT`; 3) send tokens from newly deployed `MockUSDC` to user address; 4) create `buyNFT` JavaScript function; 5) update our `base.html` and lastly 6) update our `crypt_user.html`. 

1) Currently we have max NFT variable inside `nft_package.sol` set to 5. What you need to do is to re-deploy both contracts by running `deploy.py` script in our `smart-contract/brownie_music_nfts/scripts` folder (or add setter function for this var inside smart contract and then re-deploy). You should pass let's say 1000 NFTs as second argument to `MusciNFT.deploy()` function inside Brownie `./scripts/deploy.py` (and now this call need to look something like this `musciNFTdeployed = MusicNFT.deploy(mockDeployed,1000,{'from':accountOne})`). And then from command line 

`brownie run deploy.py --network polygon-test`. 

Ones we re-deploy our contracts with new argument we need to pass new contract addresses to all JavaScript files where we use them (in our case `call_js.js` and `node_interaction.js`) as well as to import new `MockTokens` and `MusciaNFTs` in MetaMask. Then we need to transfer new `MockTokens` to buyer address before he perform new test NFT purches (all code for this we have in `node_interaction_sc.js`. Or simply you can make transfers directly in MetaMask from deployer to NFT buyers address ones you import our contracts there). 

Reason why we do all this is fact that if we keep max NFTs on 5 (what is current situation defined during our fist deploy), very soon we will hit max limit during testing phase and our calls to smart contract `buyNFT` function will start to be reverted. To avoide this situation we need to re-deploy contracts and to assigne this new address to all relevenat places as we said earlier (for how to do all this you can go and check part no. 6 in this blog seria, `smart contract deployment`).


Now, step two: setup desired MusicNFT price


1) CDN Ethers at the end (just bellow ending body tag) of `base.html` template.

   ` <script src="https://cdn.ethers.io/scripts/ethers-v4.min.js"`

Then install with npm inside our root directory because we want to use it with Node (we will call some smart contract functions also from Node)

` $npm install ethers@5.7.2`

Here is important to install this specific version of Ethers because newer versions, after 6, tend to have problems with setting up `InfuraProvider`

Now is time to set new price of MusicalNFT and to transfer some MockUSDC to the user
Add to existing `.env` 1) infura API key; 2) signer privat keys and 3) network on which we would like to connect. 

Also add `.env` to `.gitignore` (if you didn't already)

To get Infura API key you need to open profile on `https://www.infura.io/`. Ones you are in just create new project and copy API keys. They should look something like this `https://polygon-mumbai.infura.io/v3/52153a550fd8498c9041752356789010`. Be cerfule to pick up `Mumbai` testnet from `Polygon`, becuse we deploy our contracts there. Pass API key to `INFURA_API_KEY` inside newly created `.env` file. 

Then for wallet private key go to MetaMask and select account from where you made contracts deployment. Then go to `more` => `account details` => `show private keys`. Copy private key to your local enviroment varibale under `SIGNER_PRIVATE_KEY`

Last thing we need here is `ETHEREUM_NETWORK=maticmum` (mainnet Polygon is `matic` and testnet Mumabi is `maticmum`)

Now let's load `.env` file. For this we need to install packege `dotenv` with command `npm install dotenv` inside our project root directory. Ones we start to work with contracts we will require this package inside `Node` to load `.env` varibales. 

Now open `Node` 

   

```
     $node
        Welcome to Node.js v19.1.0.
        Type ".help" for more information.
        > require("dotenv").config()  
        > const network = process.env.ETHEREUM_NETWORK
        > const infura = process.env.INFURA_API_KEY
        > const private_key = process.env.SIGNER_PRIVATE_KEY
```

At this point we should have all varibales from our `.env` loaded into Node and assigned to network, infura and private_key. What we need in this moment is to defin provider, signer (the one who will pay for transaction) and instancies of our contracts.



To make instance of provider, signer and contracts we always need to have follwoing ingredients: 1) ethereum network we intend to use; 2) in our case Infura API keys; 3) private keys of the signer account; 4) contract ABI and 5) address of deployed contract. With this elements you can first instatite: provider (gateway to Ethereum network); then signer (creator and signer of tx) and finally contract itself. And with `ethers.js` this can be done very easly


Open separate terminal from the one where your Node is running. From project root directory copy:

```
    $cp smart-contracts/brownie_musical_nfts/build/contracts/MockUSDC.json ./static
```

Then `MusicNFT.json` (if you already didn't)

```

    $cp smart-contracts/brownie_musical_nfts/build/contracts/MusicNFT.json ./static
```


Let's go back to Node and import contracts ABIs (we will need them in last step when we instatie contracts) from our `Node`:
    

```
    // Assigning ABI path to const
    >const mockJsonFile = "./static/MockUSDC.json";
    >const nftJsonFile = "./static/MusicNFT.json";
    
    // Loading contract ABIs
    >const mockAbi=JSON.parse(fs.readFileSync(mockJsonFile));
    >const nftAbi=JSON.parse(fs.readFileSync(nftJsonFile));
```
    

At this point we have our `ABI` and `.env` varibles loaded in `node` and we can move to define provider, signer and contracts instancie (ones we come to the last point we will be able to work with our deplyed contracts from within `Node`).

step 1: Lets define `provider` and contract.


Node: 
   

```
     > const { ethers } = require("ethers");
        > const provider = new ethers.providers.InfuraProvider(
        network,
        infura);
```


step 2: define `signer`

    

```
>const signer = new ethers.Wallet(private_key, provider);
```

step 3: define `MusicNFT` contract as well as `MockToken`

```

    // contract address from Mumbai Polygon testnet (last deployment)
    const nft_contract_address = "0x1D33a553541606E98c74a61D1B8d9fff9E0fa138"
    const mock_usdc_address = "0xCeD9Fe6C4851acea6833DB0B7A0d2b892E4BBD5f"

    // Let's instatiate both contracts
    const contract_usdc = new ethers.Contract(mock_usdc_address, mockAbi.abi, signer);
    const contract_nft = new ethers.Contract(nft_contract_address, nftAbi.abi, signer);

```

At this point we have our contract in `Node` ready to be used. We can test that. Let's define one async function to check for name of our NFT.

```

    // This function will return name of our NFT contract
    getNftName = async () {
        let result = await contract_nft.name();
        console.log(result);
    }



    >getNftName()
    Promise {
    <pending>,
    [Symbol(async_id_symbol)]: 1779,
    [Symbol(trigger_async_id_symbol)]: 5
    }
    > MuscialNFT
```

Now we have fully functional contract in our `node` and we can update NFT price and send to potential user some `MockTokens`. Here we should notice that we are sending `MockTokens` to user because only deployer of contract have all issued tokens. In real life situation user should have his own USDC or some other stabilcoins to pay for his NFTs. Last two steps: update NFT price, send MockTokens tokens to the buyer.

```

    setNftPrice = async (price) => {
        let result = await contract_nft.setNFTPrice(price);
        
    }

    // check for new price
    var assert = require('assert');
    // When you call this function please pass as argument price you set previously
    checkNFTPrice = async (price) => {
      let nft_price = await contract_nft.NFTPriceInUSDC();
      console.log(Number(nft_price));
      assert(Number(nft_price) == price, "Price was not upddted!");
    }

    // sending MockTokens to the customer 
    // const buyer = here you should add address of acocunt from which you will purches NFTs
    const buyer = "0xB4A5D329e35F83a2e6AB5a74d56f5bD67EaB8d83"
    const amount = BigInt(10e18)

    sendTokenToBuyer = async (buyer, amount) => {
        await mock_usdc_contract.transfer(buyer, amount)
    }
```

*this JS code you can find in `static/node_interaction_sc.js`

Now when our customer have MockTokens to buy NFT, we will move to front-end and add functinoality to allow user to make this NFT purches.


For this to work we will have few step process. In first step user approve NFT contract to spend his `MockTokens` in amount calulcated by simple multiplication of number of NFTs he wants to buy time current price of `MusicNFT`. But because we are calling `MockUSDC` `transfeFrom` from within MusicNFT, `MockUSDC` contract will se `MusicNFT` as caller and it will expect `MusicNFT` to have allowance on buyer USDC mock tokens. That is why first step is to call approve function of our `MockToken` and then in second step to call `buyNFT` function from `MusicNFT` contract. If first step was ok, then `MockUSDC` will se that caller (`MusicNFT` contract) have rights to use buyer `MockTokens` and he will then transfer in last step from buyer to `MusicNFT` precise amount of tokens. Only then `MusicNFT` contract will mint new NFTs to buyer addrees and emit event of confirmation. And with this we have all steps done! 


And this function can look something like this (inside `static/call_sc.js` file):

   

```
 const nft_contract_address = "0x1D33a553541606E98c74a61D1B8d9fff9E0fa138"
    const mock_usdc_address = "0xCeD9Fe6C4851acea6833DB0B7A0d2b892E4BBD5f"
    const main_account = "0x273f4FCa831A7e154f8f979e1B06F4491Eb508B6"
    const test_account = "0xB4A5D329e35F83a2e6AB5a74d56f5bD67EaB8d83"

    // set provider
    const provider = new ethers.providers.Web3Provider(window.ethereum);

    // get signer this need to be test_account
    const signer = provider.getSigner(0);

    // see for original ABI file in github repo. you can copy from there
    const ABI_NFT = [...]

    // see for original ABI file in github repo. you can copy from there
    const ABI_USDC = [..]

    buyNFT = async () => {
        // 1. get value user pass to use from front-end about number of NFTs he would like to buy
        const numberOfNFTS = document.getElementById('NFT').value;
        
        // 2. get current price of NFT from our smart contract and caluclate total price to be payed
        const price = await nft_contract.NFTPriceInUSDC();
        console.log(Number(price)*numberOfNFTS);
        const total_price = Number(price)*numberOfNFTS

        // 3. approve MusicNFT to transfer user MockTokens in total amount on contract 
        await usdc_contract.approve(nft_contract_address, total_price)

        
        // 4. finally call buyNFT function from MusicNFT contract to buy and mint new NFTs to user account
        await nft_contract.buyNFT("uri://music_nft_token", numberOfNFTS)
    }
```

This newly created `call_js.js` script should be added to our `base.html`


   

```
 {% 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>
        <link rel="shortcut icon" type="image" href="{% static 'favicon.ico' %}" >
        <!-- <script type="module" src="{% static 'call_sc.js' %}"></script> -->
    </script>
    
    </head>
    <body onload="checkMetaMaskState()">
    <body>
        {% 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>
        </body>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
        <script src="https://cdn.ethers.io/scripts/ethers-v4.min.js"
        charset="utf-8"
        type="text/javascript"></script>
        <script type="text/javascript" src="{% static 'call_sc.js' %}"></script>
    </html>
```

And then last step is to connect `onclick` inside our `crypto_user.html` to newly created JS funciton `buyNFT()`. This will allow user to pass number of NFTs he would like to buy, then in backgorund we will calculate total price, approve tokens to `MusicNFT` contract, transfer MockTokens to `MusciNFT` contract and in last step mint new NFT (or NFTs, depends from number user give as input to front-end) and assigne to buyer address. With this we have buy NFT with crypto fully in place.   


    

```
<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%}
                <label for="NFT">Number of NFTs</label>
                <input  type="text" id="NFT" ><br>
            </form>
            <input  onclick="buyNFT()" type="submit" value="Buy" class="btn btn-secondary">
            </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 %}
```



If everything went well, ones user login into our platform he will be able to give number of NFTs he wants to buy and then press `buy` button. MetaMask will pop-up aksking him first for approval for `MockTokens` to `MusicNFT` contract, and then in second step ask to confirm start of `buyNFT` transaction. Something like this: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cvneelle0kpstl8545jc.png)

In next blog, we will listen for emited minting event on our backend and updated our database and front-end accordingly.  

In this [repo ](https://github.com/ilijapet/musical_nft_thumbnails)you can find latest code