将你的 Svelte 应用与智能合约集成
本文介绍了如何使用Svelte框架构建一个与以太坊Sepolia网络上的智能合约交互的示例应用。文章详细阐述了技术栈、环境配置、智能合约实现、前端与智能合约的交互逻辑,并提供了从本地测试部署到公共测试网的完整流程。文章结构清晰,包含步骤指导和代码示例,为开发者提供了实用的参考。
概述
今天,我们将构建一个示例应用程序,使用 Svelte 并将其连接到一个我们在 Sepolia 网络上部署的智能合约。Svelte 是一个免费的开源前端项目,正在持续开发中,允许用户轻松构建 Web 应用程序。
你将做什么
我们的前端将能够做到以下几点:
- 允许用户向合约所有者发送一条问候消息并挥手
- 在前端显示所有问候消息以及发送者的地址
- 一个用于存储用户问候消息和挥手的合约
我们的技术栈如下:
请参阅此链接 here 以查看最终代码。
你需要什么
- 在本地机器上安装的 Node.js 环境
- IDE 或代码编辑器(例如,VSCode)
- 熟悉 CLI(例如,终端,Windows Powershell)
- 在浏览器中安装的 MetaMask 扩展
- Sepolia 测试网的 ETH(你可以使用 QuickNode Multi-Chain Faucet)
| 依赖项 | 版本 |
|---|---|
| node.js | 18.18.1 |
| svelte | ^3.0.0 |
| hardhat | ^2.18.1 |
| ethers | ^5.7.2 |
| @rollup/plugin-json | ^6.0.1 |
| dayjs | ^1.10.7 |
| sirv-cli | ^1.0.0 |
| rollup | ^2.3.4 |
入门
通过运行以下命令来克隆 this svelte 启动模板,其中包含所有必需的组件和 CSS 属性。
使用 HTTPS:
git clone https://github.com/quiknode-labs/qn-guide-examples.git
或者,使用 SSH:
git clone git@github.com:quiknode-labs/qn-guide-examples.git
接下来,导航到项目目录(例如,qn-guide-examples/ethereum/wave-portal-svelte/code/wave-portal-starter-boilerplate)并通过运行以下命令安装 node_modules:
cd qn-guide-examples/ethereum/wave-portal-svelte/code/wave-portal-starter-boilerplate/
yarn
在 wave-portal-starter-boilerplate 目录中,安装 @nomicfoundation/hardhat-toolbox(其中包含我们进行智能合约开发所需的所有内容),运行以下命令:
yarn add --dev hardhat @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6
现在,让我们使用 Hardhat 初始化我们的智能合约开发环境。
初始化和配置 Hardhat 环境
要使用 Hardhat 创建一个新的智能合约开发环境,请运行下面的命令。在终端窗口中运行命令之前,请确保你处于 wave-portal-starter-boilerplate 文件夹。
npx hardhat init
在提示时选择以下设置:
- 你想做什么?选择 创建一个空的 hardhat.config.js 项目

