To build a frontend application for the counter contract, we'll do the following:
fuels
SDK dependency. Make sure your browser is using the latest version of chromium.
Our frontend application will allow users to connect with a wallet, so you'll need to have a browser wallet installed.
Before going to the next steps, install the Fuel Wallet here .
If you have previously installed the wallet, make sure you have updated to the latest version.
Once you've setup your wallet, click the "Faucet" button in the wallet to get some testnet tokens.
To split our project's contract from frontend code, let's initialize our frontend project: assuming that your terminal is open at your contract's folder /home/user/path/to/counter-contract
let's go back up one directory.
cd ..
Now, initialize a React project with TypeScript using Create React App
.
npx create-react-app frontend --template typescript
The output should be similar to this:
Success! Created frontend at Fuel/fuel-project/frontend
You should now have two folders inside your fuel-project
folder: counter-contract
and frontend
.
fuels
SDK dependency The fuels
package includes all the main tools you need to interact with your Sway programs and the Fuel network.
The @fuel-wallet
packages include everything you need to interact with user wallets.
Move into the frontend
folder by running:
cd frontend
fuels
requires Node version18.18.2 || ^20.0.0
.
Install the following packages in your frontend
folder:
npm install fuels@0.79.0 @fuels/react@0.18.0 @fuels/connectors@0.1.1 @tanstack/react-query@5.28.9
The fuels init
command generates a fuels.config.ts
file that is used by the SDK to generate contract types.
Use the contracts
flag to define where your contract folder is located, and the output
flag to define where you want the generated files to be created.
Run the command below in your frontend folder to generate the config file:
npx fuels init --contracts ../counter-contract/ --output ./src/sway-api
Now that you have a fuels.config.ts
file, you can use the fuels build
command to rebuild your contract and generate types.
Running this command will interpret the output ABI JSON from your contract and generate the correct TypeScript definitions.
If you see the folder fuel-project/counter-contract/out
you will be able to see the ABI JSON there.
Inside the fuel-project/frontend
directory run:
npx fuels build
A successful process should print and output like the following:
Building..
Building Sway programs using built-in 'forc' binary
Generating types..
š Build completed successfully!
If you're having any issues with this part, try adding
useBuiltinForc: false,
to yourfuels.config.ts
config file to make sure it's using the same version offorc
as your default toolchain.
Now you should be able to find a new folder fuel-project/frontend/src/sway-api
.
Inside the frontend/src
folder let's add code that interacts with our contract.
Because we'll be using @fuels/react
, first we need to wrap our app with the FuelProvider
component.
Add the imports below to the top of your frontend/src/index.tsx
file and setup a query client:
import { FuelProvider } from '@fuels/react';
import {
FuelWalletConnector,
FuelWalletDevelopmentConnector,
FueletWalletConnector,
} from '@fuels/connectors';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
Next, modify your frontend/src/index.tsx
file to wrap the App
component with the FuelProvider
and QueryClientProvider
components.
<QueryClientProvider client={queryClient}>
<FuelProvider
fuelConfig={{
connectors: [
new FuelWalletConnector(),
new FuelWalletDevelopmentConnector(),
new FueletWalletConnector(),
],
}}
>
<App />
</FuelProvider>
</QueryClientProvider>
Next, change the file fuel-project/frontend/src/App.tsx
to:
import { useEffect, useState } from "react";
import {
useConnectUI,
useIsConnected,
useWallet
} from '@fuels/react';
// Import the contract factory -- you can find the name in src/contracts/contracts/index.ts.
// You can also do command + space and the compiler will suggest the correct name.
import { CounterContractAbi__factory } from "./sway-api"
import type { CounterContractAbi } from "./sway-api";
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();
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()
.txParams({
gasPrice: 1,
gasLimit: 100_000,
})
.get();
setCounter(value.toNumber());
} catch(error) {
console.error(error);
}
}
const onIncrementPressed = async () => {
if (!contract) {
return alert("Contract not loaded");
}
try {
await contract.functions
.increment()
.txParams({
gasPrice: 1,
gasLimit: 100_000,
})
.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>
<button
onClick={onIncrementPressed}
style={styles.button}
>
Increment Counter
</button>
</>
) : (
<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",
marginTop: "24px",
backgroundColor: "#707070",
fontSize: "16px",
color: "#ffffffec",
border: "none",
outline: "none",
height: "60px",
padding: "0 1rem",
cursor: "pointer"
},
}
Finally, replace the value of the CONTRACT_ID
variable at the top of your App.tsx
file with the address of the contract you just deployed.
Inside the fuel-project/frontend
directory run:
npm start
Compiled successfully!
You can now view frontend in the browser.
Local: http://localhost:3000
On Your Network: http://192.168.4.48:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
Click the "Connect" button and select "Fuel Wallet Development" to connect your wallet. Once connected, you should see the counter value and increment button:
Here is the repo for this project .
If you run into any problems, a good first step is to compare your code to this repo and resolve any differences.
Tweet us @fuel_network letting us know you just built a dapp on Fuel, you might get invited to a private group of builders, be invited to the next Fuel dinner, get alpha on the project, or something š.
To develop and test faster, we recommend using the fuels dev
command to start a local node and automatically redeploy and generate types for your contract on each change.
Once you're ready to redeploy your contract to the testnet, here are the steps you should take to get your frontend and contract back in sync:
npx fuels build
. forc deploy --testnet
. App.tsx
file. Get help from the team by posting your question in the Fuel Forum .