iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Modern Web

All In One NFT Website Development系列 第 28

Day 28【Deploy NFT - Deploy the Lazy Mint in Website】Vitalik Buterin mining Ethereum

// Thats proof of work

【前言】
今天終於來到這個 Project 測試的最後一部囉,如果這個測試完成的話,就真的可以等待產品上架然後推上主網了!那廢話不多說就直接進到最後一步的 Minting dAPP 吧,這個步驟的目的是可以讓顧客在我們網頁上 Mint 我們 Deploy 的 NFT!

【Do We Need a Back-End?】
首先我們先探討一下,這個部分我們需不需要後端呢?其實區塊鏈的目的就是建造一個去中心化的資料庫,理想的情況下我們應該把資料存在鏈上,並且利用智能合約打開一個 API 接口讓 dAPP 可以取用需要的資源,或做交易之類的動作。如果存在鏈上的成本太高,也可以利用 IPFS 或類似的分散式資料庫,來把資料存在雲端去中心化系統之中!但如果擁有一個後端伺服器的話,就等於是中心化系統了對吧!

197411698_4043732392342275_4189065925703966709_n.jpg

【ABI】
那要在 web3.js 或 ethers.js 之中連動到智能合約,那我們就必須得到智能合約的 ABI,可以利用下面這張圖的指示取得自己智能合約的 ABI。

圖片 1.png

得到 ABI 之後在前後加上括號,因為複製出來的沒有 "abi":,無法取用。之後把它存 src/contracts/SmartContract.json 之中!

{
  "abi": [
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "_name",
          "type": "string"
        },
        {
          "internalType": "string",
          "name": "_symbol",
          "type": "string"
        },
        {
          "internalType": "string",
          "name": "_initBaseURI",
          "type": "string"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    ...
    }
  ]
}

【Reducer → Store → Action → APP】
下一步我們要使用 Reducer 來傳遞資料,因為在 React Components 之中傳遞物件是一個非常大的課題,這邊我先不多說,就直接看程式碼吧!

Redux.gif

├───App.js
├───Contract
│   └───SmartContract.json
├───Redux
****│   ├───store.js
│   ├───blockchain
│   │   ├───blockchainAction.js
│   │   └───**blockchainReducer.js**
└───└───data
        ├───dataAction.js
        └───**dataReducer.js**

src/redux/blockchain/blockchainReducer.js 之中,我們先初始化一些所需的資料,並且處理「取得帳戶以及合約地址」的動作。

const initialState = {
  loading: false,
  account: null,
  smartContract: null,
  web3: null,
  errorMsg: "",
};

const blockchainReducer = (state = initialState, action) => {
  switch (action.type) {
    case "CONNECTION_REQUEST":
      return {
        ...initialState,
        loading: true,
      };
    case "CONNECTION_SUCCESS":
      return {
        ...state,
        loading: false,
        account: action.payload.account,
        smartContract: action.payload.smartContract,
        web3: action.payload.web3,
      };
    case "CONNECTION_FAILED":
      return {
        ...initialState,
        loading: false,
        errorMsg: action.payload,
      };
    case "UPDATE_ACCOUNT":
      return {
        ...state,
        account: action.payload.account,
      };
    default:
      return state;
  }
};

export default blockchainReducer;

src/redux/data/dataReducer.js

const initialState = {
  loading: false,
  name: "",
  error: false,
  errorMsg: "",
};

const dataReducer = (state = initialState, action) => {
  switch (action.type) {
    case "CHECK_DATA_REQUEST":
      return {
        ...initialState,
        loading: true,
      };
    case "CHECK_DATA_SUCCESS":
      return {
        ...initialState,
        loading: false,
        name: action.payload.name,
      };
    case "CHECK_DATA_FAILED":
      return {
        ...initialState,
        loading: false,
        error: true,
        errorMsg: action.payload,
      };
    default:
      return state;
  }
};

export default dataReducer;

【Reducer → Store → Action → APP】

├───App.js
├───Contract
│   ├───SmartContract.json
├───Redux
****│   ├───**store.js**
│   ├───blockchain
│   │   ├───blockchainAction.js
│   │   └───blockchainReducer.js
└───└───data
        ├───dataAction.js
        └───dataReducer.js

