DEV Community

ilija
ilija

Posted on

Web3 backend and smart contract development for Python developers Musical NFTs part 14: Buy NFT crypto buyer

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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


    $cp smart-contracts/brownie_musical_nfts/build/contracts/MusicNFT.json ./static
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

step 2: define signer

>const signer = new ethers.Wallet(private_key, provider);
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
    }
Enter fullscreen mode Exit fullscreen mode

*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)
    }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 %}
Enter fullscreen mode Exit fullscreen mode

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

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

In this repo you can find latest code

Top comments (0)