从零开始构建 NFT 网站(3)- 前端应用
源码地址:nft-minter-tutorial。这个是 alchemy 的示例代码,使用 React 和 Alchemy SDK 实现,我使用了这段代码的 UI,并用 Vue3 和 Ethers.js 重新实现了一次。
创建项目
首先,使用 Vite 创建一个 Vue3 项目(假设我们对 Vue 或 React 一类的前端框架已经有了一定的了解)
npm create vite@latest
安装相关依赖:
npm install --save axios ethers
然后,编写页面 UI:
代码:
<script setup>
import { ref } from "vue";
defineProps({
msg: String,
});
const walletAddress = ref("");
const url = ref("");
const name = ref("");
const description = ref("");
const status = ref("");
const connectWalletPressed = () => {
// 处理连接钱包按钮的逻辑
};
const onMintPressed = () => {
// 处理 Mint NFT 按钮的逻辑
};
</script>
<template>
<h1>{{ msg }}</h1>
<div class="Minter">
<button id="walletButton" @click="connectWalletPressed">
<span v-if="walletAddress.length > 0">
Connected: {{ walletAddress.substring(0, 6) }}...
{{ walletAddress.substring(38) }}
</span>
<span v-else> Connect Wallet </span>
</button>
<br />
<form>
<h2>🖼 Link to asset:</h2>
<input
type="text"
placeholder="e.g. https://gateway.pinata.cloud/ipfs/<hash>"
v-model="url"
/>
<h2>🤔 Name:</h2>
<input type="text" placeholder="e.g. My first NFT!" v-model="name" />
<h2>✍️ Description:</h2>
<input
type="text"
placeholder="e.g. Even cooler than cryptokitties ;)"
v-model="description"
/>
</form>
<button id="mintButton" @click="onMintPressed">Mint NFT</button>
<p id="status">
{{ status }}
</p>
</div>
</template>
<style scoped></style>
连接 Metamask
connectWallet
首先,建立一个 utils 文件夹,再创建一个 interact.js 文件,把 connectWalletPressed 函数里的逻辑抽象出来:
// interact.js
export const connectWallet = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_requestAccounts",
});
const obj = {
status: "👆🏽 Write a message in the text-field above.",
address: addressArray[0],
};
return obj;
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
};
}
} else {
return {
address: "",
status: "you must install Metamask in your browser",
};
}
};
这一步是检测 Metamask 是否安装,如果已安装,就唤起钱包请求连接。
然后,在 connectWalletPressed 函数里使用这个方法:
const connectWalletPressed = async () => {
// 处理连接钱包按钮的逻辑
const walletResponse = await connectWallet();
status.value = walletResponse.status;
walletAddress.value = walletResponse.address;
};
点击按钮,在 Metamask 上点击确认,成功后会显示 Connected: 钱包地址
getCurrentWalletConnected
新的问题是,每次刷新,都要重新连接 Metamask 钱包,我们可以再写一个方法。
export const getCurrentWalletConnected = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_accounts",
});
if (addressArray.length > 0) {
return {
address: addressArray[0],
status: "👆🏽 Write a message in the text-field above.",
};
} else {
return {
address: "",
status: "🦊 Connect to Metamask using the top right button.",
};
}
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
};
}
} else {
return {
address: "",
status: "you must install Metamask in your browser",
};
}
};
getCurrentWalletConnected 和 connectWallet 方法的区别是,getCurrentWalletConnected 调用的不是 eth_requestAccounts
,而是 eth_accounts
,如果我们已经连接过,它将返回一个包含已连接到 DApp 的以太坊地址的数组。
我们可以在 onMounted 里调用这个方法:
onMounted(async () => {
const walletResponse = await getCurrentWalletConnected();
status.value = walletResponse.status;
walletAddress.value = walletResponse.address;
});
addWalletListener
钱包设置的最后一步是监听钱包,当钱包状态发生变化时(断开或切换账户),UI 可以更新。
在 Minter.vue 里,添加 addWalletListener 函数:
const addWalletListener = () => {
if (window.ethereum) {
window.ethereum.on("accountsChanged", (accounts) => {
if (accounts.length > 0) {
walletAddress.value = accounts[0];
status.value = "👆🏽 Write a message in the text-field above.";
} else {
setWawalletAddress.value = "";
status.value = "🦊 Connect to Metamask using the top right button.";
}
});
} else {
status.value = "You must install Metamask in your browser.";
}
};
最后,在 onMounted 里调用它
onMounted(async () => {
const walletResponse = await getCurrentWalletConnected();
status.value = walletResponse.status;
walletAddress.value = walletResponse.address;
addWalletListener();
});
现在,所有钱包功能的设置都已经完成了,下一步是铸造 NFT。
使用 Pinata 把元数据上传到 IPFS
第二部分里已经使用过 Pinata 了,现在我需要用代码和 Pinata 交互。
首先,到 Pinata 上创建一个 API Key,复制 API Key 和 API Secret 到 .env 里:
VITE_PINATA_KEY = <pinata-api-key>
VITE_PINATA_SECRET = <pinata-api-secret>
然后,在 utils 文件夹里创建一个名为 pinata.js 的文件,并从 .env 文件导入 Key 和 Secret:
const key = process.env.VITE_PINATA_KEY;
const secret = process.env.VITE_PINATA_SECRET;
接下来,使用 axios 向 Pinata 发送请求:
import axios from "axios";
const key = process.env.VITE_PINATA_KEY;
const secret = process.env.VITE_PINATA_SECRET;
export const pinJSONToIPFS = async (JSONBody) => {
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
return axios
.post(url, JSONBody, {
headers: {
pinata_api_key: key,
pinata_secret_api_key: secret,
},
})
.then(function (response) {
return {
success: true,
pinataUrl:
"https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,
};
})
.catch(function (error) {
console.log(error);
return {
success: false,
message: error.message,
};
});
};
加载智能合约
这里我用 ethers.js 来和合约交互。
回到 interact.js 文件,在文件顶部添加代码:
import { ethers } from "ethers";
import { pinJSONToIPFS } from "./pinata.js";
import contract from "./MyNFT.json";
然后添加合约 ABI 和合约地址(上一部分添加过的):
import { ethers } from "ethers";
import { pinJSONToIPFS } from "./pinata.js";
import contract from "./MyNFT.json";
const contractABI = contract.abi;
const contractAddress = "0x7130Df343097ED88d112Cec1B366bDaa3530a67e";
实现 mintNFT 函数
mintNFT 接收三个参数:url;name;description;就是我们在表单上输入的内容。
export const mintNFT = async (url, name, description) => {
const metadata = new Object();
metadata.name = name;
metadata.image = url;
metadata.description = description;
const pinataResponse = await pinJSONToIPFS(metadata);
if (!pinataResponse.success) {
return {
success: false,
status: "😢 Something went wrong while uploading your tokenURI.",
};
}
const tokenURI = pinataResponse.pinataUrl;
try {
const web3Provider = new ethers.BrowserProvider(window.ethereum);
const signer = await web3Provider.getSigner();
// 连接钱包到合约
const nftContract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
// 执行合约方法
let nftTx = await nftContract.mintNFT(
window.ethereum.selectedAddress,
tokenURI
);
// 等待交易确认
await nftTx.wait();
return {
success: true,
status: `Check out your transaction on Etherscan: https://etherscan.io/tx/${nftTx.hash}`,
};
} catch (error) {
return {
success: false,
status: "😥 Something went wrong: " + error.message,
};
}
};
这段代码里做了两件事:获取 Pinata 的 Url,调用合约方法。
点击 Mint NFT 按钮,如果代码正确,应该会唤起 Metamask 钱包:
点击确认,成功完成交互之后,status 会变成如下图所示:
我们就可以到 Sepolia 测试网上查看交易详情了。
和上一部分的最后一样,我们也可以到 Opensea 测试网上找到刚才上传的 NFT。