使用React、Ethers。js、Solidity和Hardhat构建全栈dApp 在本教程中,您将学习一个web3技术栈,它允许您利用以太坊虚拟机(EVM)在包括以太坊、Polygon、Avalanche、Celo等在内的数十个区块链网络上构建全栈应用程序。 该项目的代码位于此处〔1〕。本教程的视频课程位于此处〔2〕。另请查看定义web3堆栈〔3〕 自从我开始在区块链领域工作以来,我一直在深入研究Solidity和EVM的智能合约开发。我已经确定了我认为是开始使用Solidity构建全栈dApp的技术栈: 客户端框架React 以太坊开发环境Hardhat〔4〕 以太坊Web客户端库Ethers。js〔5〕 API层TheGraphProtocol〔6〕 我在学习这个的过程中遇到的问题是,虽然对于这些东西中的每一个都有相当好的文档,但实际上没有关于如何将所有这些东西放在一起并理解它们如何相互工作的东西。有一些非常好的样板,例如scaffoldeth〔7〕(其中还包括Ethers、Hardhat和TheGraph),但对于刚入门的人来说可能太多了。 我想要一个端到端的指南来向我展示如何使用最新的资源、库和工具构建完整的以太坊应用程序。 我感兴趣的是:1。如何创建、部署和测试以太坊智能合约到本地、测试和主网2。如何在本地、测试和生产环境网络之间切换3。如何使用React、Vue、Svelte或Angular等前端的各种环境连接到合约并与之交互 在花了一些时间弄清楚所有这些并开始使用我感到非常满意的堆栈之后,我认为写出如何使用这个堆栈构建和测试一个完整的以太坊应用程序会很好,而不仅仅是为了其他人还有谁可能对这个栈感兴趣,也供自己以后参考。这就是那个参考。碎片 让我们回顾一下我们将使用的主要部分以及它们如何适应堆栈。一、以太坊开发环境 在构建智能合约时,您需要一种无需处理实时环境即可部署合约、运行测试和调试Solidity代码的方法。 您还需要一种方法将您的Solidity代码编译成可以在客户端应用程序中运行的代码在我们的例子中,是一个React应用程序。稍后我们将详细了解其工作原理。 Hardhat是专为全栈开发而设计的以太坊开发环境和框架,也是我将在本教程中使用的框架。 生态系统中的其他类似工具是Ganache〔8〕、Truffle〔9〕和Foundry〔10〕。2。以太坊Web客户端库 在我们的React应用程序中,我们需要一种方法来与已部署的智能合约进行交互。我们将需要一种方法来读取数据以及发送新交易。 ethers。js〔11〕旨在成为一个完整而紧凑的库,用于从React、Vue、Angular或Svelte等客户端JavaScript应用程序与以太坊区块链及其生态系统进行交互。这是我们将要使用的库。 生态系统中另一个流行的选择是web3。js〔12〕3。MetaMask Metamask〔13〕有助于处理帐户管理并将当前用户连接到区块链。MetaMask使用户能够以几种不同的方式管理他们的帐户和密钥,同时将他们与站点上下文隔离开来。 一旦用户连接了他们的MetaMask钱包,您作为开发人员就可以与全球可用的以太坊API(window。ethereum)交互,该API可以识别web3兼容浏览器的用户(例如MetaMask用户),并且每当您请求交易签名时,MetaMask都会提示以尽可能易于理解的方式向用户展示。4。React React是一个前端JavaScript库,用于构建Web应用程序、用户界面和UI组件。它由Facebook和许多个人开发人员和公司维护。 React及其庞大的元框架生态系统(如Next。js〔14〕、Gatsby〔15〕、Redwood〔16〕、Blitz。js〔17〕等)支持所有类型的部署目标,包括传统SPA、静态站点生成器、服务器端渲染以及这三者的组合。React似乎继续在前端领域占据主导地位,我认为至少在不久的将来会继续这样做。5。Graph 对于大多数构建在以太坊等区块链上的应用程序,直接从链上读取数据既困难又费时,因此您过去经常看到人们和公司构建自己的集中式索引服务器并为来自这些服务器的API请求提供服务。这需要大量的工程和硬件资源,并破坏了去中心化所需的安全属性。 Graph是一种用于查询区块链数据的索引协议,它支持创建完全去中心化的应用程序并解决了这个问题,暴露了应用程序可以使用的丰富的GraphQL查询层。在本指南中,我们不会为我们的应用程序构建子图,但会在以后的教程中这样做。 要了解如何使用TheGraph构建区块链API,请查看在以太坊上构建GraphQLAPI〔18〕。我们将建造什么 在本教程中,我们将构建、部署和连接到几个基本的智能合约:1。在以太坊区块链上创建和更新消息的合约2。铸造代币的合同,然后允许合同的所有者将代币发送给其他人并读取代币余额,并且新代币的所有者也可以将它们发送给其他人。 我们还将构建一个React前端,允许用户:1。从部署到区块链的合约中读取问候语2。更新问候语3。将新铸造的代币从他们的地址发送到另一个地址4。一旦有人收到代币,允许他们也将代币发送给其他人5。从部署到区块链的合约中读取代币余额先决条件1。本地机器上安装的Node。js2。MetaMask〔19〕Chrome扩展安装在您的浏览器中 您不需要为本指南拥有任何以太坊,因为我们将在整个教程的测试网络上使用假测试以太币。入门 首先,我们将创建一个新的React应用程序:npxcreatereactappreactdapp 接下来,切换到新目录并使用NPM或Yarnethers。js〔20〕安装和hardhat〔21〕使用:npminstallethershardhatnomiclabshardhatwaffleethereumwafflechainomiclabshardhatethers安装和配置以太坊开发环境 接下来,使用Hardhat初始化一个新的以太坊开发环境:npxhardhat?Whatdoyouwanttodo?CreateaJavaScriptproject?Hardhatprojectroot:Choosedefaultpath 如果您遇到有关README。md文件的错误,请删除README。md文件并重新运行该命令。 现在您应该会在根目录中看到为您创建的以下工件: hardhat。config。js您的整个Hardhat设置(即您的配置、插件和自定义任务)都包含在此文件中。scripts一个包含名为samplescript。js的脚本的文件夹,该脚本将在执行时部署你的智能合约test一个包含示例测试脚本的文件夹contracts一个包含示例Solidity智能合约的文件夹 由于MetaMask配置问题〔22〕,我们需要将HardHat配置上的链ID更新为1337。我们还需要更新已编译合约的工件〔23〕位置,使其位于React应用程序的src目录中。 要进行这些更新,请打开hardhat。config。js并将module。exports更新为如下所示:module。exports{solidity:0。8。9,paths:{artifacts:。srcartifacts,},networks:{hardhat:{chainId:1337}}};我们的智能合约 接下来,让我们看一下在contractsGreeter。sol中提供给我们的示例合约:SPDXLicenseIdentifier:MITpragmasolidity0。8。9;importhardhatconsole。sol;contractGreeter{stringgreeting;constructor(stringmemorygreeting){console。log(DeployingaGreeterwithgreeting:,greeting);greetinggreeting;}functiongreet()publicviewreturns(stringmemory){returngreeting;}functionsetGreeting(stringmemorygreeting)public{console。log(Changinggreetingfromstos,greeting,greeting);greetinggreeting;}} 这是一个非常基本的智能合约。部署时,它会设置一个Greeting变量并公开一个可以调用以返回问候语的函数(greet)。 它还公开了一个允许用户更新问候语(setGreeting)的函数。当部署到以太坊区块链时,这些方法将可供用户进行交互。读写以太坊区块链 与智能合约交互的方式有两种,读取或写入交易。在我们的合约中,greet可以认为是读,而setGreeting可以认为是写交易。 在写入或初始化交易时,您必须为要写入区块链的交易付费。为了使这项工作成功,您需要支付〔gas〕(https:www。investopedia。comtermsggasethereum。asp::textWhatIsGas(Ethereum)3F,ontheEthereumblockchainplatform),这是在以太坊区块链上成功进行交易和执行合同所需的费用或价格。 只要您只是从区块链中读取数据而不更改或更新任何内容,您就不需要进行交易,也不会产生任何gas或成本。然后,您调用的函数将仅由您连接的节点执行,因此您无需支付任何气体,并且读取是免费的。 ethers。js在我们的React应用程序中,我们与智能合约交互的方式是使用库、合约地址和Hardhat从合约创建的ABI〔24〕的组合。 什么是ABI?ABI代表应用程序二进制接口。您可以将其视为您的客户端应用程序与部署您将与之交互的智能合约的以太坊区块链之间的接口。 ABI通常由HardHat等开发框架从Solidity智能合约编译而成。您还可以经常在Etherscan〔25〕上找到智能合约的ABI编译ABI 现在我们已经了解了基本的智能合约并了解了ABI是什么,让我们为我们的项目编译一个ABI。 为此,请转到命令行并运行以下命令:npxhardhatcompile 如果您有任何依赖性错误问题hardhattoolbox,请查看此处的〔26〕安装说明。 现在,您应该会在src目录中看到一个名为artifacts的新文件夹。artifactscontractsGreeter。json文件包含ABI作为属性之一。当我们需要使用ABI时,我们可以从我们的JavaScript文件中导入它:importGreeterfrom。artifactscontractsGreeter。solGreeter。json 然后我们可以像这样引用ABI:console。log(GreeterABI:,Greeter。abi) 请注意,Ethers。js还支持人类可读的ABI〔27〕,但在本教程中不会涉及到这一点。部署和使用本地网络区块链 接下来,让我们将智能合约部署到本地区块链,以便我们对其进行测试。 要部署到本地网络,首先需要启动本地测试节点。为此,请打开CLI并运行以下命令:npxhardhatnode 当我们运行此命令时,您应该会看到一个地址和私钥列表。 这些是为我们创建的20个测试帐户和地址,我们可以使用它们来部署和测试我们的智能合约。每个账户还装有10,000个假以太币。稍后,我们将学习如何将测试帐户导入MetaMask,以便我们使用它。 接下来,使用以下代码更新scriptsdeploy。js以部署Greeter合约:consthrerequire(hardhat);asyncfunctionmain(){constGreeterawaithre。ethers。getContractFactory(Greeter);constgreeterawaitGreeter。deploy(HelloWorld);awaitgreeter。deployed();console。log(contractsuccessfullydeployedto{greeter。address});}main()。catch((error){console。error(error);process。exitCode1;}); 现在我们可以运行部署脚本并为我们想要部署到本地网络的CLI提供一个标志:npxhardhatrunscriptsdeploy。jsnetworklocalhost 执行此脚本后,智能合约应部署到本地测试网络,然后我们应该能够开始与其交互。 部署合约时,它使用了我们启动本地网络时创建的第一个帐户。 如果您查看CLI的输出,您应该能够看到如下内容:Greeterdeployedto:0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 这个地址是我们将在我们的客户端应用程序中用来与智能合约对话的地址。保持此地址可用,因为我们在从客户端应用程序连接到它时需要使用它。 要将交易发送到智能合约,我们需要使用运行npxhardhatnode时创建的帐户之一连接我们的MetaMask钱包。在CLI注销的合约列表中,您应该会看到帐号和私钥:reactdappgit:(main)npxhardhatnodeStartedHTTPandWebSocketJSONRPCserverathttp:127。0。0。1:8545AccountsAccount0:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266(10000ETH)PrivateKey:0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80。。。 我们可以将此帐户导入MetaMask,以便开始使用那里可用的一些假Eth。 为此,首先打开MetaMask并启用测试网络: 接下来,将网络更新为Localhost8545: 接下来,在MetaMask中点击账户菜单中的导入账户: 复制然后粘贴由CLI注销的私钥之一,然后单击导入。导入帐户后,您应该会在帐户中看到Eth: 现在我们已经部署了智能合约并准备好使用帐户,我们可以开始从React应用程序与其交互。连接React客户端 在本教程中,我们不会担心使用CSS构建漂亮的UI以及所有这些,我们100专注于核心功能,以帮助您启动和运行。从那里,你可以把它拿走,如果你愿意的话,让它看起来不错。 话虽如此,让我们回顾一下我们希望从React应用程序中实现的两个目标:1。从智能合约中获取greeting当前值2。允许用户更新greeting的值 了解了这些事情后,我们如何做到这一点?以下是我们需要做的事情来实现这一点:1。创建一个输入字段和一些本地状态来管理输入的值(更新greeting)2。允许应用程序连接到用户的MetaMask帐户以签署交易3。创建用于读取和写入智能合约的函数 为此,使用以下代码打开srcApp。js并更新它,将greeterAddress的值设置为您的智能合约的地址:import。App。css;import{useState}fromreact;import{ethers}fromethersimportGreeterfrom。artifactscontractsGreeter。solGreeter。jsonUpdatewiththecontractaddressloggedouttotheCLIwhenitwasdeployedconstgreeterAddressyourcontractaddressfunctionApp(){storegreetinginlocalstateconst〔greeting,setGreetingValue〕useState()requestaccesstotheusersMetaMaskaccountasyncfunctionrequestAccount(){awaitwindow。ethereum。request({method:ethrequestAccounts});}callthesmartcontract,readthecurrentgreetingvalueasyncfunctionfetchGreeting(){if(typeofwindow。ethereum!undefined){constprovidernewethers。providers。Web3Provider(window。ethereum)constcontractnewethers。Contract(greeterAddress,Greeter。abi,provider)try{constdataawaitcontract。greet()console。log(data:,data)}catch(err){console。log(Error:,err)}}}callthesmartcontract,sendanupdateasyncfunctionsetGreeting(){if(!greeting)returnif(typeofwindow。ethereum!undefined){awaitrequestAccount()constprovidernewethers。providers。Web3Provider(window。ethereum);constsignerprovider。getSigner()constcontractnewethers。Contract(greeterAddress,Greeter。abi,signer)consttransactionawaitcontract。setGreeting(greeting)awaittransaction。wait()fetchGreeting()}}return(headerclassNameAppheaderbuttononClick{fetchGreeting}FetchGreetingbuttonbuttononClick{setGreeting}SetGreetingbuttoninputonChange{esetGreetingValue(e。target。value)}placeholderSetgreetingheader);}exportdefaultApp; 要测试它,请启动React服务器:npmstart 当应用程序加载时,您应该能够获取当前问候语并将其注销到控制台。您还应该能够通过使用您的MetaMask钱包签署合同并花费假Ether来更新问候语。 部署和使用实时测试网络 我们还可以部署几个以太坊测试网络,如Ropsten、Rinkeby或Kovan,以便在无需将其部署到主网的情况下获得可公开访问的合约版本。在本教程中,我们将部署到Ropsten测试网络。 首先,请先更新您的MetaMask钱包以连接到Ropsten网络。 接下来,通过访问这个〔28〕或另一个测试水龙头,给自己发送一些测试以太币,以便在本教程的其余部分使用。 我们可以通过注册Infura〔29〕或Alchemy〔30〕(我在本教程中使用Infura)等服务来访问Ropsten(或任何其他测试网络)。 在Infura或Alchemy中创建应用程序后,您将获得一个如下所示的端点:https:ropsten。infura。iov3yourprojectid 请务必在Infura或Alchemy应用程序配置中设置ALLOWLISTETHEREUMADDRESSES,以包含您将从中进行部署的帐户的钱包地址。 要部署到测试网络,我们需要使用一些额外的网络信息更新我们的hardhat配置。我们需要设置的一件事是我们将从中部署的钱包的私钥。 要获取私钥,您可以从MetaMask导出它。 我建议不要在您的应用程序中对这个值进行硬编码,而是将其设置为环境变量。 接下来,添加具有以下配置的networks属性:module。exports{defaultNetwork:hardhat,paths:{artifacts:。srcartifacts,},networks:{hardhat:{},ropsten:{url:https:ropsten。infura。iov3yourprojectid,accounts:〔0x{yourprivatekey}〕}},solidity:0。8。9,}; 要部署,请运行以下脚本:npxhardhatrunscriptsdeploy。jsnetworkropsten 部署合约后,您应该能够开始与其交互。您现在应该能够在EtherscanRopstenTestnetExplorer〔31〕上查看实时合约。铸造代币 智能合约最常见的用例之一是创建代币,让我们看看我们如何做到这一点。由于我们对所有这些工作原理了解得更多一些,所以我们会走得更快一些。 在主contracts目录中创建一个名为Token。sol的新文件。 接下来,使用以下智能合约更新Token。sol:SPDXLicenseIdentifier:MITpragmasolidity0。8。9;importhardhatconsole。sol;contractToken{stringpublicnameNaderDabitToken;stringpublicsymbolNDT;uintpublictotalSupply1000000;mapping(addressuint)balances;constructor(){balances〔msg。sender〕totalSupply;}functiontransfer(addressto,uintamount)external{require(balances〔msg。sender〕amount,Notenoughtokens);balances〔msg。sender〕amount;balances〔to〕amount;}functionbalanceOf(addressaccount)externalviewreturns(uint){returnbalances〔account〕;}} 请注意,此代币合约仅用于演示目的,不符合ERC20〔32〕标准。我们将在这里〔33〕介绍ERC20代币 该合约将创建一个名为NaderDabitToken的新代币,并将供应量设置为1000000。 接下来,编译这个合约:npxhardhatcompile 现在,更新scriptsdeploy。js中的部署脚本以包含这个新的Token合约:consthrerequire(hardhat);asyncfunctionmain(){const〔deployer〕awaithre。ethers。getSigners();console。log(Deployingcontractswiththeaccount:,deployer。address);constGreeterawaithre。ethers。getContractFactory(Greeter);constgreeterawaitGreeter。deploy(Hello,World!);constTokenawaithre。ethers。getContractFactory(Token);consttokenawaitToken。deploy();awaitgreeter。deployed();awaittoken。deployed();console。log(Greeterdeployedto:,greeter。address);console。log(Tokendeployedto:,token。address);}main()。then(()process。exit(0))。catch(error{console。error(error);process。exit(1);}); 现在,我们可以将这个新合约部署到本地或Ropsten网络:npxhardhatrunscriptsdeploy。jsnetworklocalhost 部署合约后,您可以开始将这些代币发送到其他地址。 为此,让我们更新完成这项工作所需的客户端代码:import。App。css;import{useState}fromreact;import{ethers}fromethersimportGreeterfrom。artifactscontractsGreeter。solGreeter。jsonimportTokenfrom。artifactscontractsToken。solToken。jsonconstgreeterAddressyourcontractaddressconsttokenAddressyourcontractaddressfunctionApp(){const〔greeting,setGreetingValue〕useState()const〔userAccount,setUserAccount〕useState()const〔amount,setAmount〕useState()asyncfunctionrequestAccount(){awaitwindow。ethereum。request({method:ethrequestAccounts});}asyncfunctionfetchGreeting(){if(typeofwindow。ethereum!undefined){constprovidernewethers。providers。Web3Provider(window。ethereum)console。log({provider})constcontractnewethers。Contract(greeterAddress,Greeter。abi,provider)try{constdataawaitcontract。greet()console。log(data:,data)}catch(err){console。log(Error:,err)}}}asyncfunctiongetBalance(){if(typeofwindow。ethereum!undefined){const〔account〕awaitwindow。ethereum。request({method:ethrequestAccounts})constprovidernewethers。providers。Web3Provider(window。ethereum);constcontractnewethers。Contract(tokenAddress,Token。abi,provider)constbalanceawaitcontract。balanceOf(account);console。log(Balance:,balance。toString());}}asyncfunctionsetGreeting(){if(!greeting)returnif(typeofwindow。ethereum!undefined){awaitrequestAccount()constprovidernewethers。providers。Web3Provider(window。ethereum);console。log({provider})constsignerprovider。getSigner()constcontractnewethers。Contract(greeterAddress,Greeter。abi,signer)consttransactionawaitcontract。setGreeting(greeting)awaittransaction。wait()fetchGreeting()}}asyncfunctionsendCoins(){if(typeofwindow。ethereum!undefined){awaitrequestAccount()constprovidernewethers。providers。Web3Provider(window。ethereum);constsignerprovider。getSigner();constcontractnewethers。Contract(tokenAddress,Token。abi,signer);consttransationawaitcontract。transfer(userAccount,amount);awaittransation。wait();console。log({amount}Coinssuccessfullysentto{userAccount});}}return(headerclassNameAppheaderbuttononClick{fetchGreeting}FetchGreetingbuttonbuttononClick{setGreeting}SetGreetingbuttoninputonChange{esetGreetingValue(e。target。value)}placeholderSetgreetingbrbuttononClick{getBalance}GetBalancebuttonbuttononClick{sendCoins}SendCoinsbuttoninputonChange{esetUserAccount(e。target。value)}placeholderAccountIDinputonChange{esetAmount(e。target。value)}placeholderAmountheader);}exportdefaultApp; 接下来,运行应用程序:npmstart 我们应该能够点击GetBalance并看到我们登录到控制台的帐户中有1,000,000个代币。 您还应该能够通过单击导入令牌在MetaMask中查看它们: 接下来单击CustomToken并输入令牌合约地址,然后单击AddCustomToken。(如果询问代币小数,请选择0)现在代币应该在您的钱包中可用: 接下来,让我们尝试将这些硬币发送到另一个地址。 为此,请复制另一个帐户的地址,并使用更新后的ReactUI将它们发送到该地址。当您检查令牌数量时,它应该等于原始数量减去您发送到该地址的数量。ERC20代币 ERC20代币标准〔34〕定义了一套适用于所有ERC20代币的规则,使它们能够轻松地相互交互。ERC20使得人们可以很容易地铸造自己的代币,这些代币将与以太坊区块链上的其他代币具有互操作性。 让我们看看如何使用ERC20标准构建我们自己的代币。 首先,安装OpenZepplin〔35〕智能合约库,我们将在其中导入基础ERC20令牌:npminstallopenzeppelincontracts 接下来,我们将通过扩展(或继承)合约来创建我们的代币ERC20:SPDXLicenseIdentifier:MITpragmasolidity0。8。9;importopenzeppelincontractstokenERC20ERC20。sol;contractNDTokenisERC20{constructor(stringmemoryname,stringmemorysymbol)ERC20(name,symbol){mint(msg。sender,100000(1018));}} 构造函数允许您设置代币名称和符号,mint函数允许您铸造代币并设置数量。 默认情况下,ERC20将小数位数设置为18,因此在我们的mint函数中,我们将100,000乘以10的18次方来铸造总共100,000个代币,每个代币有18个小数位(类似于1Eth由10到18wei〔36〕。 要部署,我们需要传入构造函数值(name和symbol),因此我们可能会在部署脚本中执行如下操作:constNDTokenawaithre。ethers。getContractFactory(NDToken);constndTokenawaitNDToken。deploy(NaderDabitToken,NDT); 通过扩展原始的ERC20代币,您的代币将继承以下所有功能和功能:functionname()publicviewreturns(string)functionsymbol()publicviewreturns(string)functiondecimals()publicviewreturns(uint8)functiontotalSupply()publicviewreturns(uint256)functionbalanceOf(addressowner)publicviewreturns(uint256balance)functiontransfer(addressto,uint256value)publicreturns(boolsuccess)functiontransferFrom(addressfrom,addressto,uint256value)publicreturns(boolsuccess)functionapprove(addressspender,uint256value)publicreturns(boolsuccess)functionallowance(addressowner,addressspender)publicviewreturns(uint256remaining) 部署后,您可以使用这些功能中的任何一个与新的智能合约进行交互。有关ERC20代币的另一个示例,请查看Soliditybyexample〔37〕结论 好吧,我们在这里涵盖了很多,但对我来说,这是开始使用这个堆栈的面包和黄油核心,也是我想要拥有的东西,不仅是作为一个正在学习所有这些东西的人,而且也是未来如果我需要参考我将来可能需要的任何东西。我希望你学到了很多。 如果除了MetaMask之外你还想支持多个钱包,请查看Web3Modal〔38〕,它可以通过相当简单和可自定义的配置轻松地在你的应用程序中实现对多个提供商的支持。 在我未来的教程和指南中,我将深入研究更复杂的智能合约开发,以及如何将它们部署为subgraphs〔39〕以在它们之上公开GraphQLAPI并实现分页和全文搜索等功能。 我还将探讨如何使用IPFS和Web3数据库等技术以分散的方式存储数据。 原文:https:web3。careerlearnweb3web3interviewquestions 引用链接 〔1〕此处:https:github。comdabit3fullstackethereum 〔2〕此处:https:www。youtube。comwatch?va0osIaAOFSE 〔3〕定义web3堆栈:https:edgeandnode。comblogdefiningtheweb3stack 〔4〕Hardhat:https:hardhat。org 〔5〕Ethers。js:https:docs。ethers。iov5 〔6〕TheGraphProtocol:https:thegraph。com 〔7〕scaffoldeth:https:github。comaustintgriffithscaffoldeth 〔8〕Ganache:https:www。trufflesuite。comganache 〔9〕Truffle:https:www。trufflesuite。com 〔10〕Foundry:https:www。paradigm。xyz202112introducingthefoundryethereumdevelopmenttoolbox 〔11〕ethers。js:https:docs。ethers。iov5 〔12〕web3。js:https:web3js。readthedocs。ioenv1。3。4 〔13〕Metamask:https:metamask。iodownload。html 〔14〕Next。js:https:nextjs。org 〔15〕Gatsby:https:www。gatsbyjs。com 〔16〕Redwood:https:redwoodjs。com 〔17〕Blitz。js:https:blitzjs。com 〔18〕在以太坊上构建GraphQLAPI:https:dev。todabit3buildinggraphqlapisonethereum4poa 〔19〕MetaMask:https:metamask。io 〔20〕ethers。js:https:docs。ethers。iov5 〔21〕hardhat:https:github。comnomiclabshardhat 〔22〕MetaMask配置问题:https:hardhat。orgmetamaskissue。html 〔23〕我们还需要更新已编译合约的工件:https:hardhat。orgguidescompilecontracts。htmlartifacts 〔24〕ABI:https:docs。soliditylang。orgenv0。5。3abispec。html 〔25〕您还可以经常在Etherscan:https:etherscan。io 〔26〕此处的:https:hardhat。orghardhatrunnerpluginsnomicfoundationhardhattoolbox 〔27〕人类可读的ABI:https:blog。ricmoo。comhumanreadablecontractabisinethersjs141902f4d917 〔28〕接下来,通过访问这个:https:faucet。egorfine。com 〔29〕我们可以通过注册Infura:https:infura。iodashboardethereumcbdf7c5eee8b4e2b91e76b77ffd34533settings 〔30〕Alchemy:https:www。alchemyapi。io 〔31〕您现在应该能够在EtherscanRopstenTestnetExplorer:https:ropsten。etherscan。io 〔32〕ERC20:https:eips。ethereum。orgEIPSeip20 〔33〕我们将在这里:https:dev。todabit3thecompleteguidetofullstackethereumdevelopment3j13erc20token 〔34〕代币标准:https:ethereum。orgendevelopersdocsstandardstokenserc20 〔35〕OpenZepplin:https:github。comOpenZeppelinopenzeppelincontracts 〔36〕wei:https:www。investopedia。comtermswwei。asp 〔37〕Soliditybyexample:https:soliditybyexample。orgapperc20 〔38〕Web3Modal:https:github。comWeb3Modalweb3modal 〔39〕subgraphs:https:thegraph。comdocsdefineasubgraph