要为计数器合约构建前端应用程序,我们将执行以下步骤:
我们的前端应用程序将允许用户连接钱包,因此您需要安装浏览器钱包。
在进行下一步之前,请安装 Fuel Wallet 插件。
设置好钱包后,点击钱包中的 "Faucet" 按钮以获取一些测试网代币。
为了将项目的合约代码与前端代码分开,让我们初始化前端项目:假设您的终端当前在合约文件夹 /home/user/path/to/counter-contract
中,先返回到上一级目录。
cd ..
现在,使用 Create React App
初始化一个带有 TypeScript 的 React 项目。
npx create-react-app frontend --template typescript
输出应类似于以下内容:
Success! Created frontend at Fuel/fuel-project/frontend
现在,您的 fuel-project
文件夹中应该有两个文件夹:counter-contract
和 frontend
。
fuels
SDK 依赖 fuels
包含了与 Sway 程序和 Fuel 网络交互所需的所有主要工具。
@fuel-wallet
包含了与用户钱包交互所需的所有工具。
通过运行以下命令进入 frontend
文件夹:
cd frontend
fuels
需要 Node 版本18.18.2 || ^20.0.0
。
在 frontend
文件夹中安装以下包:
npm install fuels @fuels/react @fuels/connectors @tanstack/react-query
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
中找到一个新文件夹。
在 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
文件,用 FuelProvider
和 QueryClientProvider
组件包装 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
变量的值替换为您刚刚部署的合约地址。
在 fuel-project/frontend
目录中运行:
npm start
编译成功!
您现在可以在浏览器中查看 frontend。
本地: http://localhost:3000
在您的网络上: http://192.168.4.48:3000
请注意,开发构建未经优化。
要创建生产构建,请使用 npm run build。
点击 "Connect" 按钮并选择您已安装的钱包以连接您的钱包。
一旦连接成功,如果您的钱包中没有资金,则会看到一个链接以获取测试网络资金。
如果您在 Fuel 上有测试网络 ETH,则应该看到计数器值和增加按钮:
如果您遇到任何问题,一个很好的第一步是将您的代码与此存储库进行比较并解决任何差异。
在 Twitter 上 @fuel_network 发推告诉我们您刚刚在 Fuel 上构建了一个 dapp,您可能会被邀请加入构建者的私人群组,受邀参加下一个 Fuel 晚宴,获得项目的 alpha 版,或者其他一些东西 👀。
为了更快地开发和测试,我们建议使用 fuels dev
命令 启动本地节点,并在每次更改时自动重新部署和生成合约类型。
一旦您准备好将合约重新部署到测试网络,以下是您应该执行的步骤,以使您的前端和合约重新同步:
npx fuels build
。 App.tsx
文件中的合约 ID。 通过在 Fuel 论坛 上发布您的问题来从团队获取帮助。