# TypeScript SDK Guide ###### tags: `FuelJourney` ## Overview If you know JavaScript, you can effectively learn how to build fullstack dapps (decentralized applications) on Fuel with Sway. In this guide, you will learn how to build a full-stack dapp on Fuel from scratch. Let's take a look at what you'll be building! ## Introduction In the Sway guide, you learned how to write a smart contract for the sway-farm game. Now, you need a frontend for your game that can interact with your contract. Below is a sneak-peak of what the end result should look like! **[insert screenshot of the game] ## Trying the reference app To try running the game locally, follow the following steps below: 1. Clone the [sway-farm]() repository and move into the root folder: ```bash git clone https://github.com/FuelLabs/sway-farm cd sway-farm ``` 2. Move into the frontend folder to install dependencies and run the app locally: ```bash cd frontend npm install npm start ``` Awesome! You can now test out the app and play around with it locally to understand what you be learning to implement and how it should look like in the end. Now, let's go ahead and get started with building the app yourself! ## Prerequistes This guide assumes that you have the following tools setup: - [Fuel toolchain](https://github.com/FuelLabs/fuelup) - [Sway VSCode extension](https://marketplace.visualstudio.com/items?itemName=FuelLabs.sway-vscode-plugin) Additionally, this guide assumes that you have an existing Sway smart contract that you can build a frontend for. Head over to the [Sway Guide]() to write your first Sway smart contract! Additional resources that may be helpful: - [Fuel 101 Guide]() - [Fuel Discourse](https://forum.fuel.network/) ## Getting Started - Initialising a React app using `create-react-app` - Installing `fuels-ts` SDK dependencies - Installing Fuel wallet ```bash npm install fuels @fuel-wallet/sdk ``` ## TS SDK Basics ### WalletLocked **(https://fuellabs.github.io/fuels-ts/packages/fuel-ts-wallet/classes/WalletLocked.html)** ### BN **(https://fuellabs.github.io/fuels-ts/packages/fuel-ts-providers/classes/internal-BN.html)** ## Fuel Wallet SDK Basics **(https://wallet.fuel.network/docs/how-to-use/)** ### Fuel If you've correctly installed the Fuel wallet extension, the wallet SDK will be injected automatically on the window object on the property fuel. To access it, you can use window.fuel ```javascript window.fuel.connect(); ``` ### Request connection First, you need to request a connection with the wallet, which will authorize your application to execute other actions. You can do this by accessing fuel.connect(). ```javascript const isConnected = await fuel.connect(); console.debug("Connection response", isConnected); ``` The` connect()` method returns a promise. If you prefer to do it in an async way, you can use `fuel.on('connection', () => void)` to listen for changes in the connection. ### Get current account You can also get the current account being used in the wallet using window.fuel.currentAccount(). ```javascript const currentAccount = await fuel.currentAccount(); console.debug("Current Account ", currentAccount); ``` ## Building your frontend ### Initialising your React app 1. Create a new folder called `frontend`. 2. Initialise the React app using the following command: `npx create-react-app frontend --template typescript` 3. Generate contract types using the following command: `npx fuelchain --target=fuels --out-dir=./src/contracts ../sway-farm/out/debug/*-abi.json` You should be able to find a new folder `sway-farm/frontend/src/contracts`. This folder was auto-generated by our `fuelchain` command, these files abstract the work we would need to do to create a contract instance and generate a complete TypeScript interface to the contract making easy to develop. ### Writing wallet hooks 1. `useFuel` ```javascript= import { useState, useEffect } from 'react'; import { Fuel } from '@fuel-wallet/sdk'; const globalWindow: Window & { fuel: Fuel; } = typeof window !== 'undefined' ? window as any : ({} as any); export function useFuel() { const [error, setError] = useState(''); const [isLoading, setLoading] = useState(true); const [fuel, setFuel] = useState<Fuel>( globalWindow.fuel ); useEffect(() => { const timeout = setTimeout(() => { if (globalWindow.fuel) { setFuel(globalWindow.fuel); } else { setError('Fuel Wallet not detected on the window!'); } setLoading(false); }, 500); return () => clearTimeout(timeout); }, []); return [fuel, error, isLoading] as const; } ``` 2. `useIsConnected` ```javascript= import { useEffect, useState } from 'react'; import { useFuel } from './useFuel'; export function useIsConnected() { const [fuel] = useFuel(); const [isConnected, setIsConnected] = useState(false); useEffect(() => { async function main() { try { const accounts = await fuel.accounts(); setIsConnected(Boolean(accounts.length)); } catch (err) { setIsConnected(false); } } if (fuel) { main(); } fuel?.on('connection', main); return () => { fuel?.off('connection', main); }; }, [fuel]); return isConnected; } ``` ### Primary Game Functions The player performs several actions throughout the game such as: - **Creating a new player** - **Buying seeds** - **Planting seeds to grow new plants** - **Harvesting plants** - **Selling harvested items** - **Leveling-up** Let's go ahead and implement some functions to let the player perform the above primary functions in the game. #### Creating New Player To beging the game session, let's first write a function that creates a new player. ```javascript interface NewPlayerProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function NewPlayer({ contract, setUpdateNum, updateNum }: NewPlayerProps){ const [status, setStatus] = useState<'error' | 'loading' | 'none'>('none'); async function handleNewPlayer(){ if (contract !== null) { try { setStatus('loading') await contract.functions.new_player() .txParams({ variableOutputs: 1 }) .call(); setStatus('none') setUpdateNum(updateNum + 1) } catch(err){ console.log("Error:", err) setStatus('error') } } else { console.log("ERROR: contract missing"); setStatus('error') } } ``` #### Buying seeds Once the game has started, a player needs to buy seeds. Let's write a function in `./Components/BuySeeds.tsx` to enable this functionality. ```javascript interface BuySeedsProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } // const CONTRACT_ID = "0xcae2ec3ca9f6fc2a6c604f1c5cec7a8052e9379dc066acc651ea56515ddeca6e" export default function BuySeeds({ contract, setUpdateNum, updateNum }: BuySeedsProps) { const [amount, setAmount] = useState<string>("0"); const [status, setStatus] = useState<'error' | 'none' | `loading`>('none'); // let price = bn.parseUnits('0.00000075'); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); if (contract !== null) { try { setStatus('loading') let realAmount = parseInt(amount) / 1_000_000_000; let inputAmount = bn.parseUnits(realAmount.toFixed(9).toString()); let seedType: FoodTypeInput = { tomatoes: [] }; await contract.functions // .buy_seeds(seedType, inputAmount) .buy_seeds_free(seedType, inputAmount) // .callParams({ // forward: [price, CONTRACT_ID], // }) .call(); setUpdateNum(updateNum + 1) setStatus('none') } catch (err) { console.log("Error:", err) setStatus('error') } } else { console.log("ERROR: contract missing"); setStatus('error') } } ``` Note that the `BuySeeds` functions makes use of the `buy_seeds` from your smart contract which is a payable function. This means that in order to call the function, a wallet must also send some tokens along with the call. #### Planting seeds Now that the player has bought seeds using their wallet, it is time to plant them! Let's write a function in `./Components/PlantSeeds.tsx` to plant the seeds. ```javascript interface PlantSeedsProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function PlantSeeds({ contract, setUpdateNum, updateNum }: PlantSeedsProps) { const [amount, setAmount] = useState<string>("0"); const [status, setStatus] = useState<'error' | 'none' | 'loading'>('none'); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); if (contract !== null) { try { setStatus('loading') let realAmount = parseInt(amount) / 1_000_000_000; let inputAmount = bn.parseUnits(realAmount.toFixed(9).toString()); console.log("inputAmount:", inputAmount); let seedType: FoodTypeInput = { tomatoes: [] }; await contract.functions.plant_seeds(seedType, inputAmount).call(); setUpdateNum(updateNum + 1); setStatus('none'); } catch (err) { console.log("Error!!", err); setStatus('error'); } } else { console.log("ERROR: contract missing"); setStatus('error') } } ``` #### Harvesting Items Awesome! The player can now harvest their plants once they're ready. Let's write a function in `./Components/Harvest.tsx` to collect the grown plants. ```javascript interface HarvestProps { contract: ContractAbi | null; index: number; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function Harvest({ contract, index, setUpdateNum, updateNum }: HarvestProps) { const [status, setStatus] = useState<'error' | 'none' | 'loading'>('none'); async function harvestItem() { if (contract !== null) { try { setStatus('loading') await contract.functions.harvest(index.toString()).call() setUpdateNum(updateNum + 1) setStatus('none') } catch (err) { console.log("Error:", err) setStatus('error') } } else { console.log("ERROR: contract missing"); setStatus('error') } } ``` #### Selling Items After harvesting the plants, the player can sell them as items. Let's write a function in `./Components/SellItem.tsx` that is called when the player wishes to sell harvested items. ```javascript interface SellItemProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function SellItem({ contract, setUpdateNum, updateNum }: SellItemProps) { const [amount, setAmount] = useState<string>("0"); const [status, setStatus] = useState<'error' | 'none' | 'loading'>('none'); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); if (contract !== null) { try { setStatus('loading') let realAmount = parseInt(amount) / 1_000_000_000; let inputAmount = bn.parseUnits(realAmount.toFixed(9).toString()); let seedType: FoodTypeInput = { tomatoes: [] }; await contract.functions.sell_item(seedType, inputAmount).call() setUpdateNum(updateNum + 1); setStatus('none') } catch (err) { console.log("Error:", err) setStatus('error') } } else { console.log("ERROR: contract missing"); setStatus('error') } } ``` Note that the `SellItem` function makes use of the `sell_item` function from your Sway contract. Just like `buy_seeds`, `sell_item` is also a payable function that transfers coins to the seller when called. #### Levelling Up Now that the player has bought seeds, planted them, harvested the plants, and sold items from their harvest, you need to write a function to level-up the player. The function `levelUp` checks whether a player has enough experience or not to proceed to the next level based on the response from `getCanLevelUp`. If the player is eligible, the async function `handleLevelUp` should increment the player's level. ```javascript interface LevelUpProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function LevelUp({ contract, setUpdateNum, updateNum }: LevelUpProps) { const [status, setStatus] = useState<'error' | 'loading' | 'none'>('none'); const [canLevelUp, setCanLevelUp] = useState<boolean>(); useEffect(() => { async function getCanLevelUp() { if (contract && contract.wallet) { try { let address: AddressInput = { value: contract.wallet.address.toB256() } let id: IdentityInput = { Address: address }; let { value } = await contract.functions.can_level_up(id).get(); setCanLevelUp(value) } catch (err) { console.log("Error:", err) } } } getCanLevelUp(); }, [contract, updateNum]) async function handleLevelUp() { if (contract !== null) { try { setStatus('loading') await contract.functions.level_up().call(); setUpdateNum(updateNum + 1); setStatus('none') } catch (err) { console.log("Error:", err) setStatus('error') } } else { console.log("ERROR: contract missing"); setStatus('error') } } ``` ### Other functions to display game results Apart from the primary functions of the game, you also need to implement functions to display game results such as: - Amount of seeds bought by the player - Amount of seeds planted by the player - Number of items harvested by the player - Amount of coins owned by the player Let's go ahead and write the functions to be able to display the above properties in the game. #### ShowSeeds Once a player buys some seeds, they should be able to check the amount of seeds they have available to plant. The ShowSeeds function should return the number of seeds bought by the player. ```javascript interface ShowSeedsProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function ShowSeeds({ contract, setUpdateNum, updateNum }: ShowSeedsProps) { const [seeds, setSeeds] = useState<number>(0); useEffect(() => { async function getSeeds() { if (contract && contract.wallet) { try { let address: AddressInput = { value: contract.wallet.address.toB256() } let id: IdentityInput = { Address: address }; let seedType: FoodTypeInput = { tomatoes: [] }; let { value } = await contract.functions.get_seed_amount(id, seedType).get(); let num = parseFloat(value.format()) * 1_000_000_000 setSeeds(num) } catch (err) { console.log("Error:", err) } } } getSeeds(); }, [contract, updateNum]) ``` #### ShowPlantedSeeds Now you need a function that lets the player check the amount of seeds planted. Let's go ahead and implement this functionality using the `ShowPlantedSeeds` function. ```javascript interface ShowItemsProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function ShowItems({ contract, setUpdateNum, updateNum }: ShowItemsProps) { const [seeds, setSeeds] = useState<number>(0); useEffect(() => { async function getItems() { if (contract && contract.wallet) { try { let address: AddressInput = { value: contract.wallet.address.toB256() } let id: IdentityInput = { Address: address }; let seedType: FoodTypeInput = { tomatoes: [] }; let { value } = await contract.functions.get_item_amount(id, seedType).get(); let num = parseFloat(value.format()) * 1_000_000_000 setSeeds(num) } catch (err) { console.log("Error:", err) } } } getItems(); }, [contract, updateNum]) ``` #### ShowItems To be able to sell the items on the market, the user should be able to check the inventory at all times. The inventory should show the currently available items that the player can sell. Let's write a function that returns the remaining items in the player's inventory! ```javascript interface ShowItemsProps { contract: ContractAbi | null; setUpdateNum: Dispatch<SetStateAction<number>>; updateNum: number; } export default function ShowItems({ contract, setUpdateNum, updateNum }: ShowItemsProps) { const [seeds, setSeeds] = useState<number>(0); useEffect(() => { async function getItems() { if (contract && contract.wallet) { try { let address: AddressInput = { value: contract.wallet.address.toB256() } let id: IdentityInput = { Address: address }; let seedType: FoodTypeInput = { tomatoes: [] }; let { value } = await contract.functions.get_item_amount(id, seedType).get(); let num = parseFloat(value.format()) * 1_000_000_000 setSeeds(num) } catch (err) { console.log("Error:", err) } } } getItems(); }, [contract, updateNum]) ``` #### ShowCoins The player should be able to check their wallet balance at a certain point in the game. To enable this, let's write a function that checks and displays the amount of coins in the player's wallet. ```javascript interface ShowCoinsProps { updateNum: number; } const CONTRACT_ID = "0xcae2ec3ca9f6fc2a6c604f1c5cec7a8052e9379dc066acc651ea56515ddeca6e" export default function ShowCoins({ updateNum }: ShowCoinsProps){ const [balance, setBalance] = useState<BN>(); const [Fuel] = useFuel(); useEffect(() => { async function getAccounts() { const currentAccount = await Fuel.currentAccount(); const wallet = await Fuel.getWallet(currentAccount) const walletBalance = await wallet.getBalance(CONTRACT_ID); setBalance(walletBalance); } if (Fuel) getAccounts(); }, [Fuel, updateNum]); ``` The `ShowCoins` functions makes use of the `fuel.currentAccount()`, `fuel.getWallet()`, and `fuel.getBalance()` methods from the `Fuel-wallet/sdk` to retrieve the balance of the player's current wallet account. ### Game Components Now that you have written all your functions, refer the example game repository to understand what your Sway Farm game components should look like in the end. **(https://github.com/sarahschwartz/sway-farm/tree/main/frontend/src/components)** ### Modifying main `App.tsx` file 1. Import hooks and components ```javascript import { useState, useEffect, useMemo } from "react"; import { useIsConnected } from "./hooks/useIsConnected"; import { useFuel } from "./hooks/useFuel"; import { WalletLocked } from "fuels"; import { ContractAbi__factory } from "./contracts" import Game from "./components/Game"; import { Heading, Button, Link } from "@fuel-ui/react"; ``` 2. Add the contract id of the Sway smart contract you have deployed in your `App.tsx` file ```javascript const CONTRACT_ID = "0xd715df667608312fe9f2e57f8e92d2dd0f5e23db94d8a27f63c3c5c74c01da77" ``` 3. In the `App` function, define state variables for `accounts`, `provider`, and `active` as follows: ```javascript function App() { const [account, setAccount] = useState<string>(); const [wallet, setWallet] = useState<WalletLocked>(); ``` 4. Define `fuel` and `isConnected` using the wallet hooks you wrote earlier: ```javascript const isConnected = useIsConnected(); const [Fuel] = useFuel(); ``` 5. Get `accounts` ```javascript useEffect(() => { async function getAccounts() { const currentAccount = await Fuel.currentAccount(); const tempWallet = await Fuel.getWallet(currentAccount) setAccount(currentAccount); setWallet(tempWallet) } if (Fuel) getAccounts(); }, [Fuel, isConnected]); ``` 6. Connect contract instance to the deployed contract address using the given wallet as described below: ```javascript const contract = useMemo(() => { if (Fuel && account && wallet) { // Connects out Contract instance to the deployed contract // address using the given wallet. const contract = ContractAbi__factory.connect(CONTRACT_ID, wallet); return contract; } return null; }, [Fuel, account, wallet]); ``` 8. Add a `header` for your app as follows: ```javascript return ( <div className="App"> <header> <Heading as= "h1" fontColor="gray5">Sway Farm</Heading> </header> ``` 9. Add a button as follows: ```javascript {Fuel ? ( <div> {isConnected ? ( <Game contract={contract} /> ) : ( <div> <Button onPress={() => Fuel.connect()}>Connect Wallet</Button> </div> )} </div> ) } ``` After making the final changes, here's how your `App.tsx` file should look: ```javascript= import { useState, useEffect, useMemo } from "react"; import { useIsConnected } from "./hooks/useIsConnected"; import { useFuel } from "./hooks/useFuel"; import { WalletLocked } from "fuels"; import { ContractAbi__factory } from "./contracts" import Game from "./components/Game"; import { Heading, Button, Link } from "@fuel-ui/react"; import "./App.css"; const CONTRACT_ID = "0xcae2ec3ca9f6fc2a6c604f1c5cec7a8052e9379dc066acc651ea56515ddeca6e" function App() { const [account, setAccount] = useState<string>(); const [wallet, setWallet] = useState<WalletLocked>(); const isConnected = useIsConnected(); const [Fuel] = useFuel(); useEffect(() => { async function getAccounts() { const currentAccount = await Fuel.currentAccount(); const tempWallet = await Fuel.getWallet(currentAccount) setAccount(currentAccount); setWallet(tempWallet) } if (Fuel) getAccounts(); }, [Fuel, isConnected]); const contract = useMemo(() => { if (Fuel && account && wallet) { // Connects out Contract instance to the deployed contract // address using the given wallet. const contract = ContractAbi__factory.connect(CONTRACT_ID, wallet); return contract; } return null; }, [Fuel, account, wallet]); return ( <div className="App"> <header> <Heading as= "h1" fontColor="gray5">Sway Farm</Heading> </header> {Fuel ? ( <div> {isConnected ? ( <Game contract={contract} /> ) : ( <div> <Button onPress={() => Fuel.connect()}>Connect Wallet</Button> </div> )} </div> ) : ( <div> Download the{" "} <Link target="_blank" rel="noopener noreferrer" href="https://wallet.fuel.network/" > Fuel Wallet </Link>{" "} to play the game. </div> )} </div> ); } export default App; ``` ## Running your app Voila! Your Fuel full-stack dapp is ready to play around with. Run the following command to spin up a development build. ```bash npm install npm start ```