Building a Frontend

Icon Link构建与您的合约交互的前端

要为计数器合约构建前端应用程序,我们将执行以下步骤:

  1. 安装 Fuel 浏览器钱包。
  2. 初始化 React 项目。
  3. 安装 fuels SDK 依赖。
  4. 生成合约类型。
  5. 编写前端代码。
  6. 运行项目。

Icon Link安装 Fuel 浏览器钱包

我们的前端应用程序将允许用户连接钱包,因此您需要安装浏览器钱包。

在进行下一步之前,请安装 Fuel Wallet Icon Link 插件。

设置好钱包后,点击钱包中的 "Faucet" 按钮以获取一些测试网代币。

Icon Link初始化 React 项目

为了将项目的合约代码与前端代码分开,让我们初始化前端项目:假设您的终端当前在合约文件夹 /home/user/path/to/counter-contract 中,先返回到上一级目录。

cd ..

现在,使用 Create React App Icon Link 初始化一个带有 TypeScript 的 React 项目。

npx create-react-app frontend --template typescript

输出应类似于以下内容:

Success! Created frontend at Fuel/fuel-project/frontend

现在,您的 fuel-project 文件夹中应该有两个文件夹:counter-contractfrontend

project folder structure

Icon Link安装 fuels SDK 依赖

fuels 包含了与 Sway 程序和 Fuel 网络交互所需的所有主要工具。 @fuel-wallet 包含了与用户钱包交互所需的所有工具。

Icon Link安装

通过运行以下命令进入 frontend 文件夹:

cd frontend
Icon InfoCircle

fuels 需要 Node 版本 18.18.2 || ^20.0.0

frontend 文件夹中安装以下包:

npm install fuels @fuels/react @fuels/connectors @tanstack/react-query

Icon Link生成合约类型

fuels init 命令会生成一个 fuels.config.ts 文件,该文件用于 SDK 生成合约类型。 使用 contracts 标志来定义合约文件夹的位置,使用 output 标志来定义生成的文件的创建位置。

在前端文件夹中运行以下命令以生成配置文件:

npx fuels init --contracts ../counter-contract/ --output ./src/sway-api

现在,您有了一个 fuels.config.ts 文件,可以使用 fuels build 命令来重新构建合约并生成类型。 运行此命令将解释合约输出的 ABI JSON 并生成正确的 TypeScript 定义。 如果您查看 fuel-project/counter-contract/out 文件夹,可以看到 ABI JSON 文件。

fuel-project/frontend 目录中运行:

npx fuels build

成功的过程应打印如下输出:

Building..
Building Sway programs using source 'forc' binary
Generating types..
🎉  Build completed successfully!

现在,您应该能够在 fuel-project/frontend/src/sway-api 中找到一个新文件夹。

Icon Link修改应用程序

frontend/src 文件夹中添加与合约交互的代码。

因为我们将使用 @fuels/react,所以首先需要使用 FuelProvider 组件包装我们的应用程序。

frontend/src/index.tsx 文件的顶部添加以下导入并设置查询客户端:

import { FuelProvider } from '@fuels/react';
import {
  defaultConnectors,
} from '@fuels/connectors';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 
const queryClient = new QueryClient();

接下来,修改 frontend/src/index.tsx 文件,用 FuelProviderQueryClientProvider 组件包装 App 组件。

    <QueryClientProvider client={queryClient}>
      <FuelProvider
        fuelConfig={{
          connectors: defaultConnectors(),
        }}
      >
        <App />
      </FuelProvider>
    </QueryClientProvider>

接下来,将 fuel-project/frontend/src/App.tsx 文件更改为:

import { useEffect, useState } from "react";
import {
  useBalance,
  useConnectUI,
  useIsConnected,
  useWallet
} from '@fuels/react';
import { CounterContractAbi__factory  } from "./sway-api"
import type { CounterContractAbi } from "./sway-api";
 
// REPLACE WITH YOUR CONTRACT ID
const CONTRACT_ID = 
  "0x...";
 
