开发dApp的三个步骤
原文链接:https://blog.chain.link/how-to-build-a-dapp/
(文中链接复制至浏览器打开)
去中心化应用,或者叫 dApp,是一种不依赖于中心化服务器的应用。相反,dApp 使用像是区块链和预言机这些 Web3 技术,来实现自己的逻辑和后台功能,具备不可篡改和安全的特性。
在这个技术教程中,你会学习到怎样开发一个 end-to-end 的 dApp。在 dApp 中,用户可以通过一个智能合约,获取和存储 ETH 的当前价格。这个教程 demo 代码存储在 Github 中。
demo 代码:
https://github.com/pappas999/chainlink-dapp-tutorial
要求 你需要先安装以下软件: NodeJS:https://nodejs.org/ MetaMask:https://metamask.io/ 去中心化应用是什么? 与传统的 App 在中心化服务器运行后端代码不同的是,dApp 的后端代码是运行在区块链上的。当然,dApp 的前端代码和 UI 可以使用任何语言开发,可以部署在任何服务器上与后端逻辑相交互。 因为 dApp 可以通过安全性很高且不可篡改的智能合约来承载后端逻辑,所以 dApp 有很多 Web2 系统中没有的优势: 不会宕机 隐私性更强 抗操纵 在最小信任环境下执行逻辑 然而,这些优势也带来了对应的缺点。因为代码是部署在区块链上,这些逻辑默认是无法修改的,所以 dApp 的维护难度比较高。除此以外,因为代码是运行在分布式网络中,而不是中心化服务器,所以性能会比较低。另外,由于用户需要有 Web3 钱包并且通过有足够的加密资产来支付手续费,所以用户体验也会下降。 dApp 组件 dApp 的组件会有三个不同的类型:智能合约,前端逻辑(UI)和数据存储。 智能合约 智能合约存储了 dApp 的业务逻辑和当前的状态,这个是 dApp 和传统网络应用的最大区别,也正是因为这一点让 dApp 具备了以上提到过的优势。 前端 / UI 尽管后端逻辑需要开发者完成智能合约代码,并把它部署在区块链上,但是在前端,开发者还是使用标准的网络技术,比如 HTML 和 javascript,因此开发者可以使用自己熟悉的工具,库和框架。客户端的 UI 通常通过 Web3.js 「https://web3js.readthedocs.io/en/v1.7.4/」和 Ether.js 「https://docs.ethers.io/v5/」与智能合约交互。像是对信息进行签名并且发送给智能合约这些操作,通常是通过浏览器的 Web3 钱包 MetaMask 完成。 数据存储 大多数应用需要存储数据,但是因为区块链分布式的特点,在链上存储大量的数据效率很低,而且非常贵。这也是为什么许多 dApp 需要使用 IPFS 「https://ipfs.io/」或者 Filecoin 「https://filecoin.io/」这样的链下存储服务来存储数据,只让区块链存储重要的业务逻辑和状态。 当然你也可以选择传统的云存储服务,然而还是有很多开发者选择分布式存储,因为区块链应用可以提供最小信任的特性。 Source以太坊 dApp 架构 来源:The Architecture of a Web3 Application 现在我们知道了 dApp 的组件,让我们开发一个简单的 dApp。 第一步:创建智能合约 我们 dApp 中的智能合约是一个简单的例子,它可以查看数据并且反应出区块链上的变化。在这个例子中,我们会通过 Chainlink ETH/USD 喂价对查看 ETH/USD 的价格,然后将结果永久存储在智能合约上。 第一步是打开 Chainlink 的文档,然后导航到 Using Data Feeds 页面。从这里将源代码复制进你的 IDE 中的一个新的文件里(比如 Visual Code),或者你可以点击“Open In Remix”按钮,然后使用在线 IDE Remix。 在这个例子中,我们会使用 Visual Studio Code 和 Hardhat(一个 EVM 开发框架)。 首先,为我们的 dApp 创建一个新的文件夹,并在这个文件夹中创建一个后端文件夹,用来存储智能合约代码: 接下来,我通过 VS Code 打开创建好的文件夹,然后安装 Hardhat: 当安装完成之后,在“contracts”文件夹中删掉 Touch.sol ,然后在这个文件夹中创建一个叫做 PriceConsumerV3.sol 的文件。在这个文件将存储我们的合约,所以将 Chainlink 文档中的代码复制到这个文件中,然后保存。 在样例代码中,你会看到 demo 合约已经有一个叫做 getLatestPrice 的功能来通过 Rinkeby 上的 ETH/USD 喂价对查看 Ethereum 的当前价格。 创建一个新的变量和函数,在智能合约上储存这个值。 然后,创建一个新的函数,它会被 dApp 的前端调用。这个函数会通过调用 getLatestPrice 函数查看 Ethereum 的最新价格,然后将这个值存储在 storedPrice 这个参数中: 你的新的合约应该和下面的一样: 第二步:部署智能合约 现在你已经可以在 Rinkeby 测试网中编译和部署你的合约了,如果没有测试网的通证的话,可以在 Chainlink faucet 获得一些。 如果你使用的是 Remix 的话,你可以通过 Remix 编译和部署你的合约。如果你使用的是像是 Visual Studio Code 这样的 IDE 的话,我们推荐使用 Hardhat 来管理你的合约。 在部署合约之前,第一步是安装 Hardhat 工具包,Chainlink 合约库和 dotenv 库。dotenv 可以将存储密码和敏感信息存储在一个单独的 .env 文件中: 然后,将 hardhat-config.js 文件中的内容换成下面的内容: 下一步是在 backend 文件夹中创建一个 .env 文件。然后你需要从 Web3 钱包中获取你的私钥,然后粘贴到 PRIVATE_KEY 这一行。请再确定一下,为了安全你在这个例子中最好使用一个在主网上没有任何的资产的新 Web3 钱包。 当这些做完以后,你需要一个 RPC endpoint 来接入 Rinkeby 网络。你可以将它粘贴到 .env 文件的 RINKEBY_RPC_URL 中的 RPC URL 中。我们推荐注册一个免费的 Infura 或者 Alchemy 账户以获取一个 RPC URL。 创建 .env 文件 下一步是修改“script” 文件夹中 deploy.js 文件中的内容,使得它可以部署你的新合约。打开文件,然后将代码替换为下列代码。 现在你已经可以通过 Hardhat 来编译你的智能合约并且把它部署在 Rinkeby 网络中: 你现在应该看到类似下面这行的信息,会展示你部署在 Rinkeby 网络上的智能合约地址。注意这个地址,我们在后面的步骤中需要用到它。 部署的智能合约 恭喜,你已经完成了 dApp 的合约部分! 第三步:创建前端应用 dApp 的前端逻辑和 UI 可以通过各种框架完成。 React 「https://reactjs.org/」是最受欢迎的 Javascript 代码库之一,它可以用来开发功能丰富的网页,因此也被许多 Web3 dApp 所使用。除此之外,Ether.js 是一个 Javascript 库,它是用来和 EVM 区块链连接和交互的。当你把这两者结合起来,就可以开始开发你的 dApp 的前端的了。 在这部分,我们将使用 create-react-app 「https://create-react-app.dev/」创建一个新的 React 应用。然后我会介绍如何通过 Ether.js 来将 UI 和已经部署的智能合约连接起来,完成一个 end-to-end 的 dApp。 创建 React 应用 开发前端代码之前,需要先安装和初始化一个 cerate-react-app 项目,然后修改它以满足我们的 dApp。第一步将这个库安装到“frontend”文件夹: 这一步完成后,你应该可以在 “frontend” 文件夹中看到所有相关的 React 代码。打开“frontend” 文件夹然后做以下操作: 删除 /src/setupTests.js 删除 /src/ReportWebVitals.js 删除 /src/logo.svg 删除 /src/App.test.js 删除 /src/App.css 文件夹结构应该如下所示: React front-end 文件夹结构 在修改 React 应用代码之前,我们需要先安装 Bootstrap 和 Ether.js。Bootstrap 是一个很流行的前端 CSS 框架,有很多 React 可以使用的 UI widgets 和 CSS 样式。Ether.js 可以将前端代码与区块链上已经部署的智能合约相连接。在 “frontend” 文件夹中输入以下命令: 现在我们可以开始修改 React 应用的代码,在 /src/ 文件夹中打开 App.js 文件,然后删掉这些内容。我们从 0 开始编写。 第一步是告诉 app 我们想要使用 React(包括 useEffect 和 useState 库)和 Ether.js: 下一步,创建一个叫 “App” 的函数然后 export 它: 现在我们将开始完成 “App” 函数的代码。加入下面的代码,这些代码会做以下的操作: 建立 storePrice 和 setStoresPrice 的 react hook。 连接你的 Metamask Web3 钱包。 设置已经部署的智能合约地址和 ABI。Ether.js 在与已经部署的合约交互的时候需要这两个信息。 把智能合约地址这个值(可以在部署的时候获得)插入到 REPLACE_WITH_DEPLOYED_CONTRACT_ADDRESS 这里。 智能合约的 ABI 可以从文件 /backend/artifacts/contracts/PriceConsumerV3.json 中获得,你还可以使用 code minifier 对它进行更好的格式化,存储在你的应用中。 现在我们在应用中创建两个函数: getStoredPrice 会连接部署的合约,并且通过 storedPrice() 获取当前价格。 setNewPrice 会调用已部署合约的 storeLatestPrice 函数,等到交易完成,然后调用 getStoredPrice 函数来获取存储在智能合约中的价格。 我们会在应用中加入 getStoredPrice 函数,它会在加载页面的时候调用 getter 函数: 前端代码的最后一步是返回 JSX 代码以让浏览器 render。将下面的代码复制进 App 的函数中,在 getStorePrice() 的下面。这些代码会做下面的操作: 返回一个简单的 2 列 grid layout。 第一列包含了智能合约中存储的 ETH/USD 价格。 第二列包含了一个按钮,用户可以使用这个按钮来与智能合约交互,更新存储的价格。点击按钮,然后调用下面的 setNewPrice 函数。
你的应用现在已经完成了。如果需要,你可以和这里完整代码「https://github.com/pappas999/chainlink-dapp-tutorial」比较,保证你的代码中没有错误。你可以运行你的 dApp 了。 运行你的 dApp 在确认你所有的文件都已经存储以后,在 frontend 文件夹中运行以下命令来启动你的 dApp: 在应用被载入以后,浏览器中会有一个新的窗口,展示 dApp 的 UI,你应该从 Metamask 看到一个弹出的通知,请求将钱包连接到这个应用上。 React 前端 在检查你在 Metamask 的账户中有一些 Rinkeby ETH 以后,点击 “Update” 按钮,就可以和你已经部署好的智能合约进行交互。你应该会收到 Metamask 的通知,请求你确认交易。在你完成这些以后,过几秒你的 dApp 会自动刷新,然后当前的 Ethereum 会出现在 “Stored Price” 区域: React 前端展示 Data Feed 结果 恭喜,你已经成功创建,部署并且交互了一个简单的 dApp!在这个教程中,你只是在你的电脑上运行了一个本地前端,同时你也可以把它部署在云服务器中,或者使用去中心化版本的前端,可以将其部署在 IPFS 中!你也可以修改应用的 CSS 来改变 UI 让它更符合你的使用场景。 总结 去中心化应用可以用像是区块链和智能合约这些 Web3 科技替代传统的后端服务器,带来传统应用没有安全性和抗操纵的特点。 在这个 demo 中,我们创建了一个简单的 dApp,dApp 中包含了一个智能合约,这个智能合约可以从 Chainlink ETH/USD 喂价对中获得最新的价格,然后储存在智能合约之中。然后我们创建了一个简单的 UI,使用了 React 和 Ether.js 连接并且与部署好的合约相交互。 您可以关注 Chainlink 预言机并且私信加入开发者社区,有大量关于智能合约的学习资料以及关于区块链的话题!
mkdir chainlink-dapp-examplecd chainlink-dapp-examplemkdir backendcd backend
npm init -ynpm install --save-dev hardhatnpx hardhat(choose create javascript project, choose default parameters)
function getLatestPrice() public view returns (int) { ( /*uint80 roundID*/, int price, /*uint startedAt*/, /*uint timeStamp*/, /*uint80 answeredInRound*/ ) = priceFeed.latestRoundData(); return price;
int public storedPrice;
function storeLatestPrice() external { storedPrice = getLatestPrice(); }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PriceConsumerV3 {
AggregatorV3Interface internal priceFeed;
int public storedPrice;
/**
* Network: Rinkeby
* Aggregator: ETH/USD
* Address: 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e
*/
constructor() {
priceFeed =
AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e);
}
/**
* Returns the latest price
*/
function getLatestPrice() public view returns (int) {
(
/*uint80 roundID*/,
int price,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = priceFeed.latestRoundData();
return price;
}
function storeLatestPrice() external {
storedPrice = getLatestPrice();
}
}
npm install --save-dev @nomicfoundation/hardhat-toolboxnpm install @chainlink/contracts --savenpm install dotenv
require("@nomicfoundation/hardhat-toolbox");
//require("@nomiclabs/hardhat-ethers")
require('dotenv').config()
const RINKEBY_RPC_URL = process.env.RINKEBY_RPC_URL ||
"https://eth-rinkeby.alchemyapi.io/v2/your-api-key"
const PRIVATE_KEY = process.env.PRIVATE_KEY || "abcdef"
module.exports = {
defaultNetwork: "rinkeby",
networks: {
hardhat: {
// // If you want to do some forking, uncomment this
// forking: {
// url: MAINNET_RPC_URL
// }
},
localhost: {
},
rinkeby: {
url: RINKEBY_RPC_URL,
accounts: [PRIVATE_KEY],
saveDeployments: true,
},
},
solidity: "0.8.9",
};
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");
async function main() {
const PriceConsumer = await hre.ethers.getContractFactory("PriceConsumerV3");
const priceConsumer = await PriceConsumer.deploy();
await priceConsumer.deployed();
console.log("Contract deployed to:", priceConsumer.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
npx hardhat compilenpx hardhat run --network rinkeby scripts/deploy.js
cd ..npx create-react-app frontend
cd frontendnpm install bootstrapnpm install ethers
import React, { useEffect, useState } from 'react';import { ethers } from "ethers";
function App() {
}
export default App;
const [storedPrice, setStoredPrice] = useState(''); const provider = new ethers.providers.Web3Provider(window.ethereum) const signer = provider.getSigner() const contractAddress = <REPLACE_WITH_DEPLOYED_CONTRACT_ADDRESS>’'; const ABI = '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getLatestPrice","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"storeLatestPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"storedPrice","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"}]' const contract = new ethers.Contract(contractAddress, ABI, signer);
const getStoredPrice = async () => {
try {
const contractPrice = await contract.storedPrice();
setStoredPrice(parseInt(contractPrice) / 100000000);
} catch (error) {
console.log("getStoredPrice Error: ", error);
}
}
async function updateNewPrice() {
try {
const transaction = await contract.storeLatestPrice();
await transaction.wait();
await getStoredPrice();
} catch (error) {
console.log("updateNewPrice Error: ", error);
}
}
getStoredPrice()
.catch(console.error)
return (
<div className="container">
<div className="row mt-5">
<div className="col">
<h3>Stored Price</h3>
<p>Stored ETH/USD Price: {storedPrice}</p>
</div>
<div className="col">
<h3>Update Price</h3>
<button type="submit" className="btn btn-dark"
onClick={updateNewPrice}>Update</button>
</div>
</div>
</div>
);
npm run start
比推快讯
更多 >>- 链上借贷协议Venus Protocol已于以太坊主网部署DAI市场
- 花旗现预计美联储将于7月降息,2024年共降息100个基点
- 零信任DePIN网络Staex完成战略轮融资,Moonrock Capital参投
- 灰度向Coinbase Prime等地址转入约1811.1枚BTC
- 比特币波动指数昨日降至60.53,日跌幅8.07%
- 美国利率期货合约交易员预计美联储九月首次降息的可能性增加
- 稳定币公司OpenDelta完成250万美元Pre-Seed轮融资
- 美国3月核心PCE物价指数年率2.8%,预期2.7%
- Web3游戏L3E7完成1000万美元融资
- 芝加哥期权交易所拟将数字资产衍生品业务整合到其全球衍生品和结算业务中
- 某鲸鱼地址将3527枚ezETH PT和3300枚ETH存入Zircuit
- Quantix Capital投资NFA Labs 1000万美美元
- Uniswap基金会向Auditless拨款120万美元以创建Protocol Grants Program
- Arthur Hayes:美财长耶伦或将推出天量流动性注入计划,加速加密牛市回归
- RWA协议The Conlony完成新一轮融资,TechStars等参投
- 名义价值93亿美元的BTC与ETH期权即将到期
- Amaranth基金会创始人花费2470万美元购买7814枚ETH
- Vitalik:PoW也相当中心化,PoW只是转向PoS之前的临时阶段
- 中国证监会科技监管司司长、信息中心主任姚前接受审查调查
- 比特币长期持有者已从1月份开始抛售仓位
- PenPad宣布获得加密投资机构Animoca Brands投资
- 富达现货比特币ETF首次录得资金流出
- 华盛证券成为首批虚拟资产现货ETF承销商
- Lava Network发布LAVA代币经济学,6.6%代币将用于API提供者奖励
- Apple工程师辞职构建以太坊Blobspace的衍生品产品
- BNB Chain宣布在BSC区块链中纳入原生流动性质押功能
- Base协议负责人:Base网络上USDC数量仅次于以太坊
- FBI 警告美国人不要使用未经注册的加密货币传输服务
- SEC 向 Consensys 发出关于 MetaMask 的 Wells 通知,称其作为无牌经纪商运营
- Pantera Capital计划为其新的加密基金筹集10亿美元
- Consensys 对 SEC 提起诉讼,以捍卫以太坊生态系统
- Axelar宣布将通过Filecoin虚拟机提供链上去中心化存储
- 金融科技巨头Stripe时隔六年后再次接受加密支付,目前支持 USDC 稳定币
- Paxos Treasury新增铸造1亿枚PYUSD
- 链上文化创意平台Spotlight完成200万美元Pre-Seed轮融资,Folius Ventures领投
- Starknet面向永续合约和期权协议开启DeFi Spring第三阶段
- 美众议院金融服务委员会高级议员:稳定币法案可能很快会出台
- Arkham:贝莱德在其 2 个 ETF 钱包中收到了超过 2万美元的符文代币空投
- 去中心化物理基础设施网络Natix完成460万美元战略融资,拟空投 10 亿枚 NATIX 代币
- Coinbase国际交易所将上线AEVO、ENA、ETHFI永续合约
- BTC突破64000美元,日内跌幅收窄至1.07%
- 9只现货比特币ETF今日净减持1,104枚BTC,净流出约7010万美元
- W代币现可通过Wormhole NTT在Solana、以太坊及L2网络上无缝转移
- Immutable 推出价值 5000 万美元的加密游戏奖励计划
- Wordcoin计划与PayPal和OpenAI进行合作
- 富兰克林顿普顿为其链上美国政府货币基金推出点对点转账服务
比推专栏
更多 >>观点
项目
比推热门文章
- 灰度向Coinbase Prime等地址转入约1811.1枚BTC
- 比特币波动指数昨日降至60.53,日跌幅8.07%
- 美国利率期货合约交易员预计美联储九月首次降息的可能性增加
- 稳定币公司OpenDelta完成250万美元Pre-Seed轮融资
- 美国3月核心PCE物价指数年率2.8%,预期2.7%
- Web3游戏L3E7完成1000万美元融资
- 芝加哥期权交易所拟将数字资产衍生品业务整合到其全球衍生品和结算业务中
- 某鲸鱼地址将3527枚ezETH PT和3300枚ETH存入Zircuit
- Quantix Capital投资NFA Labs 1000万美美元
- Uniswap基金会向Auditless拨款120万美元以创建Protocol Grants Program