将你的 Svelte 应用与智能合约集成

  • QuickNode
  • 发布于 2024-03-16 11:17
  • 阅读 19

本文介绍了如何使用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

该文件将包含我们的智能合约代码。在此新文件中,粘贴以下智能合约代码。该合约将允许我们存储 wavereaction typegreeting 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,具有 WaveCakeHype 作为预定义值。
  • 第 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 连接到以太坊

在本教程中,我们将不专注于开发用户界面。相反,我们将完全专注于核心功能和集成。在前端中,我们将主要关注两件事:

  1. 从智能合约中获取所有问候消息并将其显示在前端。
  2. 创建函数以发送消息和问候。

让我们开始与前端的集成。要测试我们的前端,使用以下命令启动服务器。但在启动服务器之前,请确保当前目录位于 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 行:通过传递 合约地址合约 abiprovider 创建我们合约的本地实例。
  • 第 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() 函数,使用 reactionmessage 作为参数。
  • 第 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 测试网络,然后单击 创建端点

Google Sign Up

创建端点后,你将拥有一个 HTTP 和 WSS URL。为了本指南的目的,请保留 HTTP 提供程序 URL,因为你稍后将需要它。

使用 QuickNode Multi-Chain Faucet 获取 ETH

由于我们将在测试网上部署智能合约,因此我们需要测试网 ETH 来支付交易费用。

你可以使用 QuickNode Multi-Chain Faucet 在许多区块链上获取测试 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 频道与我们联系或使用下面的表单提供反馈。通过关注我们的 TwitterTelegram 公告频道,随时了解最新动态。

我们 ❤️ 反馈!

让我们知道 如果你有任何反馈或对新主题的请求。我们非常乐意听取你的意见。

  • 原文链接: quicknode.com/guides/eth...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。