// Thats proof of work
【前言】
今天终於来到这个 Project 测试的最後一部罗,如果这个测试完成的话,就真的可以等待产品上架然後推上主网了!那废话不多说就直接进到最後一步的 Minting dAPP 吧,这个步骤的目的是可以让顾客在我们网页上 Mint 我们 Deploy 的 NFT!
【Do We Need a Back-End?】
首先我们先探讨一下,这个部分我们需不需要後端呢?其实区块链的目的就是建造一个去中心化的资料库,理想的情况下我们应该把资料存在链上,并且利用智能合约打开一个 API 接口让 dAPP 可以取用需要的资源,或做交易之类的动作。如果存在链上的成本太高,也可以利用 IPFS 或类似的分散式资料库,来把资料存在云端去中心化系统之中!但如果拥有一个後端服务器的话,就等於是中心化系统了对吧!
【ABI】
那要在 web3.js 或 ethers.js 之中连动到智能合约,那我们就必须得到智能合约的 ABI,可以利用下面这张图的指示取得自己智能合约的 ABI。
得到 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 之中传递物件是一个非常大的课题,这边我先不多说,就直接看程序码吧!
├───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."));
}
};
};
在 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>
);
}
【小结】
到这里全部的测试流程都完成啦,明天就可以进入上主链,然後打完收工!!!真是开心我快要可以休息了,自从搞了这个 Project 真的是没有一天睡好呜呜。
【参考资料 - 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
.NET 5 Web API 布署到 Linux 上执行的时候,会跑在一个 Kestrel 服务器上...
上班Day2 目前都还蛮好玩的 学到很多东西 发现以之前所学还是太浅了 继续努力:) void OS...
摘要 资料集预处理 1.1 ImageDataGenerator 1.2 flow_from_dir...
大家好,我是毛毛。ヾ(´∀ ˋ)ノ 废话不多说开始今天的解题Day~ 66. Plus One Qu...
如何同时进行 昨天的日子中小光学到了如何使用delegate跟Linq,这让小光在资料处理中的程序开...