@onflow/fcl
A high level abstraction (built on top of @onflow/sdk) that enables development of browser based dApps.
Status
- Last Updated: May 7th 2020
- Stable: Yes
- Risk of Breaking Change: Low
The things that exists probably won't be chainging much externally, we will be adding new functionality in the near future.
Install
npm install --save @onflow/fcl
Overview
Having trouble with something? Reach out to us on Discord, we are more than happy to help.
Todo List
Quick Start
From zero to users interacting with the Flow blockchain using a dapp you made.
NOTE This quick start will NOT go into the deployment of contracts. We will supply a more comprehensive tutorial covering this soon, so stay tuned.
- Last Updated: May 7th 2020
Brief
We are going to:
- Create a small React app that highlights some core FCL concepts
- Install and start the emulator
flow
- Install and start the FCL Dev Wallet (
fcl-wallet
)
The React App will be representative of the dapp you want to make. Durring development we will be using the fcl-wallet
to act as a custodial wallet that we can authenticate with as well as authorize transactions, the emulator flow
will act as an Access Node. Access Nodes are our gateway to the blockchain.
With these three things we can create a dapp in development, and then once there is a Flow Mainnet/Testnet, all we need to do is change some configuration values and our dapp should work there too.
Creating the React App
In this example we will be using React and Create React App. But there really isn't anything stopping you from using any other framwork you choose.
To use Create React App to create a React App we need to run:
npx create-react-app my-dapp
This will take a little bit of time but afterwords we will have a bootstrapped directory called my-dapp
which will have a ready-to-go React App. Once done, lets change into that directory and install some dependencies.
cd my-dapp
npm install --save @onflow/fcl styled-components
npm install --save-dev @onflow/dev-wallet
The above installed two run time dependencies for us (@onflow/fcl
and styled-components
), as well as the development dependency @onflow/dev-wallet
@onflow/fcl
will be the library we use to interact with the Flow blockchain.styled-components
will let us add a little@onflow/dev-wallet
will act as our custodial wallet that can authenticate and authorize transaction.
Installing and Starting the Emulator
The Flow emulator is a lightweight tool that emulates the behaviour of the real Flow network that we can use during development.
We cannot run the emulator if we do not have the emulator. The emulator comes bundled with the Flow CLI
you can install that following these instructions.
After Flow CLI
has been installed, lets get the emulator up and running.
flow emulator start --init
Running the above should have started the emulator, as well as created a flow.json
file that configures it. In the future, you will not need to use the --init
flag if you start the emulator from a directory that already has the flow.json
file.
At this point in time, you should the emulator running and a flow.json
file that looks something like this:
{
"accounts": {
"root": {
"address": "0000000000000000000000000000000000000001",
"privateKey": "e19081c8964b8dcf3902cc71e37d1f07f86fb357d79dd2fb57006419e0f95e95",
"sigAlgorithm": "ECDSA_P256",
"hashAlgorithm": "SHA3_256"
}
}
}
You will need the value for accounts.root.privateKey
in the flow.json
shortly when we go to start up the dev-wallet, which is our next step.
Starting up the dev-wallet
{
"scripts": {
"dev:wallet": "PK=e19081c8964b8dcf3902cc71e37d1f07f86fb357d79dd2fb57006419e0f95e95 fcl-wallet"
}
}
npm run dev:wallet
@onflow/dev-wallet@0.0.4
*** *** *** *** *** *** ***
š FCL Dev Wallet has started:
* Origin: http://localhost:8701
* FCL Authn: http://localhost:8701/flow/authenticate
* GraphiQL: http://localhost:8701/graphql
* Access Node: http://localhost:8080
* Root Address: 01
* Private Key: e19081c8964b8dcf3902cc71e37d1f07f86fb357d79dd2fb57006419e0f95e95
Include this code in development to configure fcl:
import * as fcl from "@onflow/fcl"
fcl.config()
.put("challenge.handshake", "http://localhost:8701/flow/authenticate")
*** *** *** *** *** *** ***
Start Up our Dapp
npm start
Configure FCL to use the dev wallet
// src/App.js
import React from "react";
import logo from "./logo.svg";
import "./App.css";
+ import * as fcl from "@onflow/fcl";
+
+ fcl.config()
+ .put("challenge.handshake", "http://localhost:8701/flow/authenticate")
+
function App() {
// ...
}
Authentication
import React, {useState, useEffect} from "react"
import styled from "styled-components"
import * as fcl from "@onflow/fcl"
const Root = styled.div``
const Img = styled.img`
width: 35px;
height: 35px;
`
const Button = styled.button``
const SignIn = () => {
const [user, setUser] = useState(null)
useEffect(() => fcl.currentUser().subscribe(setUser), [])
if (user == null) return null
if (user.loggedIn) return null
return <Button onClick={fcl.authenticate}>Sign In/Up</Button>
}
const Profile = () => {
const [user, setUser] = useState(null)
useEffect(() => fcl.currentUser().subscribe(setUser), [])
if (user == null) return null
if (!user.loggedIn) return null
return (
<>
{user.identity.avater && <Img src={user.identity.avatar} />}
<Name>{user.identity.name || "Anonymous"}</Name>
<Button onClick={fcl.unauthenticate}>Sign Out</button>
</>
)
}
export default function CurrentUser() {
return (
<Root>
<SignIn />
<Profile />
</Root>
)
}
// src/App.js
// ...
+
+ import CurrentUser from "./CurrentUser"
+
fcl.config()
.put("challenge.handshake", "http://localhost:8701/flow/authenticate")
function App() {
return (
<div className="App">
+ <CurrentUser/>
<header className="App-header">
{/*...*/}
</header>
</div>
)
}
Our First Script
import React, {useState} from "react"
import styled from "styled-components"
import * as fcl from "@onflow/fcl"
const Root = styled.div``
const Header = styled.div``
const Button = styled.button``
const Results = styled.pre``
export default function ScriptOne() {
const [data, setData] = useState(null)
const runScript = async e => {
e.preventDefault()
const response = await fcl.send([
fcl.script`
pub fun main(): Int {
return 7 + 4
}
`,
])
setData(await fcl.decode(response))
}
return (
<Root>
<Header>Script One</Header>
<Button>Run Script</Button>
{data && <Results>{JSON.stringify(data, null, 2)}</Results>}
</Root>
)
}
// src/App.js
// ...
import CurrentUser from "./CurrentUser"
+ import ScriptOne from "./ScriptOne"
// ...
function App() {
return (
<div className="App">
<CurrentUser/>
+ <ScriptOne/>
<header className="App-header">
{/*...*/}
</header>
</div>
)
}
A more complex Script
export function Woot({x, y}) {
this.x = x
this.y = y
}
// App.js
+ import Woot from "./woot"
// ...
fcl.config()
.put("challenge.handshake", "http://localhost:8701/flow/authenticate")
+ .put("decoder.Woot", woot => new Woot(woot))
// ...
import React, {useState} from "react"
import styled from "styled-components"
import * as fcl from "@onflow/fcl"
const Root = styled.div``
const Header = styled.div``
const Button = styled.button``
const Results = styled.pre``
export default function ScriptTwo() {
const [data, setData] = useState(null)
const runScript = async e => {
e.preventDefault()
const response = await fcl.send([
fcl.script`
pub struct Woot {
pub var x: Int
pub var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
pub fun main(): [Woot] {
return [Woot(x: 1, y: 2), Woot(x: 3, y: 4)]
}
`,
])
setData(await fcl.decode(response))
}
return (
<Root>
<Header>Script Two</Header>
<Button>Run Script</Button>
{data && <Results>{JSON.stringify(data, null, 2)}</Results>}
</Root>
)
}
// src/App.js
// ...
import CurrentUser from "./CurrentUser"
import ScriptOne from "./ScriptOne"
import ScriptTwo from "./ScriptTwo"
// ...
function App() {
return (
<div className="App">
<CurrentUser/>
<ScriptOne/>
+ <ScriptTwo/>
<header className="App-header">
{/*...*/}
</header>
</div>
)
}
Transaction Time
import React, {useState, useEffect} from "react"
import styled from "styled-components"
import * as fcl from "@onflow/fcl"
const Root = styled.div``
const Button = styled.button``
const Status = styled.pre``
export default function Transaction() {
const [status, setStatus] = useState("Not Started")
const runTransaction = async e => {
e.preventDefault()
setState("Resolving...")
const response = await fcl.send([
fcl.transaction`
transaction {
execute {
log("A transaction happened")
}
}
`,
fcl.proposer(fcl.currentUser().authorization),
fcl.payer(fcl.currentUser().authorization),
])
setState("Transaction Sent, Waiting for Confirmation")
const unsub = fcl.tx(response).subscribe(transaction => {
if (fcl.tx.isSealed(transaction)) {
setState("Transaction Confirmed: Is Sealed")
unsub()
}
})
}
return (
<Root>
<Button onClick={runTransaction}>Run Transaction</Button>
<Status>{status}</Status>
</Root>
)
}
// src/App.js
// ...
import CurrentUser from "./CurrentUser"
import ScriptOne from "./ScriptOne"
import ScriptTwo from "./ScriptTwo"
+ import Transaction from "./Transaction"
// ...
function App() {
return (
<div className="App">
<CurrentUser/>
<ScriptOne/>
<ScriptTwo/>
+ <Transaction/>
<header className="App-header">
{/*...*/}
</header>
</div>
)
}