export default function Home() {
  const [contract, setContract] = useState<CounterContractAbi>();
  const [counter, setCounter] = useState<number>();
  const { connect, isConnecting } = useConnectUI();
  const { isConnected } = useIsConnected();
  const { wallet } = useWallet();
  const { balance } = useBalance({
    address: wallet?.address.toAddress(),
    assetId: wallet?.provider.getBaseAssetId(),
  });
 
  useEffect(() => {
    async function getInitialCount(){
      if(isConnected && wallet){
        const counterContract = CounterContractAbi__factory.connect(CONTRACT_ID, wallet);
        await getCount(counterContract);
        setContract(counterContract);
      }
    }
    
    getInitialCount();
  }, [isConnected, wallet]);
 
  const getCount = async (counterContract: CounterContractAbi) => {
    try{
      const { value } = await counterContract.functions
      .count()
      .get();
      setCounter(value.toNumber());
    } catch(error) {
      console.error(error);
    }
  }
 
  const onIncrementPressed = async () => {
    if (!contract) {
      return alert("Contract not loaded");
    }
    try {
      await contract.functions
      .increment()
      .call();
      await getCount(contract);
    } catch(error) {
      console.error(error);
    }
  };
 
  return (
    <div style={styles.root}>
      <div style={styles.container}>
        {isConnected ? (
          <>
            <h3 style={styles.label}>Counter</h3>
            <div style={styles.counter}>
              {counter ?? 0}
            </div>
 
            {balance && balance.toNumber() === 0 ? (
              <p>Get testnet funds from the <a target="_blank" rel="noopener noreferrer"  href={`https://faucet-testnet.fuel.network/?address=${wallet?.address.toAddress()}`}>Fuel Faucet</a> to increment the counter.</p>
          ) : 
          (
            <button
            onClick={onIncrementPressed}
            style={styles.button}
            >
              Increment Counter
            </button>
          )
          }
            
          <p>Your Fuel Wallet address is:</p>
          <p>{wallet?.address.toAddress()}</p>
          </>
        ) : (
          <button
          onClick={() => {
            connect();
          }}
          style={styles.button}
          >
            {isConnecting ? 'Connecting' : 'Connect'}
          </button>
        )}
      </div>
    </div>
  );
}
 
const styles = {
  root: {
    display: 'grid',
    placeItems: 'center',
    height: '100vh',
    width: '100vw',
    backgroundColor: "black",
  } as React.CSSProperties,
  container: {
    color: "#ffffffec",
    display: "flex",
    flexDirection: "column",
    alignItems: "center",
  } as React.CSSProperties,
  label: {
    fontSize: "28px",
  },
  counter: {
    color: "#a0a0a0",
    fontSize: "48px",
  },
  button: {
    borderRadius: "8px",
    margin: "24px 0px",
    backgroundColor: "#707070",
    fontSize: "16px",
    color: "#ffffffec",
    border: "none",
    outline: "none",
    height: "60px",
    padding: "0 1rem",
    cursor: "pointer"
  },
}

最后,将 App.tsx 文件顶部的 CONTRACT_ID 变量的值替换为您刚刚部署的合约地址。

Icon Link运行您的项目

fuel-project/frontend 目录中运行:

npm start
编译成功!

您现在可以在浏览器中查看 frontend。

  本地:            http://localhost:3000
  在您的网络上:  http://192.168.4.48:3000

请注意,开发构建未经优化。
要创建生产构建,请使用 npm run build。

点击 "Connect" 按钮并选择您已安装的钱包以连接您的钱包。

一旦连接成功,如果您的钱包中没有资金,则会看到一个链接以获取测试网络资金。

如果您在 Fuel 上有测试网络 ETH,则应该看到计数器值和增加按钮:

screenshot of the UI

Icon Link您刚刚在 Fuel 上构建了一个全栈 dapp! ⛽

这是此项目的存储库 Icon Link

如果您遇到任何问题,一个很好的第一步是将您的代码与此存储库进行比较并解决任何差异。

在 Twitter 上 @fuel_network 发推告诉我们您刚刚在 Fuel 上构建了一个 dapp,您可能会被邀请加入构建者的私人群组,受邀参加下一个 Fuel 晚宴,获得项目的 alpha 版,或者其他一些东西 👀。

Icon Link更新合约

为了更快地开发和测试,我们建议使用 fuels dev 命令 启动本地节点,并在每次更改时自动重新部署和生成合约类型。

一旦您准备好将合约重新部署到测试网络,以下是您应该执行的步骤,以使您的前端和合约重新同步:

  • 在您的前端目录中,重新运行此命令:npx fuels build
  • 在您的合约目录中,重新部署合约。
  • 在您的前端目录中,更新 App.tsx 文件中的合约 ID。

Icon Link需要帮助吗?

通过在 Fuel 论坛 Icon Link 上发布您的问题来从团队获取帮助。