现在,你应该在项目文件夹内看到一个 hardhat.config.js 文件。
接下来,打开你的 hardhat.config.js 文件。此文件包含有关 Hardhat 以太坊环境的所有配置。更新该文件的内容,使其如下所示:
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.4",
paths: {
artifacts: "./src/artifacts",
},
networks: {
hardhat: {
chainId: 1337,
},
},
};
有了我们的 Hardhat 配置设置后,让我们继续。
我们现在需要创建所需的文件。首先,我们将创建一个 contracts 并在其中创建一个名为 WavePortal.sol 的文件:
mkdir contracts
echo > contracts/WavePortal.sol
该文件将包含我们的智能合约代码。在此新文件中,粘贴以下智能合约代码。该合约将允许我们存储 wave,reaction type,greeting message 并向随机用户发放奖品。
你的 WavePortal.sol 文件应如下所示:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract WavePortal {
enum Reaction {
Wave,
Cake,
Hype
}
struct Wave {
Reaction reaction;
string message;
address waver;
uint256 timestamp;
}
uint256 totalWaves;
Wave[] public waveList;
event NewWave(
Reaction reaction,
string message,
address indexed from,
uint256 timestamp
);
constructor() {}
function wave(Reaction _reaction, string memory _message) public {
totalWaves += 1;
waveList.push(Wave(_reaction, _message, msg.sender, block.timestamp));
emit NewWave(_reaction, _message, msg.sender, block.timestamp);
}
function getAllWaves() public view returns (Wave[] memory) {
return waveList;
}
function getTotalWaves() public view returns (uint256) {
return waveList.length;
}
}
对上述代码的解释。
- 第 1 行:指定 SPDX 许可证 类型,这是 Solidity ^0.6.8 之后的补充。每当智能合约的源代码公开时,这些许可证可以帮助解决/避免版权问题。如果你不想指定任何许可证类型,你可以使用特殊值 UNLICENSED 或干脆跳过整个注释(这不会导致错误,只会引发警告)。
- 第 2 行:声明 Solidity 版本。
- 第 4 行:开始我们的合约名为
WavePortal。 - 第 5 行:将 Reaction 声明为 enum,具有
Wave,Cake和Hype作为预定义值。 - 第 11 行:将 Wave 声明为包含 Reaction 类型的变量 reaction,string 类型的 message,address 类型的 waver 和 uint 类型的 timestamp 的结构。
- 第 18 行:声明一个类型为 uint 的变量
totalWaves用于存储总波数。 - 第 19 行:声明一个 Wave 类型的数组
waveList用于存储所有波。 - 第 21 行:声明事件
NewWave,包含有关反应、消息、发送者地址和波的时间戳的信息。此事件在我们的波成功存储到合约时触发。 - 第 28 行:初始化构造函数,但无需设置任何内容,因此我们将其留空。
- 第 30 行:声明函数 wave,具有两个参数,_reaction 为类型
Reaction,将存储我们的反应信息,_message 为类型 string,将存储用户的消息。将 _message 声明为 memory 意味着,函数执行后将会从内存中销毁。 - 第 36 行:声明函数 getAllWaves,返回一个包含所有波的数组。
- 第 40 行:声明函数 getTotalWaves,返回波的数量。
与以太坊区块链交互
我们可以使用我们的合约的 ABI、ethers.js 库和我们的 contract address 从 Svelte 应用程序与智能合约交互。ABI 代表应用程序二进制接口。我们可以将 ABI 视为一个接口,提供前端与智能合约中的所有可调用函数和变量的访问。
我们可以通过使用 Hardhat 这样的开发框架编译我们的智能合约来获取 ABI。你经常可以在 Etherscan 上找到智能合约的 ABI。
所以,让我们通过运行下面的命令编译我们的智能合约。
npx hardhat compile
确保你在项目的根目录内(例如,wave-portal-starter-boilerplate)
在成功编译后,你应该在 code/wave-portal-starter-boilerplate/src 文件夹下看到一个名为 artifacts 的新文件夹。你可以在 artifacts/contracts/WavePortal.json 文件夹下找到我们合约的 ABI。我们可以通过简单地导入此 .json 文件来使用这个 ABI。
部署到本地
为了方便快速的交互,我们将把合约部署到本地测试节点。为此,我们需要在同一目录中创建另一个终端窗口(Mac 用户按 CMD + T;Windows 用户按 CTRL + SHIFT + T)。然后,运行以下命令启动本地链节点:
npx hardhat node
运行此命令后,将在终端中列出所有帐户及其私钥:

这些是 Hardhat 创建给我们的 20 个测试帐户,我们可以使用这些帐户在本地部署和测试我们的智能合约。每个帐户都有足够数量的测试 Ether。
让我们使用这些帐户之一将智能合约部署到本地主机。但在此之前,在我们的 scripts 文件夹中,我们将创建一个名为 deploy.js 的文件:
mkdir scripts
echo > scripts/deploy.js
此文件将在我们尝试部署合约时执行。在这个 deploy.js 文件中,包含以下代码:
const hre = require("hardhat")
async function main() {
const WavePortal = await hre.ethers.getContractFactory("WavePortal");
const wavePortal = await WavePortal.deploy();
await wavePortal.waitForDeployment();
console.log("WavePortal deployed to:", wavePortal.target);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
让我们回顾一下代码。
- 第 1 行:声明异步函数 main,当我们部署智能合约时执行。
- 第 4 行:调用 hre.ethers.getContractFactory,返回合约的实例。
- 第 5 行:调用
.deploy(),这将部署合约的实例。 - 第 6 行:调用
.waitForDeployment(),等待合约部署。 - 第 7 行:在控制台记录已部署合约的地址。
- 第 10 行:调用
main()函数并添加错误处理。
现在,让我们运行部署脚本并提供标志 --network localhost 给 CLI,指示我们希望将合约部署到本地网络。
npx hardhat run scripts/deploy.js --network localhost
该脚本将我们的智能合约部署到本地网络,现在我们应该能够与之交互。
在成功部署后,你应该在终端中看到以下输出:

注意: 此合约是在本地节点上部署的,使用了我们启动本地网络时创建的第一个帐户。
请存储此地址以供参考,因为我们将在与 Svelte 客户端的智能合约交互时需要它。
现在,为了向我们在测试节点上部署的智能合约发送交易,我们需要配置 MetaMask 钱包以使用 Hardhat 环境创建的帐户之一。
让我们将其中一个帐户导入我们 MetaMask 钱包并使用其测试 ETH。为此,请按照以下步骤操作:
- 在左上角,选择你当前连接的网络。
- 选择 添加网络。
- 选择 手动添加网络。
- 输入网络名称:Localhost 8545
- 输入你的 Hardhat 网络 RPC URL,http://127.0.0.1:8545/(或 http://localhost:8545)。
- 输入货币符号:ETH
- 输入你的 Hardhat 网络链 ID,1337(或 0x539 的十六进制格式)。
添加自定义网络后,将你的网络更改为新添加的网络。接下来,我们将从 Hardhat 环境中导入 MetaMask 的其中一个私钥。
在 MetaMask 扩展程序顶部,单击 账户 下拉,然后单击 导入账户。输入在运行 Hardhat 本地区块链的终端窗口中获得的私钥,然后单击 导入。确保在继续进行下一个步骤之前选择此帐户。
由于我们的合约已成功部署并且 MetaMask 钱包已配置,让我们开始从 Svelte 前端进行交互。
使用 Svelte 连接到以太坊
在本教程中,我们将不专注于开发用户界面。相反,我们将完全专注于核心功能和集成。在前端中,我们将主要关注两件事:
- 从智能合约中获取所有问候消息并将其显示在前端。
- 创建函数以发送消息和问候。
让我们开始与前端的集成。要测试我们的前端,使用以下命令启动服务器。但在启动服务器之前,请确保当前目录位于 code/wave-portal-starter-boilerplate/:
yarn dev
现在,你应该在 localhost:5000 上看到如下前端。如果 localhost:5000 无法工作,则尝试 http://127.0.0.1:5000/。
注意:如果你是 M1 用户,你可能需要在 Mac 上禁用 Airplay 设置,以使其正常工作。

你会注意到,通过单击任何一个问候,什么也没有发生。同时,我们无法看到任何以前的问候。因此,让我们添加逻辑,将问候发送到我们的智能合约并获取所有以前的问候。
导航到 code/wave-portal-starter-boilerplate/src 文件夹下的 App.svelte 文件。App.svelte 在服务器启动时呈现到主页,因此包含获取波的所有功能。
更新你在部署时记录到 CLI 中的合约地址,放在第 10 行。
const CONTRACT_ADDRESS = 'YOUR_CONTACT_ADDRESS';
现在,在 App.svelte 的第 12 行的 getAllWaves() 函数中粘贴以下代码。此函数从区块链网络提取所有问候到我们的客户端。
async function getAllWaves() {
if (!window.ethereum) {
return;
}
const provider = new ethers.providers.Web3Provider(window.ethereum);
const wavePortalContract = new ethers.Contract(
CONTRACT_ADDRESS,
WavePortal.abi,
provider
);
const recievedWaves = await wavePortalContract.getAllWaves();
if (!recievedWaves) {
waveList = [];
return;
}
const normalizeWave = (wave) => ({
reaction: wave.reaction,
message: wave.message,
waver: wave.waver,
timestamp: new Date(wave.timestamp * 1000),
});
waveList = recievedWaves
.map(normalizeWave)
.sort((a, b) => b.timestamp - a.timestamp);
console.log('waveList: ', waveList);
return;
}
对上述代码的解释。
- 第 1 行:声明一个异步函数 getAllWaves(),该函数将从我们的智能合约获取所有波。
- 第 2 行:检查我们是否在窗口中获得一个 ethereum 对象,如果没有,则返回 null。
- 第 6 行:获取 provider 以访问区块链数据。
- 第 7 行:通过传递 合约地址,合约 abi 和 provider 创建我们合约的本地实例。
- 第 12 行:通过调用 getAllWaves() 方法从我们的智能合约中获取所有波。
- 第 14-17 行:如果我们没有得到任何波,则返回一个空数组。
- 第 19 行:声明 normalizeWave() 函数,用于解构波。
- 第 26 行:解构 recievedWaves(),基于 timestamp 进行排序,并将这些排序后的波分配给 waveList 变量。
我们必须导入 WavePortal 合约的 ABI,以便能够与我们的智能合约交互。因此,将以下导入语句添加到 App.svelte 的第 3 行。
import { ethers } from 'ethers';
import WavePortal from './artifacts/contracts/WavePortal.sol/WavePortal.json'
你应该会看到如下所示的错误。

这是因为我们尝试在 App.svelte 中导入 JSON 文件,而要导入 JSON 文件,我们需要通过运行以下命令添加额外的插件 rollup-plugin。
yarn add @rollup/plugin-json
导航到 code/wave-portal-starter-boilerplate/ 目录中的 rollup.config.js 文件。此文件包含所有有关 rollup 的配置。现在,在你的 rollup.config.js 文件中导航到插件数组,如下所示并添加 json(),位于第 60 行。
plugins: [\
commonjs(),\
json(),\
...\
]
在 code/wave-portal-starter-boilerplate/ 目录中的 rollup.config.js 文件中,为了使用 json(),我们还需要从我们新添加的插件中导入 json(),因此在 rollup.config.js 文件的第 7 行添加以下导入语句。
import json from "@rollup/plugin-json";
现在,重新启动开发服务器,你应该看到前端服务器成功启动。目前,你不会在前端看到任何问候,因为我们没有问候。所以,让我们添加一个发送问候的功能。
为此,导航到 code/wave-portal-starter-boilerplate/src/components/SendWave.svelte 文件。此文件将包含发送波的逻辑。通过粘贴以下代码来完成第 7 行的 sendWaveReaction() 函数。此功能将发送 wave reaction。
async function sendWaveReaction(reaction, message) {
loading = true;
try {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const wavePortalContract = new ethers.Contract(
CONTRACT_ADDRESS,
WavePortal.abi,
signer
);
const transaction = await wavePortalContract.wave(reaction, message, {
gasLimit: 400000,
});
await transaction.wait();
message = '';
fetchWaves();
loading = false;
} catch (error) {
alert('Error while sending wave', error);
loading = false;
}
}
以上代码的回顾。
- 第 1 行:声明一个异步函数 sendWaveReaction(),该函数将向我们的智能合约发送反应。
- 第 2 行:将 loading 变量设置为 true。
- 第 4 行:声明一个 provider 变量,其中包含用于访问区块链数据的只读抽象。
- 第 5 行:从 provider 中储存 signer 对象到 singer 变量,以便我们可以签署交易。
- 第 6 行:创建我们的智能合约的本地实例。
- 第 11 行:调用智能合约中的 wave() 函数,使用 reaction 和 message 作为参数。
- 第 14 行:等待事务完成。
- 第 15 行:发送波后重置消息变量的值。
- 第 16 行:再次获取所有波。
- 第 17 行:将加载指示器设置为 false。
- 第 19 行:如果有问题,显示一个 alert。
我们还需要在 SendWave.svelte 文件中第 2 行添加以下导入语句。
import { ethers } from 'ethers';
import WavePortal from '../artifacts/contracts/WavePortal.sol/WavePortal.json';
为了从我们的前端与智能合约交互,我们需要将 MetaMask 钱包连接到我们的网站。为此,在 code/wave-portal-starter-boilerplate/src/components/Wallet.svelte 文件中,通过粘贴以下代码在第 6 行来完成 connectWallet() 函数。 Wallet.svelte 将包含将 MetaMask 钱包连接到前端所需的所有逻辑。
async function connectWallet() {
walletConnected = false;
const { ethereum } = window;
await ethereum
.request({ method: 'eth_requestAccounts' })
.then((accountList) => {
const [firstAccount] = accountList;
account = firstAccount;
walletConnected = true;
})
.catch((error) => {
walletConnected = false;
connectWalletError = error;
console.log('error connecting wallet');
});
}
以上代码的解释:
- 第 1 行:声明一个异步函数 connectWallet()。
- 第 2 行:将 walletConnected 变量设置为 false。
- 第 3 行:从窗口获取 ethereum 对象。
- 第 4 行:调用 ethereum.request
({ method: 'eth_requestAccounts' }),这将提供连接钱包的帐户。 - 第 7 行:从所有帐户的数组中获取 第一个帐户。
- 第 8 行:将 account 变量的值分配为 第一个帐户。
- 第 9 行:将 walletConnected 变量设为 true。
- 第 12 行:如果连接钱包时遇到错误,则将 walletConnected 变量设置为 false。
现在,重新启动服务器,如果需要,你应该在单击 连接 MetaMask 按钮时看到 MetaMask 弹出窗口。连接后,我们将能够成功发送问候、挥手,并获取所有问候。
现在,我们的智能合约在本地节点上成功运行,因此让我们在实时公共测试网上部署它。你可以随意关闭在 Hardhat 上运行的本地节点,因为不再需要它。
创建以太坊 Sepolia 端点
在本教程中,我们将部署到 Sepolia 测试网络。为此,我们需要更新我们的 MetaMask 钱包以连接到 Sepolia 网络,并向自己发送一些测试 ETH(你可以使用 QuickNode Multi-Chain Faucet)。不用担心,我们将向你展示每一步。
导航到 Endpoints 页面上的 QuickNode(不用担心,你可以免费创建一个帐户),然后单击 创建端点。然后,选择 Ethereum 链和 Sepolia 测试网络,然后单击 创建端点。

创建端点后,你将拥有一个 HTTP 和 WSS URL。为了本指南的目的,请保留 HTTP 提供程序 URL,因为你稍后将需要它。
使用 QuickNode Multi-Chain Faucet 获取 ETH
由于我们将在测试网上部署智能合约,因此我们需要测试网 ETH 来支付交易费用。
你可以使用 QuickNode Multi-Chain Faucet 在许多区块链上获取测试 ETH。只需选择区块链和网络,然后粘贴你的钱包地址或连接钱包即可。

注意:你需要在以太坊主网中至少有 0.001 ETH 才能使用水龙头。
部署智能合约
要在测试网上部署,我们需要在 hardhat.config.js 中更新额外的网络和钱包信息。我们需要设置从中进行部署的钱包的私钥。你可以通过单击右上角的省略号(三个点),然后点击 账户详细信息 --> 显示私钥 来导出你的私钥。请保留此内容,以便你在下面的文件中使用。

因此,请打开 hardhat.config.js 文件并输入以下代码:
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.4",
paths: {
artifacts: './src/artifacts',
},
networks: {
hardhat: {},
sepolia: {
url: "<YOUR_QUICKNODE_URL_HERE>",
accounts: [`0x${your-private-key}`]
}
},
};
你需要将 YOUR_QUICKNODE_URL_HERE 替换为你在 创建以太坊 Sepolia 端点 中获得的 HTTP 提供程序 URL。此外,用你从 MetaMask 获得的私钥替换 your-private-key 占位符。
现在,运行以下命令,将我们的智能合约部署到 Sepolia 网络,确保你在根目录(例如,code/wave-portal-starter-boilerplate/)内。
npx hardhat run scripts/deploy.js --network sepolia
在成功部署后,你应该在终端中看到以下输出。
WavePortal deployed to: 0x4f5F98f3696e1dDc107fd786d252D6Ff8D351B6b
你需要在 src/App.svelte 中更新你的合约地址,使用你刚刚在 Sepolia 区块链上创建的合约地址。
另外,由于我们的前端应用程序使用与 Hardhat toolbox 不同版本的 Ethers,我们需要将其降级到正确的版本:
yarn remove ethers
yarn add --dev ethers@5.7.2
刷新你的前端应用并连接你的钱包,现在你应该能够在 Sepolia 区块链上与其进行交互。
结论
恭喜!你已使用 Svelte 集成了你的第一个 dApp。请尝试上述挑战,如果你遇到任何错误,随时通过 Twitter 联系我(@0xCrispy)。
请注意,此项目中仍有许多内容可以改进。因此,我们挑战你尝试以下内容:
- 在交易进行时显示加载指示器
- 检测 MetaMask 的当前网络。如果用户处于 Sepolia 以外的网络,则提示用户更改网络。
如果你有任何问题,请随时通过我们专用的 Discord 频道与我们联系或使用下面的表单提供反馈。通过关注我们的 Twitter 和 Telegram 公告频道,随时了解最新动态。
我们 ❤️ 反馈!
让我们知道 如果你有任何反馈或对新主题的请求。我们非常乐意听取你的意见。
- 原文链接: quicknode.com/guides/eth...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~