src/redux/store.js 之中,我們把其當作中間層,把上述兩個 Reducer 匯入。

import { applyMiddleware, compose, createStore, combineReducers } from "redux";
import thunk from "redux-thunk";
import blockchainReducer from "./blockchain/blockchainReducer";
import dataReducer from "./data/dataReducer";

const rootReducer = combineReducers({
  blockchain: blockchainReducer,
  data: dataReducer,
});

const middleware = [thunk];
const composeEnhancers = compose(applyMiddleware(...middleware));

const configureStore = () => {
  return createStore(rootReducer, composeEnhancers);
};

const store = configureStore();

export default store;

【Reducer → Store → Action → APP】

├───App.js
├───Contract
│   ├───SmartContract.json
├───Redux
****│   ├───store.js
│   ├───blockchain
│   │   ├───**blockchainAction.js**
│   │   └───blockchainReducer.js
└───└───data
        ├───**dataAction.js**
        └───dataReducer.js

來到 src/redux/data/dataAction.js 之中,我們把存在 store.js 裡面的資料抓進來。這邊 call 出 Name。

// log
import store from "../store";

const fetchDataRequest = () => {
  return {
    type: "CHECK_DATA_REQUEST",
  };
};

const fetchDataSuccess = (payload) => {
  return {
    type: "CHECK_DATA_SUCCESS",
    payload: payload,
  };
};

const fetchDataFailed = (payload) => {
  return {
    type: "CHECK_DATA_FAILED",
    payload: payload,
  };
};

export const fetchData = (account) => {
  return async (dispatch) => {
    dispatch(fetchDataRequest());
    try {
      let name = await store
        .getState()
        .blockchain.smartContract.methods.name()
        .call();

      dispatch(
        fetchDataSuccess({
          name,
        })
      );
    } catch (err) {
      console.log(err);
      dispatch(fetchDataFailed("Could not load data from contract."));
    }
  };
};

圖片 4.png

src/redux/blockchain/blockchainAction.js 之中,我們也把資料匯入。因為會使用到 ABI 所以記得要把 SmartContract.json 匯入。此外我們當前是在測試網之中測試,所以 networkId 要特別去改。

// constants
import Web3 from "web3";
import SmartContract from "../../contracts/SmartContract.json";
// log
import { fetchData } from "../data/dataActions";

const connectRequest = () => {
  return {
    type: "CONNECTION_REQUEST",
  };
};

const connectSuccess = (payload) => {
  return {
    type: "CONNECTION_SUCCESS",
    payload: payload,
  };
};

const connectFailed = (payload) => {
  return {
    type: "CONNECTION_FAILED",
    payload: payload,
  };
};

const updateAccountRequest = (payload) => {
  return {
    type: "UPDATE_ACCOUNT",
    payload: payload,
  };
};

export const connect = () => {
  return async (dispatch) => {
    dispatch(connectRequest());
    if (window.ethereum) {
      let web3 = new Web3(window.ethereum);
      try {
        const accounts = await window.ethereum.request({
          method: "eth_requestAccounts",
        });
        const networkId = await window.ethereum.request({
          method: "net_version",
        });
        // const NetworkData = await SmartContract.networks[networkId];
        if (networkId == 4) {
          const SmartContractObj = new web3.eth.Contract(
            SmartContract.abi,
            // NetworkData.address
            "0xd768c48ae485325f834a4480188b4a67da89d8e0"
          );
          dispatch(
            connectSuccess({
              account: accounts[0],
              smartContract: SmartContractObj,
              web3: web3,
            })
          );
          // Add listeners start
          window.ethereum.on("accountsChanged", (accounts) => {
            dispatch(updateAccount(accounts[0]));
          });
          window.ethereum.on("chainChanged", () => {
            window.location.reload();
          });
          // Add listeners end
        } else {
          dispatch(connectFailed("Change network to Polygon."));
        }
      } catch (err) {
        dispatch(connectFailed("Something went wrong."));
      }
    } else {
      dispatch(connectFailed("Install Metamask."));
    }
  };
};

