Simple Frontend
Flow Client Library (FCL) is a JavaScript library developed to facilitate interactions with the Flow blockchain. It provides developers with tools to build, integrate, and interact with Flow directly from web applications. This quickstart will guide you through interacting with a contract already deployed on Flow, reading and mutating its state, and setting up wallet authentication using FCL's Discovery UI.
For this tutorial, we're going to create a React app using Create React App. We'll keep the code as simple as possible, so even if you're coming from another framework, you can follow along.
Objectives
After completing this guide, you'll be able to:
- Display data from a Cadence smart contract on a React frontend using the Flow Client Library.
- Mutate the state of a smart contract by sending transactions using FCL and a wallet.
- Set up the Discovery UI to use a wallet for authentication.
Creating the App
First, let's create our app and navigate to it with the following terminal commands. From the root of where you keep your source code:
_10npx create-react-app fcl-app-quickstart_10cd fcl-app-quickstart
This command uses sets up a new React project named fcl-app-quickstart
. Then, we navigate into the project directory.
Open the new project in your editor.
The default layout includes some boilerplate code that we don't need. Let's simplify src/App.js
to start with a clean slate. Replace the contents of src/App.js
with:
_13// src/App.js_13_13import './App.css';_13_13function App() {_13 return (_13 <div className="App">_13 <div>FCL App Quickstart</div>_13 </div>_13 );_13}_13_13export default App;
This code defines a simple App
component that displays the text "FCL App Quickstart".
Now, let's run our app with the following npm
command:
_10npm start
This will start the development server and open your app in the browser. You should see a page displaying FCL App Quickstart
.
Setting Up FCL
To interact with the Flow blockchain, we need to install the Flow Client Library (FCL). Stop the development server by pressing Ctrl+C
in the terminal, and then run the following command to install FCL:
_10npm install @onflow/fcl --save
This command installs FCL and adds it to your project's dependencies.
Next, we'll configure FCL to connect to the Flow Testnet. An Access Node serves as the primary point of interaction for clients to communicate with the Flow network. It provides a gateway for submitting transactions, querying data, and retrieving information.
In src/App.js
, import FCL and add the configuration code:
_10// src/App.js_10_10import * as fcl from '@onflow/fcl';_10_10fcl.config({_10 'accessNode.api': 'https://rest-testnet.onflow.org',_10});
This configuration sets the access node endpoint to the Flow Testnet.
Your src/App.js
should now look like this:
_16import './App.css';_16import * as fcl from '@onflow/fcl';_16_16fcl.config({_16 'accessNode.api': 'https://rest-testnet.onflow.org',_16});_16_16function App() {_16 return (_16 <div className="App">_16 <div>FCL App Quickstart</div>_16 </div>_16 );_16}_16_16export default App;
Querying the Chain
Now, let's read data from a smart contract deployed on the Flow Testnet. We'll use a HelloWorld
contract deployed to the account 0xa1296b1e2e90ca5b
(you can view the contract here to see what it looks like).
This contract has a public variable greeting
that we can read.
First, we'll set up state in our app to store the greeting and manage component updates. We'll use React's useState
and useEffect
hooks.
Update your imports in src/App.js
to include useState
and useEffect
:
_10import { useEffect, useState } from 'react';
Next, initialize the greeting
state variable inside your App
component:
_10const [greeting, setGreeting] = useState('');
Now, let's create a function to query the greeting from the blockchain:
_12const queryGreeting = async () => {_12 const res = await fcl.query({_12 cadence: `_12 import HelloWorld from 0xa1296b1e2e90ca5b_12_12 pub fun main(): String {_12 return HelloWorld.greeting_12 }_12 `,_12 });_12 setGreeting(res);_12};
- Explanation:
- We use
fcl.query
to send a script to the Flow blockchain. - The Cadence script imports the
HelloWorld
contract and defines amain
function that returns thegreeting
variable. - The result of the query is stored in
res
, and we update thegreeting
state withsetGreeting(res)
.
- We use
Next, use the useEffect
hook to call queryGreeting
when the component mounts:
_10useEffect(() => {_10 queryGreeting();_10}, []);
The empty array []
ensures that queryGreeting
is called only once when the component first renders.
Finally, update the return
statement to display the greeting:
_10return (_10 <div className="App">_10 <div>FCL App Quickstart</div>_10 <div>Greeting: {greeting}</div>_10 </div>_10);
At this point, your src/App.js
file should look like this:
_37import { useEffect, useState } from 'react';_37import './App.css';_37import * as fcl from '@onflow/fcl';_37_37fcl.config({_37 'accessNode.api': 'https://rest-testnet.onflow.org',_37});_37_37function App() {_37 const [greeting, setGreeting] = useState('');_37_37 const queryGreeting = async () => {_37 const res = await fcl.query({_37 cadence: `_37 import HelloWorld from 0xa1296b1e2e90ca5b_37_37 pub fun main(): String {_37 return HelloWorld.greeting_37 }_37 `,_37 });_37 setGreeting(res);_37 };_37_37 useEffect(() => {_37 queryGreeting();_37 }, []);_37_37 return (_37 <div className="App">_37 <div>FCL App Quickstart</div>_37 <div>Greeting: {greeting}</div>_37 </div>_37 );_37}_37_37export default App;
Now, run npm start
again. After a moment, the greeting from the HelloWorld
contract should appear on your page!
Mutating the Chain State
Now that we've successfully read data from the Flow blockchain, let's modify the state by changing the greeting
in the HelloWorld
contract. To do this, we'll need to send a transaction to the blockchain, which requires user authentication through a wallet.
Setting Up Wallet Authentication with Discovery
Before we can send a transaction, we need to set up wallet authentication. We'll use FCL's Discovery UI to allow users to connect their wallet with minimal setup.
Add the following line to your FCL configuration in src/App.js
:
_10fcl.config({_10 'accessNode.api': 'https://rest-testnet.onflow.org',_10 'discovery.wallet': 'https://fcl-discovery.onflow.org/testnet/authn',_10});
One-Line Discovery UI Setup:
- By adding the
'discovery.wallet'
line to the FCL configuration, we enable the Discovery UI. - This provides an interface for users to select and authenticate with a wallet.
- This is all it takes—just one line—to integrate wallet selection into your app.
Adding Authentication Buttons
Let's add a simple authentication flow to our app. We'll allow users to log in and log out, and display their account address when they're logged in.
First, add a new state variable to manage the user's authentication state:
_10const [user, setUser] = useState({ loggedIn: false });
Then, use useEffect
to subscribe to the current user's authentication state:
_10useEffect(() => {_10 fcl.currentUser.subscribe(setUser);_10 queryGreeting();_10}, []);
- Explanation:
fcl.currentUser.subscribe(setUser)
sets up a listener that updates theuser
state whenever the authentication state changes.- We also call
queryGreeting()
to fetch the greeting when the component mounts.
Next, define the logIn
and logOut
functions:
_10const logIn = () => {_10 fcl.authenticate();_10};_10_10const logOut = () => {_10 fcl.unauthenticate();_10};
- Explanation:
fcl.authenticate()
triggers the authentication process using the Discovery UI.fcl.unauthenticate()
logs the user out.
Now, update the return
statement to include authentication buttons and display the user's address when they're logged in:
_15return (_15 <div className="App">_15 <div>FCL App Quickstart</div>_15 <div>Greeting: {greeting}</div>_15 {user.loggedIn ? (_15 <div>_15 <p>Address: {user.addr}</p>_15 <button onClick={logOut}>Log Out</button>_15 {/* We'll add the transaction form here later */}_15 </div>_15 ) : (_15 <button onClick={logIn}>Log In</button>_15 )}_15 </div>_15);
Now, when the user clicks the "Log In" button, they'll be presented with the Discovery UI to select a wallet for authentication.
Sending a Transaction to Change the Greeting
Next, we'll add a form to allow the user to change the greeting by sending a transaction to the blockchain.
First, add a state variable to hold the new greeting:
_10const [newGreeting, setNewGreeting] = useState('');
Now, define the sendTransaction
function:
_31const sendTransaction = async () => {_31 try {_31 const transactionId = await fcl.mutate({_31 cadence: `_31 import HelloWorld from 0xa1296b1e2e90ca5b_31_31 transaction(newGreeting: String) {_31 prepare(acct: AuthAccount) {}_31 execute {_31 HelloWorld.changeGreeting(newGreeting: newGreeting)_31 }_31 }_31 `,_31 args: (arg, t) => [arg(newGreeting, t.String)],_31 proposer: fcl.currentUser,_31 payer: fcl.currentUser,_31 authorizations: [fcl.currentUser.authorization],_31 limit: 50,_31 });_31_31 console.log('Transaction Id', transactionId);_31_31 await fcl.tx(transactionId).onceSealed();_31 console.log('Transaction Sealed');_31_31 queryGreeting();_31 setNewGreeting('');_31 } catch (error) {_31 console.error('Transaction Failed', error);_31 }_31};
- Explanation:
fcl.mutate
is used to send a transaction to the Flow blockchain.- The Cadence transaction imports the
HelloWorld
contract and callschangeGreeting
with the new greeting. - We pass the
newGreeting
as an argument to the transaction. proposer
,payer
, andauthorizations
are set tofcl.currentUser
, meaning the authenticated user will sign and pay for the transaction.- We wait for the transaction to be sealed (completed and finalized on the blockchain) using
fcl.tx(transactionId).onceSealed()
. - After the transaction is sealed, we call
queryGreeting()
to fetch the updated greeting.
Next, update the return
statement to include the input field and button for changing the greeting:
_17{user.loggedIn ? (_17 <div>_17 <p>Address: {user.addr}</p>_17 <button onClick={logOut}>Log Out</button>_17 <div>_17 <input_17 type="text"_17 placeholder="Enter new greeting"_17 value={newGreeting}_17 onChange={(e) => setNewGreeting(e.target.value)}_17 />_17 <button onClick={sendTransaction}>Change Greeting</button>_17 </div>_17 </div>_17) : (_17 <button onClick={logIn}>Log In</button>_17)}
- Explanation:
- When the user is logged in, we display an input field for the new greeting and a button to submit it.
- The input field is controlled by the
newGreeting
state. - Clicking the "Change Greeting" button triggers the
sendTransaction
function.
Full Code
Your src/App.js
should now look like this:
_98import { useEffect, useState } from 'react';_98import './App.css';_98import * as fcl from '@onflow/fcl';_98_98fcl.config({_98 'accessNode.api': 'https://rest-testnet.onflow.org',_98 'discovery.wallet': 'https://fcl-discovery.onflow.org/testnet/authn',_98});_98_98function App() {_98 const [greeting, setGreeting] = useState('');_98 const [user, setUser] = useState({ loggedIn: false });_98 const [newGreeting, setNewGreeting] = useState('');_98_98 const queryGreeting = async () => {_98 const res = await fcl.query({_98 cadence: `_98 import HelloWorld from 0xa1296b1e2e90ca5b_98_98 pub fun main(): String {_98 return HelloWorld.greeting_98 }_98 `,_98 });_98 setGreeting(res);_98 };_98_98 useEffect(() => {_98 fcl.currentUser.subscribe(setUser);_98 queryGreeting();_98 }, []);_98_98 const logIn = () => {_98 fcl.authenticate();_98 };_98_98 const logOut = () => {_98 fcl.unauthenticate();_98 };_98_98 const sendTransaction = async () => {_98 try {_98 const transactionId = await fcl.mutate({_98 cadence: `_98 import HelloWorld from 0xa1296b1e2e90ca5b_98_98 transaction(newGreeting: String) {_98 prepare(acct: AuthAccount) {}_98 execute {_98 HelloWorld.changeGreeting(newGreeting: newGreeting)_98 }_98 }_98 `,_98 args: (arg, t) => [arg(newGreeting, t.String)],_98 proposer: fcl.currentUser,_98 payer: fcl.currentUser,_98 authorizations: [fcl.currentUser.authorization],_98 limit: 50,_98 });_98_98 console.log('Transaction Id', transactionId);_98_98 await fcl.tx(transactionId).onceSealed();_98 console.log('Transaction Sealed');_98_98 queryGreeting();_98 setNewGreeting('');_98 } catch (error) {_98 console.error('Transaction Failed', error);_98 }_98 };_98_98 return (_98 <div className="App">_98 <div>FCL App Quickstart</div>_98 <div>Greeting: {greeting}</div>_98 {user.loggedIn ? (_98 <div>_98 <p>Address: {user.addr}</p>_98 <button onClick={logOut}>Log Out</button>_98 <div>_98 <input_98 type="text"_98 placeholder="Enter new greeting"_98 value={newGreeting}_98 onChange={(e) => setNewGreeting(e.target.value)}_98 />_98 <button onClick={sendTransaction}>Change Greeting</button>_98 </div>_98 </div>_98 ) : (_98 <button onClick={logIn}>Log In</button>_98 )}_98 </div>_98 );_98}_98_98export default App;
Running the App
Now, run your app with npm start
and open it in your browser.
-
Log In:
- Click the "Log In" button.
- The Discovery UI will appear, presenting you with a list of wallets to authenticate with (e.g., Flow Wallet).
- Select a wallet and follow the prompts to log in.
-
Change Greeting:
- Once logged in, you'll see your account address displayed.
- Enter a new greeting in the input field.
- Click the "Change Greeting" button.
- Your wallet will prompt you to approve the transaction.
- After approving, the transaction will be sent to the Flow blockchain.
-
View Updated Greeting:
- After the transaction is sealed, the app will automatically fetch and display the updated greeting.
- You should see your new greeting displayed on the page.