[NFT] Pythonで独自コントラクトを作成しアイコンをmint(NFT化)してみた

# 今回の記事の目的
OpenSeaなどのNFTマーケットを利用すれば簡単にNFT化できるが、今回はPython(一部solidity)で独自にやってみることによってNFT化とはどんなことをやっているか理解する
※詳しい用語の説明はしてませんリンクを貼っていますので詳しく知りたかたはそちらを参考にしてください！

# 記事の流れ
- NFTとは
- NFTにしたい画像とmetadataをIPFSにアップロードする
- 独自コントラクトを作成する
- mintする
- OpenSeaからmintしたNFTを確認する

# NFTとは
NFTとは、「Non Fungible Token(ノン ファンジブル トークン: 非代替性トークン)」という意味でブロックチェーン技術を使用した、替えが効かない、唯一無二と証明されたトークンのことです。
このトークンとデジタルデータを紐づけることで唯一無二のデジダルデータの証明ができるということです。

[NFTとは](https://minkabu.co.jp/choice/cc-what-is-nft/)

# さっそくやってみる

### まずはNFTにしたい画像とmetadataをIPFSにアップロードします
- IPFSとは非中央集権の分散型ファイルシステムのことです。
    - NFTにする画像の保存場所はどこでも(S3等)でもいいのですがweb3のdecentralized的な考え方でいうとIPFSに保存するのが一般的です。
  
[IPFSとは](https://ipfs-book.decentralized-web.jp/what_is_ipfs/)   
- IPFSに画像をアップロードするために今回はpinataというサービスを使用します。
    - pinataにサインアップ後NFTにしたい画像をアップロードします。
    - 画像のURIが発行されます
        - 今回利用する画像は(https://gateway.pinata.cloud/ipfs/QmZKRfNt52ZdQtBpz3C33ujB6XUKsn1oj3WrJjK8BdJAiX)
    - 次にmetadataを作成します
        - 実際にブロックチェーン上に画像を書き込むわけではなくmetadataの中に画像のURIを記載しそのmetadataのURIをブロックチェーン上に書き込みます。
        - 今回作成したmetadataは下記です。
            ```json
           {
                "name": "harrier's icon",
                "image": "https://gateway.pinata.cloud/ipfs/QmZKRfNt52ZdQtBpz3C33ujB6XUKsn1oj3WrJjK8BdJAiX",
                "description": "Hyper NFT Creator harrier's icon"
           }
            ```
       - 作成したmetadataをpinataにアップロードします。
       - metadataのURIが発行されるのでこれを後ほどブロックチェーン上に記録します。(https://gateway.pinata.cloud/ipfs/QmbuizjxTp7oLTzAwC2RDc9KLHEvaz6883bk3E9tqfrmSn)


[pinata](https://www.pinata.cloud/)
[pinataの使いかた](https://gomacoin.com/howtousepinata/)

### 続いて独自コントラクトを作成する
- 独自コントラクトとは
    - ユーザーが独自に作ったスマートコントラクトのことを独自コントラクトと言います。
        - OpenSeaで画像をアップロードしてNFTを作成する場合はOpenSeaの共有コントラクトを使うことになります。
    - スマートコントラクトとはブロックチェーン上で動く契約(プログラム)のこと。
        - 独自コントラクトを作成するとこの契約(プログラム)を自由に書くことができる
        
- solidityを使ってコントラクトを作成
    - solidityとはブロックチェーン上で動くスマートコントラクトを作成するための言語です
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

// ここではopenzeppelinというライブラリを使用しERC721の規格に即したコントラクトを作成しています。
contract CONTRACT is ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("HARRIER's Contract", "HAR") {}

    // スマートコントラクトとしてmintという関数を実装しています。
    // この中でtoAddressに対してNFTを発行しています
    function mint(address toAddress, string memory matadataURI)
        public onlyOwner
        returns (uint256)
    {
        uint256 newTokenId = _tokenIds.current();
        _mint(sendAddress, newTokenId);
        _setTokenURI(newTokenId, matadataURI);

        _tokenIds.increment();
        return newTokenId;
    }
}
```

- solidityのコードをpythonでコンパイルして実行
```python
import os
from logging import getLogger, INFO, basicConfig

from web3 import Web3
from web3.middleware import geth_poa_middleware
from solcx import compile_source, install_solc
from dotenv import load_dotenv

basicConfig(level=INFO)
logger = getLogger(__name__)
load_dotenv()


if __name__ == "__main__":
    # infuraというサービスのurl
    # https://infura.io/
    INFURA_URL = os.getenv("INFURA_URL")
    # 自分のwalletのaddress(コントラクトの作成にwalletが必要になります。今回はmetamaskを使用してます。)
    WALLET_ADDRESS = os.getenv("WALLET_ADDRESS")
    # metamaskのsecret_key(絶対に外部に漏らしてはいけません)
    SECRET_KEY = os.getenv("SECRET_KEY")

    w3 = Web3(Web3.HTTPProvider(INFURA_URL))
    wallet_address = w3.toChecksumAddress(WALLET_ADDRESS)
    w3.middleware_onion.inject(geth_poa_middleware, layer=0)

    # 先ほど作成したsolidityのコードを読み込む
    with open("contract.sol", "r") as file:
        source = file.read()
        install_solc("0.8.14")
        compiled_sol = compile_source(
            source,
            output_values=["abi", "bin"],
            solc_version="0.8.14",
            base_path="node_modules",
        )
    # コンパイルしたデータからコントラクトIDとコントラクタインターフェイスを取得
    contract_id = next(iter(compiled_sol))
    contract_interface = compiled_sol[contract_id]
    # コントラクタを作成するためのガス代と上限の設定
    base_tx = {
        "nonce": w3.eth.get_transaction_count(wallet_address),
        "gas": 10000000,
        "gasPrice": w3.toWei("2", "gwei"),
    }
　　　　　　　　# コントラクトの作成
    contract = contract = w3.eth.contract(
        abi=contract_interface["abi"], bytecode=contract_interface["bin"]
    )
    # トランザクション作成
    contract_tx = contract.constructor().buildTransaction(base_tx)
    # 秘密鍵を使用してトランザクションに署名
    signed_tx = w3.eth.account.sign_transaction(contract_tx, private_key=SECRET_KEY)
    # トランザクション送信
    w3.eth.send_raw_transaction(signed_tx.rawTransaction)
    tx_hash = w3.toHex(w3.keccak(signed_tx.rawTransaction))
　　　　　　　　# トランザクション結果を取得
    receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
    contract_address = receipt["contractAddress"]
    logger.info(f"contract_address: {contract_address}")

```

- これでコントラクトの作成が完了です
    - 出力結果はこのようになってます `contract_address: 0xc34a91401f25A81E18D58DFFC8feC5fAE5D39116`
    - https://mumbai.polygonscan.com/ こちらで出力されたcontract_addressを入れると作成されてるのがわかります
    ![スクリーンショット 2022-06-07 15.21.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310614/d26ca82a-d659-9871-8821-983133db5780.png)

### 次にmintしていきます！

- mintとはNFTを発行する事です

- pythonでmintする

```python
import os
from logging import getLogger, INFO, basicConfig

from web3 import Web3
from web3.middleware import geth_poa_middleware
from solcx import compile_source, install_solc
from dotenv import load_dotenv

basicConfig(level=INFO)
logger = getLogger(__name__)
load_dotenv()


if __name__ == "__main__":
    INFURA_URL = os.getenv("INFURA_URL")
    # NFTを発行したいwalletのアドレス
    WALLET_ADDRESS = os.getenv("WALLET_ADDRESS")
    # walletのsecret_key
    SECRET_KEY = os.getenv("SECRET_KEY")
    # pinataにアップロードしたmetadataのURI
    METADATA_URI = os.getenv("METADATA_URI")
    # 先ほど出力されたcontract_address
    CONTRACT_ADDRESS = os.getenv("CONTRACT_ADDRESS")

    w3 = Web3(Web3.HTTPProvider(INFURA_URL))
    wallet_address = w3.toChecksumAddress(WALLET_ADDRESS)
    w3.middleware_onion.inject(geth_poa_middleware, layer=0)
    # solidityのコードを読み込む
    with open("contract.sol", "r") as file:
        source = file.read()
        install_solc("0.8.14")
        compiled_sol = compile_source(
            source,
            output_values=["abi", "bin"],
            solc_version="0.8.14",
            base_path="node_modules",
        )
    # コンパイルしたデータからコントラクトIDとコントラクタインターフェイスを取得
    contract_id = next(iter(compiled_sol))
    contract_interface = compiled_sol[contract_id]
    # コントラクタを作成するためのガス代と上限の設定
    base_tx = {
        "nonce": w3.eth.get_transaction_count(wallet_address),
        "gas": 10000000,
        "gasPrice": w3.toWei("2", "gwei"),
    }
    # コントラクトの取得
    contract_address = w3.toChecksumAddress(CONTRACT_ADDRESS)
    contract = w3.eth.contract(
        address=contract_address,
        abi=contract_interface["abi"],
        bytecode=contract_interface["bin"],
    )
    # スマートコントラクトとして登録したmint関数を呼び出しmintする
    mint_tx = contract.functions.mint(wallet_address, METADATA_URI).buildTransaction(
        base_tx
    )
    # トランザクションに署名
    sign_mint_tx = w3.eth.account.sign_transaction(mint_tx, private_key=SECRET_KEY)
    # トランザクション送信
    w3.eth.send_raw_transaction(sign_mint_tx.rawTransaction)
    tx_hash = w3.toHex(w3.keccak(sign_mint_tx.rawTransaction))
    # トランザクションが終わるのを待つ
    w3.eth.wait_for_transaction_receipt(tx_hash)
    # Done
    logger.info("Mint complete.")

```

- これでmintが完了になります。
- OpenSeaからmintができているか確認してみます。
    - 　https://testnets.opensea.io/
    - 　コントラクトアドレスで検索をかけてみると作成した`HARRIERs Contract`の中に`harrier's icon`が登録されてるのが確認できます。
    ![スクリーンショット 2022-06-07 15.41.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310614/978e0337-d37c-770c-5894-8d9c6aae11d1.png)


# 最後に
現状NFTアートの使い道としては売買、投機、コレクション的な目的しか有りませんが、Twitter, Instagram, FacebookがNFTアイコンを採用すると発表してますのでその際はぜひ皆さんもmintしてみてはいがでしょうか！

# 参考
https://note.com/yutakikuchi/n/n03dc50bc5935

https://minkabu.co.jp/choice/cc-what-is-nft/

https://ipfs-book.decentralized-web.jp/what_is_ipfs/

https://www.caica.jp/media/crypto/nft-mint/

https://www.sbbit.jp/article/fj/40394