Skip to main content

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:


_10
npx create-react-app fcl-app-quickstart
_10
cd 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
_13
import './App.css';
_13
_13
function App() {
_13
return (
_13
<div className="App">
_13
<div>FCL App Quickstart</div>
_13
</div>
_13
);
_13
}
_13
_13
export 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:


_10
npm start

This will start the development server and open your app in the browser. You will 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:


_10
npm 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
_10
import * as fcl from '@onflow/fcl';
_10
_10
fcl.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:


_16
import './App.css';
_16
import * as fcl from '@onflow/fcl';
_16
_16
fcl.config({
_16
'accessNode.api': 'https://rest-testnet.onflow.org',
_16
});
_16
_16
function App() {
_16
return (
_16
<div className="App">
_16
<div>FCL App Quickstart</div>
_16
</div>
_16
);
_16
}
_16
_16
export 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:


_10
import { useEffect, useState } from 'react';

Next, initialize the greeting state variable inside your App component:


_10
const [greeting, setGreeting] = useState('');

Now, let's create a function to query the greeting from the blockchain:


_13
const queryGreeting = async () => {
_13
const res = await fcl.query({
_13
cadence: `
_13
import HelloWorld from 0xa1296b1e2e90ca5b
_13
_13
access(all)
_13
fun main(): String {
_13
return HelloWorld.greeting
_13
}
_13
`,
_13
});
_13
setGreeting(res);
_13
};

  • Explanation:
    • We use fcl.query to send a script to the Flow blockchain.
    • The Cadence script imports the HelloWorld contract and defines a main function that returns the greeting variable.
    • The result of the query is stored in res, and we update the greeting state with setGreeting(res).

Next, use the useEffect hook to call queryGreeting when the component mounts:


_10
useEffect(() => {
_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:


_10
return (
_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:


_38
import { useEffect, useState } from 'react';
_38
import './App.css';
_38
import * as fcl from '@onflow/fcl';
_38
_38
fcl.config({
_38
'accessNode.api': 'https://rest-testnet.onflow.org',
_38
});
_38
_38
function App() {
_38
const [greeting, setGreeting] = useState('');
_38
_38
const queryGreeting = async () => {
_38
const res = await fcl.query({
_38
cadence: `
_38
import HelloWorld from 0xa1296b1e2e90ca5b
_38
_38
access(all)
_38
fun main(): String {
_38
return HelloWorld.greeting
_38
}
_38
`,
_38
});
_38
setGreeting(res);
_38
};
_38
_38
useEffect(() => {
_38
queryGreeting();
_38
}, []);
_38
_38
return (
_38
<div className="App">
_38
<div>FCL App Quickstart</div>
_38
<div>Greeting: {greeting}</div>
_38
</div>
_38
);
_38
}
_38
_38
export 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:


_10
fcl.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:


_10
const [user, setUser] = useState({ loggedIn: false });

Then, use useEffect to subscribe to the current user's authentication state:


_10
useEffect(() => {
_10
fcl.currentUser.subscribe(setUser);
_10
queryGreeting();
_10
}, []);

  • Explanation:
    • fcl.currentUser.subscribe(setUser) sets up a listener that updates the user 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:


_10
const logIn = () => {
_10
fcl.authenticate();
_10
};
_10
_10
const 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:


_15
return (
_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:


_10
const [newGreeting, setNewGreeting] = useState('');

Now, define the sendTransaction function:


_31
const 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 calls changeGreeting with the new greeting.
    • We pass the newGreeting as an argument to the transaction.
    • proposer, payer, and authorizations are set to fcl.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:


_99
import { useEffect, useState } from 'react';
_99
import './App.css';
_99
import * as fcl from '@onflow/fcl';
_99
_99
fcl.config({
_99
'accessNode.api': 'https://rest-testnet.onflow.org',
_99
'discovery.wallet': 'https://fcl-discovery.onflow.org/testnet/authn',
_99
});
_99
_99
function App() {
_99
const [greeting, setGreeting] = useState('');
_99
const [user, setUser] = useState({ loggedIn: false });
_99
const [newGreeting, setNewGreeting] = useState('');
_99
_99
const queryGreeting = async () => {
_99
const res = await fcl.query({
_99
cadence: `
_99
import HelloWorld from 0xa1296b1e2e90ca5b
_99
_99
access(all)
_99
fun main(): String {
_99
return HelloWorld.greeting
_99
}
_99
`,
_99
});
_99
setGreeting(res);
_99
};
_99
_99
useEffect(() => {
_99
fcl.currentUser.subscribe(setUser);
_99
queryGreeting();
_99
}, []);
_99
_99
const logIn = () => {
_99
fcl.authenticate();
_99
};
_99
_99
const logOut = () => {
_99
fcl.unauthenticate();
_99
};
_99
_99
const sendTransaction = async () => {
_99
try {
_99
const transactionId = await fcl.mutate({
_99
cadence: `
_99
import HelloWorld from 0xa1296b1e2e90ca5b
_99
_99
transaction(newGreeting: String) {
_99
prepare(acct: AuthAccount) {}
_99
execute {
_99
HelloWorld.changeGreeting(newGreeting: newGreeting)
_99
}
_99
}
_99
`,
_99
args: (arg, t) => [arg(newGreeting, t.String)],
_99
proposer: fcl.currentUser,
_99
payer: fcl.currentUser,
_99
authorizations: [fcl.currentUser.authorization],
_99
limit: 50,
_99
});
_99
_99
console.log('Transaction Id', transactionId);
_99
_99
await fcl.tx(transactionId).onceSealed();
_99
console.log('Transaction Sealed');
_99
_99
queryGreeting();
_99
setNewGreeting('');
_99
} catch (error) {
_99
console.error('Transaction Failed', error);
_99
}
_99
};
_99
_99
return (
_99
<div className="App">
_99
<div>FCL App Quickstart</div>
_99
<div>Greeting: {greeting}</div>
_99
{user.loggedIn ? (
_99
<div>
_99
<p>Address: {user.addr}</p>
_99
<button onClick={logOut}>Log Out</button>
_99
<div>
_99
<input
_99
type="text"
_99
placeholder="Enter new greeting"
_99
value={newGreeting}
_99
onChange={(e) => setNewGreeting(e.target.value)}
_99
/>
_99
<button onClick={sendTransaction}>Change Greeting</button>
_99
</div>
_99
</div>
_99
) : (
_99
<button onClick={logIn}>Log In</button>
_99
)}
_99
</div>
_99
);
_99
}
_99
_99
export 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.