export const updateAccount = (account) => {
  return async (dispatch) => {
    dispatch(updateAccountRequest({ account: account }));
    dispatch(fetchData(account));
  };
};

特別說一下這邊為什麼可以直接使用 window.ethereum 的原因我們之前介紹 ethers.js 有解釋過,還記得當初我在【連動 MetaMask】的時候對這個有很大的疑惑呢!

【Reducer → Store → Action → APP】

├───**App.js**
├───Contract
│   ├───SmartContract.json
├───Redux
****│   ├───store.js
│   ├───blockchain
│   │   ├───blockchainAction.js
│   │   └───blockchainReducer.js
└───└───data
        ├───dataAction.js
        └───dataReducer.js

src/App.js 先把 store 傳入。

...
import store from "./redux/store";
import { Provider } from "react-redux";
...
export function AccountBox(props) {
  ...
  return (
	  <Provider store={store}>
      ...
			<SignupForm />
	  </Provider>
  );
}

【Minting Form】

首先導入套件,以及需要的資料。

import { useDispatch, useSelector } from "react-redux";
import { connect } from "./redux/blockchain/blockchainActions";
import { fetchData } from "./redux/data/dataActions";
...

初始化,並且利用 useDispatch, useSelector 把剛剛寫的 blockchain 抓進來。

export function SignupForm(props) {
	...
  const [inputID, setInputID] = useState("");
  const [claiming, setClaiming] = useState(false);
  const dispatch = useDispatch();
  const blockchain = useSelector((state) => state.blockchain);

  useEffect(() => {
    if (blockchain.account !== "" && blockchain.smartContract !== null) {
      dispatch(fetchData(blockchain.account));
    }
  }, [blockchain.smartContract, dispatch]);

這邊利用我們之前在介紹 web3.js 提到的 methods 來獲取我們在撰寫智能合約裡面寫的 mint 函數。以及要把價格改成以 Wei 為單位。

const claimNFTs = (_amount) => {
    setClaiming(true);
    blockchain.smartContract.methods.mint(blockchain.account, _amount).send({
      from: blockchain.account,
      value: blockchain.web3.utils.toWei((0.02 * _amount).toString(), "ether"),
			// 這裡記得要傳入字串

    }).once("error", (err) => {
      setClaiming(false);
    }).then((receipt) => {
      setClaiming(false);
    })
  };

// IN SMART CONTRACT
function mint(address _to, uint256 _mintAmount) public payable {
    ...
 }

最後我們把前端的東西安排好就好!這裡可以直接選要 Mint 的數量並且連線到智能合約,最後按下 Mint 就大功告成了!

return (
    <BoxContainer>
      ...
      {blockchain.account === "" || blockchain.smartContract === null ? (
        <SubmitButton1
          onClick={(e) => {
            e.preventDefault();
            dispatch(connect());
          }} >
          {"Connect!"}
        </SubmitButton1>
      ) : (
        <SubmitButton1 disabled={claiming}
          onClick={(e) => {
            e.preventDefault();
            if(inputID === ""){
              claimNFTs(1);
            }
            else{
              claimNFTs(inputID);
            }
            
          }} >
          {claiming ? "Claiming" : "Mint!"}
        </SubmitButton1>
      )}
		...
    </BoxContainer>
  );
}

圖片 3.png

【小結】
到這裡全部的測試流程都完成啦,明天就可以進入上主鏈,然後打完收工!!!真是開心我快要可以休息了,自從搞了這個 Project 真的是沒有一天睡好嗚嗚。

p7XNxWU.jpg

【參考資料 - Minting dAPP】
How do you get a json file (ABI) from a known contract address?
Designing a minting app
Code a 10000 NFT Minting Dapp part 1
How to Mint an NFT (Part 2/3 of NFT Tutorial Series) | ethereum.org
?How to Mint an NFT Using Web3.js
?How to Mint an NFT with Ethers.js
Create a Complete NFT App - Smart contract, Backend, Frontend


上一篇
Day 27【Deploy NFT - Deploy on Testnet】Hey Listen, I QUIT!!
下一篇
Day 29【Deploy NFT - Deploy on Mainnet】你看,出來了
系列文
All In One NFT Website Development30

尚未有邦友留言

立即登入留言