一个应用程序的部署阶段是开发过程中的一个关键步骤。在这一阶段,应用程序从本地托管到可供世界上任何地方的目标受众使用。
随着区块链在构建应用程序方面的使用越来越多,你可能已经想知道与智能合约互动的DApps是如何托管的。
在本教程中,你将通过使用React、Hardhat和Alchemy构建一个去中心化的宠物收养应用样本,学习如何用Fleek托管DApps。
在开始本教程之前你需要什么
本教程包含几个实践步骤。为了跟上进度,我建议你做以下工作:
- 安装React,我们将用它来构建用户界面。我在本教程中使用的是React v14。
- 安装Hardhat,我们将使用它作为我们的开发环境。
- 为Alchemy区块链开发者平台创建一个免费账户
- 为Fleek创建一个免费账户,你将在下一节了解更多信息
- 下载MetaMask浏览器扩展
MetaMask是一个加密货币钱包,允许用户通过浏览器或移动应用程序访问DApps。你还会想要一个以太坊测试网的MetaMask测试账户,用于测试智能合约。我在本教程中使用的是Ropsten测试网络。
什么是Fleek?
Fleek是一个Web3解决方案,旨在使你的网站、DApps和服务的部署过程无缝化。目前,Fleek提供了一个网关,用于在InterPlanetary文件系统(IPFS)或Dfinity的互联网计算机(IC) 上托管您的服务。
Fleek将自己描述为相当于Web3应用程序的Netlify。因此,你会发现一些与Netlify相似的功能,如使用docker镜像执行构建和生成部署预览。
根据IPFS的博客,"Fleek在2022年的主要目标是重组其IPFS基础设施,以进一步分散和激励。它还将包括新的Web3基础设施供应商,用于网络建设堆栈的不同部分"。
Fleek为IPFS的挑战提供了一个解决方案,即每次更新时,你的网站的哈希值都会改变,因此很难有一个固定的地址哈希值。在最初的部署之后,Fleek将建立、钉住和更新你的网站。
让我们在下一节开始构建我们的样本DApp,并使用Fleek部署它。我们将把DApp托管在IPFS上。
构建一个DApp样本以部署到Fleek上
在这一节中,我们将为一家宠物店建立一个去中心化的收养跟踪系统。
如果你熟悉Truffle套件,你可能会认出这个练习的某些部分。这个DApp的灵感来自于Truffle指南。我们将通过使用Alchemy、Hardhat和React使事情更进一步。
为了使你能够专注于编写智能合约和部署DApp,我已经建立了UI组件和状态。智能合约和React代码将被包含在一个项目中。
只需从我的GitHub仓库克隆React应用程序,即可开始编写智能合约:
git clone https://github.com/vickywane/react-web3
接下来,将目录改为克隆的文件夹,并安装package.json 文件中列出的依赖项:
# change directory
cd react-web3
# install application dependencies
npm install
随着React应用程序的建立,让我们继续创建宠物收养智能合约。
创建宠物领养智能合约
在react-web3 目录中,创建一个合约文件夹,以存储我们的宠物收养智能合约的 Solidity 代码。
使用您的代码编辑器,创建一个名为Adoption.sol 的文件,并粘贴以下代码,以在智能合约中创建必要的变量和函数,包括:
- 一个16长度的数组来存储每个宠物收养者的地址
- 一个用于收养宠物的函数
- 一个检索所有被收养宠物的地址的函数
//SPDX-License-Identifier: Unlicense
// ./react-web3/contracts/Adoption.sol
pragma solidity ^0.8.0;
contract Adoption {
address[16] public adopters;
event PetAssigned(address indexed petOwner, uint32 petId);
// adopting a pet
function adopt(uint32 petId) public {
require(petId >= 0 && petId <= 15, "Pet does not exist");
adopters[petId] = msg.sender;
emit PetAssigned(msg.sender, petId);
}
// Retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
}
接下来,在合约文件夹中创建另一个名为deploy-contract-script.js 的文件。将下面的JavaScript代码粘贴到该文件中。该代码将作为一个脚本,使用Hardhat中的异步getContractFactory 方法来创建一个收养智能合约的工厂实例,然后将其部署:
// react-web3/contract/deploy-contract-script.js
require('dotenv').config()
const { ethers } = require("hardhat");
async function main() {
// We get the contract to deploy
const Adoption = await ethers.getContractFactory("Adoption");
const adoption = await Adoption.deploy();
await adoption.deployed();
console.log("Adoption Contract deployed to:", adoption.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
最后,创建一个名为hardhat.config.js 的文件。这个文件将指定Hardhat的配置。
在hardhat.config.js 文件中添加以下 JavaScript 代码,以指定 Solidity 版本和你的 Ropsten 网络账户的 URL 端点。
require("@nomiclabs/hardhat-waffle");
require('dotenv').config();
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.4",
networks: {
ropsten: {
url: process.env.ALCHEMY_API_URL,
accounts: [`0x${process.env.METAMASK_PRIVATE_KEY}`]
}
}
};
我使用环境变量ALCHEMY_API_URL 和METAMASK_PRIVATE_KEY 来存储用于Ropsten网络配置的URL和私人账户密钥值:
METAMASK_PRIVATE_KEY与您的MetaMask钱包相关联。ALCHEMY_API_URL链接到一个Alchemy应用程序。
你可以使用.env 文件和dotenv 包在这个react-web3 项目中存储和访问这些环境变量。我们将在下一节回顾如何做到这一点。
如果你一直成功地跟随,你在项目的这个阶段还没有创建一个Alchemy应用程序。你将需要使用其API URL端点,所以让我们继续在Alchemy上创建一个应用程序。
创建一个Alchemy应用程序
Alchemy提供的功能使您能够连接到一个网络的外部远程过程调用(RPC)节点。RPC节点使您的DApp和区块链的通信成为可能。
使用您的网络浏览器,导航到Alchemy网络仪表板并创建一个新的应用程序。
为该应用程序提供一个名称和描述,然后选择Ropsten网络。点击 "创建应用程序 "按钮,继续。
应用程序创建后,你会发现它列在页面底部。
点击 "查看密钥",显示Alchemy应用程序的API密钥。请注意HTTP URL。我在下面的图片中编辑了这个信息。
在你的Hardhat项目中创建一个.env 文件,如下图所示。你将使用这个.env 文件来存储你的Alchemy应用程序的URL和MetaMask私钥。
// react-web3/.env
ALCHEMY_API_URL=<ALCHEMY_HTTP_URL>
METAMASK_PRIVATE_KEY=<METAMASK_PRIVATE_KEY>
用Alchemy的HTTP URL和你的MetaMask私钥替换上面的ALCHEMY_HTTP_URL 和METAMASK_PRIVATE_KEY 占位符。按照MetaMask导出私钥指南,了解如何为你的钱包导出这些信息。
最后,执行下一个命令,将你的宠物收养智能合约部署到指定的Ropsten网络。
npx hardhat run contracts/deploy-contract-script.js --network ropsten
如下图所示,注意合同部署后返回到你的控制台的地址。你将在下一节中需要这个地址:
在这一点上,宠物领养智能合约已经部署完毕。现在让我们把注意力转移到DApp本身,并创建与宠物收养智能合约互动的功能。
构建DApp前台
与Truffle指南中的宠物店教程类似,我们的DApp将显示十六个不同品种的狗,可以被收养。每只狗的详细信息都存储在 [src/pets.json](https://github.com/vickywane/react-web3/tree/master/src) 文件中。我们正在使用TailwindCSS来设计这个DApp。
要开始,请打开 [state/context.js](https://github.com/vickywane/react-web3/tree/master/src/state) 文件,用下面的代码替换现有内容:
// react-web3/state/context.js
import React, {useEffect, useReducer} from "react";
import Web3 from "web3";
import {ethers, providers} from "ethers";
const {abi} = require('../../artifacts/contracts/Adoption.sol/Adoption.json')
if (!abi) {
throw new Error("Adoptiom.json ABI file missing. Run npx hardhat run contracts/deploy-contract-script.js")
}
export const initialState = {
isModalOpen: false,
dispatch: () => {
},
showToast: false,
adoptPet: (id) => {
},
retrieveAdopters: (id) => {
},
};
const {ethereum, web3} = window
const AppContext = React.createContext(initialState);
export default AppContext;
const reducer = (state, action) => {
switch (action.type) {
case 'INITIATE_WEB3':
return {
...state,
isModalOpen: action.payload,
}
case 'SENT_TOAST':
return {
...state,
showToast: action.payload.toastVisibility
}
default:
return state;
}
};
const createEthContractInstance = () => {
try {
const provider = new providers.Web3Provider(ethereum)
const signer = provider.getSigner()
const contractAddress = process.env.REACT_APP_ADOPTION_CONTRACT_ADDRESS
return new ethers.Contract(contractAddress, abi, signer)
} catch (e) {
console.log('Unable to create ethereum contract. Error:', e)
}
}
export const AppProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
const instantiateWeb3 = async _ => {
if (ethereum) {
try {
// Request account access
return await ethereum.request({method: "eth_requestAccounts"})
} catch (error) {
// User denied account access...
console.error("User denied account access")
}
} else if (web3) {
return
}
return new Web3(Web3.givenProvider || "ws://localhost:8545")
}
const adoptPet = async id => {
try {
const instance = createEthContractInstance()
const accountData = await instantiateWeb3()
await instance.adopt(id, {from: accountData[0]})
dispatch({
type: 'SENT_TOAST', payload: {
toastVisibility: true
}
})
// close success toast after 3s
setTimeout(() => {
dispatch({
type: 'SENT_TOAST', payload: {
toastVisibility: false
}
})
}, 3000)
} catch (e) {
console.log("ERROR:", e)
}
}
const retrieveAdopters = async _ => {
try {
const instance = createEthContractInstance()
return await instance.getAdopters()
} catch (e) {
console.log("RETRIEVING:", e)
}
}
useEffect(() => {
(async () => { await instantiateWeb3() })()
})
return (
<AppContext.Provider
value={{
...state,
dispatch,
adoptPet,
retrieveAdopters
}}
>
{children}
</AppContext.Provider>
);
};
通过阅读上面的代码块,你会观察到以下情况:
以太坊和Web3对象从浏览器窗口被解构。MetaMask扩展将Ethereum对象注入到浏览器中。
createEthContractInstance 辅助函数使用合同的ABI和Alchemy的地址创建并返回一个宠物收养合同的实例。
instantiateWeb3 辅助函数将在一个数组中检索并返回用户的账户地址,使用MetaMask来验证以太坊窗口对象是否被定义。
instantiateWeb3 辅助函数也在useEffect 钩子中执行,以确保用户在网络浏览器中打开应用程序后立即与MetaMask连接。
adoptPet 函数期望一个数字petId 参数,创建收养合同实例,并使用createEthContractInstance 和instantiateWeb3 辅助函数检索用户的地址。
petId 参数和用户账户地址从宠物收养合同实例传入adopt 方法,以收养宠物。
retrieveAdopters 函数在收养实例上执行getAdopters 方法,以检索所有收养的宠物的地址。
保存这些修改,启动React开发服务器,在http://localhost4040/,查看宠物店DApp。
在这一点上,收养合同内的功能已经在state/context.js 文件中实现,但还没有执行。如果不通过MetaMask认证,用户的账户地址将是未定义的,所有收养宠物的按钮将被禁用,如下图所示。
让我们继续添加宠物领养合同地址作为环境变量,在Fleek上托管DApp。
将React DApp部署到Fleek上
在Fleek上托管DApp可以通过Fleek仪表盘、Fleek CLI,甚至是使用Fleek GitHub Actions的编程方式完成。在本节中,你将学习如何使用Fleek CLI,因为我们通过Fleek在IPFS上托管宠物店DApp。
配置Fleek CLI
执行下面的命令,在你的电脑上全局安装Fleek CLI:
npm install -g @fleek/cli
要使用已安装的Fleek CLI,你需要有一个Fleek账户的API密钥作为环境变量存储在你的终端上。让我们继续使用Fleek网页仪表板为你的账户生成一个API密钥。
使用你的网络浏览器,导航到你的Fleek账户仪表板,点击你的账户头像,显示一个弹出菜单。在这个菜单中,点击 "设置 "来导航到你的Fleek账户设置。
在你的Fleek账户设置中,点击 "生成API "按钮,启动 "API详情 "模式,这将为你的Fleek账户生成一个API密钥。
拿着生成的API密钥,替换下面命令中的FLEEK_API_KEY 占位符。
export FLEEK_API_KEY='FLEEK_API_KEY'
执行这个命令可以将Fleek API密钥导出为你电脑终端的一个临时环境变量。当你对你的Fleek账户执行命令时,Fleek CLI将读取FLEEK_API_KEY 这个变量的值。
通过Fleek CLI初始化一个网站
在你使用Fleek在IPFS上托管DApp及其文件之前,你需要在本地生成React DApp的静态文件。
在构建过程中,可以通过指定Docker镜像和用于构建静态文件的命令来自动生成静态文件。然而,在本教程中,你将手动生成静态文件。
执行下面的npm命令,在build 目录中为DApp生成静态文件。
npm run build
接下来,使用以下命令在react-web3 文件夹中初始化一个Fleek站点工作区。
fleek site:init
初始化过程对每个Fleek站点来说都是一次性的步骤。Fleek CLI将启动一个交互式会话,指导你完成初始化网站的过程。
在初始化过程中,你会被提示输入一个teamId ,如下图所示。
你可以在Fleek仪表板的URL中找到你的teamId ,作为数字。一个例子teamId ,如下图所示。
在这一点上,Fleek CLI已经在react-web3 目录下生成了一个.fleek.json 文件,其中有Fleek的主机配置。然而,还缺少一件事:包含宠物收养智能合约地址的环境变量。
让我们继续看看如何为Fleek上的本地部署站点添加环境变量。
添加环境变量
Fleek使开发者能够通过Fleek仪表板或配置文件安全地管理其网站的敏感凭证。由于你在本教程中是通过命令行本地托管网站,你将在.fleek.json 文件中指定你的环境变量。
在下面的代码中,将ADOPTION_CONTRACT_ADDRESS 占位符替换为宠物收养智能合约地址。记住,这个地址是我们在本教程前面使用npx命令创建Alchemy应用程序并部署智能合约后返回的。
{
"site": {
"id": "SITE_ID",
"team": "TEAM_ID",
"platform": "ipfs",
"source": "ipfs",
"name": "SITE_NAME"
},
"build": {
"baseDir": "",
"publicDir": "build",
"rootDir": "",
"environment": {
"REACT_APP_ADOPTION_CONTRACT": "ADOPTION_CONTRACT_ADDRESS"
}
}
}
注意:当你初始化Fleek网站时,Fleek CLI会在.fleek.json 文件中自动生成SITE_ID 、TEAM_ID 和SITE_NAME 占位符。
上面的代码还包含以下对象,你应该把它添加到你的.fleek.json 文件中的构建对象中:
"environment": {
"REACT_APP_ADOPTION_CONTRACT": "ADOPTION_CONTRACT_ADDRESS"
}
上面的JSON对象指定了Fleek在构建DApp时使用的一个node docker镜像。在构建过程中,command 字段中的npm命令将被执行。
执行下面的命令,使用.fleek.json 文件中的新构建配置重新部署DApp。
fleek site:deploy
恭喜你!DApp已经完全部署完毕。DApp已经完全部署完毕,你现在可以通过你的网络浏览器访问实时网站。你也可以通过Fleek仪表盘按照以下步骤获得关于托管DApp的更多详细信息。
- 导航到你的Fleek仪表盘
- 点击你部署的DApp的名称
- 在左边看到部署的网站URL
- 在右边看到一个部署预览图

点击网站URL,在新的浏览器标签中打开DApp。DApp启动后,会立即提示您连接一个MetaMask钱包。钱包连接后,你将能够通过点击 "收养 "按钮,收养16只狗中的任何一只。
这就是了!你的宠物领养DApp样本已经被部署到Fleek。
总结
在本教程中,我们重点介绍了通过Fleek在IPFS上构建和托管一个示例DApp。这个过程的开始与Truffle指南中的宠物收养智能合约类似。然后,你通过构建一个DApp来与宠物收养智能合约进行互动,从而更进一步。
如果你想利用本教程中的步骤来托管一个可生产的DApp,我强烈建议你考虑以下几点。
首先,确保将Fleek连接到代码主机提供商,如GitHub,并从其存储库中的生产分支部署DApp。这将允许Fleek在你向部署的分支推送新的代码提交时自动重新部署DApp。
第二,如果你使用.fleek.json 文件来存储环境变量,在你的.gitignore 文件中包括.fleek.json 的文件名。这样做将确保.fleek.json 文件不会被推送,你的环境变量也不会被暴露。
我希望你觉得这个教程很有用。如果你有任何问题,欢迎分享评论。