Web3 backend and smart contract development for Python developers Musical NFTs part 5: writing smart contracts
Published: 2023-09-25 12:52:07

We will use some `OpenZeppelin` contracts and that is why we need first to installed them. Brownie comes with package manager we can use for this purpose. Universal sintax is `brownie pm install [ORGANIZATION]/[REPOSITORY]@[VERSION]` in our case

    $brownie pm install OpenZeppelin/openzeppelin-contracts@4.5.0

    #check if package is properly installed
    $brownie pm list
    Brownie v1.19.3 - Python development framework for Ethereum

    The following packages are currently installed:

    OpenZeppelin
    └─OpenZeppelin/openzeppelin-contracts@4.5.0


Now in our `nft_package.sol` test contract try to import newly installed OpenZeppelin contract ERC721. Open `nft_package.sol` from `./contracts`

    // SPDX-License-Identifier: MIT

    # Here we are importing newly installed ERC721 contract
    import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/ERC721.sol";

    #defining compiler
    pragma solidity 0.8.13;

    contract NftPackage {

        uint256 public test = 10;
        
    }

<br>

From command line:

    $brownie compile
    Brownie v1.19.3 - Python development framework for Ethereum

    Compiling contracts...
    Solc version: 0.8.13
    Optimizer: Enabled  Runs: 200
    EVM Version: Istanbul
    Generating build data...
    - OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Receiver
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Metadata
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Address
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Context
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Strings
    - OpenZeppelin/openzeppelin-contracts@4.5.0/ERC165
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC165
    - NftPackage

    Project has been compiled. Build artifacts saved at /my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts

We successfully installed and imported OpenZepplein ERC721 inside our contract and now we can start to code our Muscial NFT contract.

Idea here is to have musical NFT based on `ERC721` contract with few simple functionalities like minting and adding tokenURI. Then we will inherit from few other `OpenZepellin` contracts like `ownable` (track and manage via modifier who have right to call certain functions), `counters` (count number of newly minted tokens) and `URIStorage` (linking our newly minted NFT to NFT metadata on IPFS).

