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


<<:  25. 从学生社团到技术社群 x 技术年会 x COSCUP

>>:  Day 28: Incident Response on AWS

[Day14] 架设 Nginx 当我们的 Web Server

.NET 5 Web API 布署到 Linux 上执行的时候,会跑在一个 Kestrel 服务器上...

第7砍 - 第一滴血

上班Day2 目前都还蛮好玩的 学到很多东西 发现以之前所学还是太浅了 继续努力:) void OS...

【第11天】训练模型-Keras Application重要函数

摘要 资料集预处理 1.1 ImageDataGenerator 1.2 flow_from_dir...

Day 8 - Plus One

大家好,我是毛毛。ヾ(´∀ ˋ)ノ 废话不多说开始今天的解题Day~ 66. Plus One Qu...

D-20 非同步 ? async ? await

如何同时进行 昨天的日子中小光学到了如何使用delegate跟Linq,这让小光在资料处理中的程序开...