├── .env.development
├── .env.production
├── .eslintrc.json
├── .gitignore
├── README.md
├── cadence
└── contracts
│ └── Profile.cdc
├── components
├── Landing.js
├── Profile.js
└── Transaction.js
├── contexts
├── AuthContext.js
└── TransactionContext.js
├── flow.json
├── flow
└── config.js
├── next.config.js
├── package.json
├── pages
├── _app.js
├── about.js
├── api
│ └── hello.js
└── index.js
├── public
├── favicon.ico
├── favicon.png
├── flow-logo.svg
└── vercel.svg
└── styles
├── Home.module.css
└── globals.css
/.env.development:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_ACCESS_NODE_API=http://localhost:8080
2 | NEXT_PUBLIC_DISCOVERY_WALLET=http://localhost:8701/fcl/authn
3 | NEXT_PUBLIC_CONTRACT_PROFILE=0xf8d6e0586b0a20c7
4 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_ACCESS_NODE_API=https://rest-testnet.onflow.org
2 | NEXT_PUBLIC_DISCOVERY_WALLET=https://fcl-discovery.onflow.org/testnet/authn
3 | NEXT_PUBLIC_CONTRACT_PROFILE=0xba1132bc08f82fe2
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "react/no-unknown-property": ["error", { "ignore": ["indeterminate"] }]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | package-lock.json
37 |
38 | .idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to use the Flow Client Library (FCL) with Next.js
2 |
3 | Everything you need to build a Next.js project with the Flow Client Library (FCL).
4 |
5 | For a SvelteKit example, see my other repo: https://github.com/muttoni/fcl-sveltekit
6 |
7 | ## [Live demo](https://fcl-nextjs-quickstart.vercel.app/)
8 |
9 | [](https://fcl-sveltekit.vercel.app/)
10 |
11 | ## Running on Flow Testnet
12 | This project will run on the Flow testnet simply as:
13 | ```bash
14 | npm run build
15 | npm run start
16 | ```
17 |
18 | ## Developing with Flow emulator
19 |
20 | **Pre-Requisite**: To develop locally, make sure you have the Flow CLI installed: https://docs.onflow.org/flow-cli/install/
21 |
22 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start the emulator, deploy the contracts, followed by the development server:
23 |
24 | ```bash
25 | flow emulator start --dev-wallet
26 | flow project deploy --network emulator
27 |
28 | npm run dev
29 | # or start the server and open the app in a new browser tab
30 | npm run dev -- --open
31 | ```
32 |
33 | > NOTE: If you are switching between testnet and the emulator without changing tabs, FCL will keep you logged in with your testnet address (or vice-versa). Remember to logout inbetween environments to avoid runtime errors!
34 |
35 | ## Building
36 |
37 | Before creating a production version of your app, build it!
38 |
39 | ```bash
40 | npm run build
41 | ```
42 |
43 | ## Testimonials
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cadence/contracts/Profile.cdc:
--------------------------------------------------------------------------------
1 | /** Generic Profile Contract
2 |
3 | License: MIT
4 |
5 | I am trying to figure out a generic re-usable Profile Micro-Contract
6 | that any application can consume and use. It should be easy to integrate
7 | this contract with any application, and as a user moves from application
8 | to application this profile can come with them. A core concept here is
9 | given a Flow Address, a profiles details can be publically known. This
10 | should mean that if an application were to use/store the Flow address of
11 | a user, than this profile could be visible, and maintained with out storing
12 | a copy in an applications own databases. I believe that anytime we can move
13 | a common database table into a publically accessible contract/resource is a
14 | win.
15 |
16 | could be a little more than that too. As Flow Accounts can now have
17 | multiple contracts, it could be fun to allow for these accounts to have
18 | some basic information too. https://flow-view-source.com is a side project
19 | of mine (qvvg) and if you are looking at an account on there, or a contract
20 | deployed to an account I will make it so it pulls info from a properly
21 | configured Profile Resource.
22 |
23 | ====================
24 | ## Table of Contents
25 | ====================
26 | Line
27 | Intro ......................................................... 1
28 | Table of Contents ............................................. 27
29 | General Profile Contract Info ................................. 41
30 | Examples ...................................................... 50
31 | Initializing a Profile Resource ............................. 59
32 | Interacting with Profile Resource (as Owner) ................ 112
33 | Reading a Profile Given a Flow Address ...................... 160
34 | Reading a Multiple Profiles Given Multiple Flow Addresses ... 192
35 | Checking if Flow Account is Initialized ..................... 225
36 |
37 |
38 | ================================
39 | ## General Profile Contract Info
40 | ================================
41 |
42 | Currently a profile consists of a couple main pieces:
43 | - name – An alias the profile owner would like to be refered as.
44 | - avatar - An href the profile owner would like applications to use to represent them graphically.
45 | - color - A valid html color (not verified in any way) applications can use to accent and personalize the experience.
46 | - info - A short description about the account.
47 |
48 | ===========
49 | ## Examples
50 | ===========
51 |
52 | The following examples will include both raw cadence transactions and scripts
53 | as well as how you can call them from FCL. The FCL examples are currently assuming
54 | the following configuration is called somewhere in your application before the
55 | the actual calls to the chain are invoked.
56 |
57 | ==================================
58 | ## Initializing a Profile Resource
59 | ==================================
60 |
61 | Initializing should be done using the paths that the contract exposes.
62 | This will lead to predictability in how applications can look up the data.
63 |
64 | -----------
65 | ### Cadence
66 | -----------
67 |
68 | import Profile from 0xba1132bc08f82fe2
69 |
70 | transaction {
71 | let address: address
72 | prepare(currentUser: AuthAccount) {
73 | self.address = currentUser.address
74 | if !Profile.check(self.address) {
75 | currentUser.save(<- Profile.new(), to: Profile.privatePath)
76 | currentUser.link<&Profile.Base{Profile.Public}>(Profile.publicPath, target: Profile.privatePath)
77 | }
78 | }
79 | post {
80 | Profile.check(self.address): "Account was not initialized"
81 | }
82 | }
83 |
84 | -------
85 | ### FCL
86 | -------
87 |
88 | import {query} from "@onflow/fcl"
89 |
90 | await mutate({
91 | cadence: `
92 | import Profile from 0xba1132bc08f82fe2
93 |
94 | transaction {
95 | prepare(currentUser: AuthAccount) {
96 | self.address = currentUser.address
97 | if !Profile.check(self.address) {
98 | currentUser.save(<- Profile.new(), to: Profile.privatePath)
99 | currentUser.link<&Profile.Base{Profile.Public}>(Profile.publicPath, target: Profile.privatePath)
100 | }
101 | }
102 | post {
103 | Profile.check(self.address): "Account was not initialized"
104 | }
105 | }
106 | `,
107 | limit: 55,
108 | })
109 |
110 | ===============================================
111 | ## Interacting with Profile Resource (as Owner)
112 | ===============================================
113 |
114 | As the owner of a resource you can update the following:
115 | - name using `.setName("MyNewName")` (as long as you arent verified)
116 | - avatar using `.setAvatar("https://url.to.my.avatar")`
117 | - color using `.setColor("tomato")`
118 | - info using `.setInfo("I like to make things with Flow :wave:")`
119 |
120 | -----------
121 | ### Cadence
122 | -----------
123 |
124 | import Profile from 0xba1132bc08f82fe2
125 |
126 | transaction(name: String) {
127 | prepare(currentUser: AuthAccount) {
128 | currentUser
129 | .borrow<&{Profile.Owner}>(from: Profile.privatePath)!
130 | .setName(name)
131 | }
132 | }
133 |
134 | -------
135 | ### FCL
136 | -------
137 |
138 | import {mutate} from "@onflow/fcl"
139 |
140 | await mutate({
141 | cadence: `
142 | import Profile from 0xba1132bc08f82fe2
143 |
144 | transaction(name: String) {
145 | prepare(currentUser: AuthAccount) {
146 | currentUser
147 | .borrow<&{Profile.Owner}>(from: Profile.privatePath)!
148 | .setName(name)
149 | }
150 | }
151 | `,
152 | args: (arg, t) => [
153 | arg("qvvg", t.String),
154 | ],
155 | limit: 55,
156 | })
157 |
158 | =========================================
159 | ## Reading a Profile Given a Flow Address
160 | =========================================
161 |
162 | -----------
163 | ### Cadence
164 | -----------
165 |
166 | import Profile from 0xba1132bc08f82fe2
167 |
168 | pub fun main(address: Address): Profile.ReadOnly? {
169 | return Profile.read(address)
170 | }
171 |
172 | -------
173 | ### FCL
174 | -------
175 |
176 | import {query} from "@onflow/fcl"
177 |
178 | await query({
179 | cadence: `
180 | import Profile from 0xba1132bc08f82fe2
181 |
182 | pub fun main(address: Address): Profile.ReadOnly? {
183 | return Profile.read(address)
184 | }
185 | `,
186 | args: (arg, t) => [
187 | arg("0xba1132bc08f82fe2", t.Address)
188 | ]
189 | })
190 |
191 | ============================================================
192 | ## Reading a Multiple Profiles Given Multiple Flow Addresses
193 | ============================================================
194 |
195 | -----------
196 | ### Cadence
197 | -----------
198 |
199 | import Profile from 0xba1132bc08f82fe2
200 |
201 | pub fun main(addresses: [Address]): {Address: Profile.ReadOnly} {
202 | return Profile.readMultiple(addresses)
203 | }
204 |
205 | -------
206 | ### FCL
207 | -------
208 |
209 | import {query} from "@onflow/fcl"
210 |
211 | await query({
212 | cadence: `
213 | import Profile from 0xba1132bc08f82fe2
214 |
215 | pub fun main(addresses: [Address]): {Address: Profile.ReadOnly} {
216 | return Profile.readMultiple(addresses)
217 | }
218 | `,
219 | args: (arg, t) => [
220 | arg(["0xba1132bc08f82fe2", "0xf76a4c54f0f75ce4", "0xf117a8efa34ffd58"], t.Array(t.Address)),
221 | ]
222 | })
223 |
224 | ==========================================
225 | ## Checking if Flow Account is Initialized
226 | ==========================================
227 |
228 | -----------
229 | ### Cadence
230 | -----------
231 |
232 | import Profile from 0xba1132bc08f82fe2
233 |
234 | pub fun main(address: Address): Bool {
235 | return Profile.check(address)
236 | }
237 |
238 | -------
239 | ### FCL
240 | -------
241 |
242 | import {query} from "@onflow/fcl"
243 |
244 | await query({
245 | cadence: `
246 | import Profile from 0xba1132bc08f82fe2
247 |
248 | pub fun main(address: Address): Bool {
249 | return Profile.check(address)
250 | }
251 | `,
252 | args: (arg, t) => [
253 | arg("0xba1132bc08f82fe2", t.Address)
254 | ]
255 | })
256 |
257 | */
258 | pub contract Profile {
259 | pub let publicPath: PublicPath
260 | pub let privatePath: StoragePath
261 |
262 | pub resource interface Public {
263 | pub fun getName(): String
264 | pub fun getAvatar(): String
265 | pub fun getColor(): String
266 | pub fun getInfo(): String
267 | pub fun asReadOnly(): Profile.ReadOnly
268 | }
269 |
270 | pub resource interface Owner {
271 | pub fun getName(): String
272 | pub fun getAvatar(): String
273 | pub fun getColor(): String
274 | pub fun getInfo(): String
275 |
276 | pub fun setName(_ name: String) {
277 | pre {
278 | name.length <= 15: "Names must be under 15 characters long."
279 | }
280 | }
281 | pub fun setAvatar(_ src: String)
282 | pub fun setColor(_ color: String)
283 | pub fun setInfo(_ info: String) {
284 | pre {
285 | info.length <= 280: "Profile Info can at max be 280 characters long."
286 | }
287 | }
288 | }
289 |
290 | pub resource Base: Owner, Public {
291 | access(self) var name: String
292 | access(self) var avatar: String
293 | access(self) var color: String
294 | access(self) var info: String
295 |
296 | init() {
297 | self.name = "Anon"
298 | self.avatar = ""
299 | self.color = "#232323"
300 | self.info = ""
301 | }
302 |
303 | pub fun getName(): String { return self.name }
304 | pub fun getAvatar(): String { return self.avatar }
305 | pub fun getColor(): String {return self.color }
306 | pub fun getInfo(): String { return self.info }
307 |
308 | pub fun setName(_ name: String) { self.name = name }
309 | pub fun setAvatar(_ src: String) { self.avatar = src }
310 | pub fun setColor(_ color: String) { self.color = color }
311 | pub fun setInfo(_ info: String) { self.info = info }
312 |
313 | pub fun asReadOnly(): Profile.ReadOnly {
314 | return Profile.ReadOnly(
315 | address: self.owner?.address,
316 | name: self.getName(),
317 | avatar: self.getAvatar(),
318 | color: self.getColor(),
319 | info: self.getInfo()
320 | )
321 | }
322 | }
323 |
324 | pub struct ReadOnly {
325 | pub let address: Address?
326 | pub let name: String
327 | pub let avatar: String
328 | pub let color: String
329 | pub let info: String
330 |
331 | init(address: Address?, name: String, avatar: String, color: String, info: String) {
332 | self.address = address
333 | self.name = name
334 | self.avatar = avatar
335 | self.color = color
336 | self.info = info
337 | }
338 | }
339 |
340 | pub fun new(): @Profile.Base {
341 | return <- create Base()
342 | }
343 |
344 | pub fun check(_ address: Address): Bool {
345 | return getAccount(address)
346 | .getCapability<&{Profile.Public}>(Profile.publicPath)
347 | .check()
348 | }
349 |
350 | pub fun fetch(_ address: Address): &{Profile.Public} {
351 | return getAccount(address)
352 | .getCapability<&{Profile.Public}>(Profile.publicPath)
353 | .borrow()!
354 | }
355 |
356 | pub fun read(_ address: Address): Profile.ReadOnly? {
357 | if let profile = getAccount(address).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() {
358 | return profile.asReadOnly()
359 | } else {
360 | return nil
361 | }
362 | }
363 |
364 | pub fun readMultiple(_ addresses: [Address]): {Address: Profile.ReadOnly} {
365 | let profiles: {Address: Profile.ReadOnly} = {}
366 | for address in addresses {
367 | let profile = Profile.read(address)
368 | if profile != nil {
369 | profiles[address] = profile!
370 | }
371 | }
372 | return profiles
373 | }
374 |
375 |
376 | init() {
377 | self.publicPath = /public/profile
378 | self.privatePath = /storage/profile
379 |
380 | self.account.save(<- self.new(), to: self.privatePath)
381 | self.account.link<&Base{Public}>(self.publicPath, target: self.privatePath)
382 |
383 | self.account
384 | .borrow<&Base{Owner}>(from: self.privatePath)!
385 | .setName("qvvg")
386 | }
387 | }
--------------------------------------------------------------------------------
/components/Landing.js:
--------------------------------------------------------------------------------
1 | import "../flow/config";
2 | import { useAuth } from "../contexts/AuthContext";
3 | import Profile from "./Profile";
4 | import Link from 'next/link';
5 |
6 | function Landing() {
7 | const { currentUser, profileExists, logOut, logIn, signUp, createProfile } =
8 | useAuth();
9 |
10 | const AuthedState = () => {
11 | return (
12 |
51 |
12 | This is a FCL-powered app built on Flow. 13 |
14 |15 | This app shows how to: 16 |
17 |Use this app as a starting template to build your very own web3 app, powered on Flow! Flow is a fast, decentralized, and developer-friendly blockchain, designed as the foundation for a new generation of games, apps, and the digital assets that power them. It is based on a unique, multi-role architecture, and designed to scale without sharding, allowing for massive improvements in speed and throughput while preserving a developer-friendly, ACID-compliant environment.
24 |