I will write whole contract here and explain with inline comments what is going on (very useful tool for this kind of basic contracts and experimentation is [OpenZepelinWizard](https://www.openzeppelin.com/contracts))

We will deploy two main contracts: MockUSDC and MusicNFT. MockUSDC is basically ERC20 that mimic USDC on Mumbai testnet. Reason why we don't use real test USDC is fact that is very hard to obtain them on Mumbai and MockUSDC will play that role just fine. Second contract is ERC721 where we simply mint new musical NFTs and assigne new URI. Here are contracts (with explanations inline). 

ERC20 MockUSDC


    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.9;

    import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC20/ERC20.sol";
    import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/access/Ownable.sol";

    contract MockUSDC is ERC20, Ownable {
            // Mint to deoployer all MUSDC tokens
            constructor() ERC20("MockUSDC", "MUSDC") {
            _mint(msg.sender, 1000000000000000 * 10 ** decimals());
        }
    }

From command line
    
    $brownie compile

    Brownie v1.19.3 - Python development framework for Ethereum

    Compiling contracts...
    Solc version: 0.8.21
    Optimizer: Enabled  Runs: 200
    EVM Version: Istanbul
    Generating build data...
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Ownable
    - OpenZeppelin/openzeppelin-contracts@4.5.0/ERC20
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20Metadata
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Context
    - MockUSDC

    Project has been compiled. Build artifacts saved at /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts

We have our mock token ready to be used

And here is MusicNFT contracts

```

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/ERC721.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/access/Ownable.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/utils/Counters.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC20/IERC20.sol";


contract MusicNFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    IERC20 public immutable USDC;

    uint256 public immutable maxNFT;

    uint256 public NFTPriceInUSDC;

    // Event to be emited ones we mint new NFT token
    event newNFTMinted(uint256 numberOfNFT, address owner);

    // Event to be emited ones new price is setted
    event newPrice(string message, uint256 newPrice);

    constructor(address _usdc, uint256 _maxNft) ERC721("MuscialNFT", "MFT") {
        // instatiating mock ERC20 token (that we previusly deployed)
        USDC = IERC20(_usdc);    
        // Defining maximum number of tokens this contract cna mint
        maxNFT = _maxNft;     
    }



    /** 
        This function is used for minting new NFTs. We need to pass token uri (uniform resource 
        Identifier) from IPFS (via Pinata).  Metadata of some NFT is basically 
        detail explanation of NFT content: characteristic, link to song in our 
        case and other relevant information. Second argument that we need to pass is number of 
        NFTs we want to buy
    */

    // For crypto buyers
    function buyNFT(string memory uri, uint256 nftCount) public {
        // Here we are cheking if desired number of NFTs is bigger then one
        require(nftCount > 0, "You have to mint at least one Track Pack NFT.");
        // Here we are cheking if number of already minted NFT is bigger then max allowed to be minted
        require(nftCount + _tokenIdCounter.current() <= maxNFT, "There aren't enough Track Pack NFTs for this drop for you to mint you amount you chose. Another drop will be available soon!");
        // Here we check if user balance in MockUSDC is bigger the number NFTs he want to buy * price of NFTs
        require(USDC.balanceOf(msg.sender) >= NFTPriceInUSDC * nftCount, "You don't have enough USDC to pay for the Track Pack NFT(s).");
        // Check if total amount of approved MOckUSDC to this contract tokens is bigger then number of NFTs * price  
        require(USDC.allowance(msg.sender, address(this)) >= NFTPriceInUSDC * nftCount, "You haven't approved this contract to spend enough of your USDC to pay for the Track Pack NFT(s).");
        // If everything goes ok then make MockUSDC tokens transfer from user account to this contract
        USDC.transferFrom(msg.sender, address(this), NFTPriceInUSDC * nftCount);
        // Take new NFT token ID
        for (uint256 x = 0; x < nftCount; x++) {
            uint256 tokenId = _tokenIdCounter.current();
            // Increment counter
            _tokenIdCounter.increment();
            // Mint new token
            _safeMint(msg.sender, tokenId);
            // Set new token URI to token ID
            _setTokenURI(tokenId, uri);
            // Emit event about succesful minting
            emit newNFTMinted(tokenId, msg.sender);
        }
    }
    
    // When credit card buyers buy new NFT we need to mint to custodial wallet. Same as beafore. Diffrence is: onnly owner can mint, 2. there is no need to pay in usdc
    // it is already done by credit card. 
    function createNFT(address custodialWallet, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(custodialWallet, tokenId);
        _setTokenURI(tokenId, uri);
        emit newNFTMinted(tokenId, custodialWallet);
    }
    // This function is used to set new NFT price if necessary
    function setNFTPrice(uint256 _newPrice) public onlyOwner {
        NFTPriceInUSDC = _newPrice;
        emit newPrice("New price is seted", _newPrice);
    }

    // Get token URI function
    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }


    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

}

```


Now we have also our second contract written. Lets compile it and  see if everything works well

    $brownie compile
    Brownie v1.19.3 - Python development framework for Ethereum

    Compiling contracts...
    Solc version: 0.8.21
    Optimizer: Enabled  Runs: 200
    EVM Version: Istanbul
    Generating build data...
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Ownable
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20
    - OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Receiver
    - OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721URIStorage
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Metadata
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Address
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Context
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Counters
    - OpenZeppelin/openzeppelin-contracts@4.5.0/Strings
    - OpenZeppelin/openzeppelin-contracts@4.5.0/ERC165
    - OpenZeppelin/openzeppelin-contracts@4.5.0/IERC165
    - MusicNFT

    Project has been compiled. Build artifacts saved at /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts

With this two contract we can move to deployment to Mumbai and start with integration with our Django backend. 

Code can be found in github [repo](https://github.com/ilijapet/musical_nft_thumbnails)