├── public
├── favicon.ico
├── vercel.svg
├── thirteen.svg
└── next.svg
├── jsconfig.json
├── next.config.js
├── pages
├── _app.js
├── api
│ └── hello.js
├── _document.js
└── index.js
├── package.json
├── flow
└── config.js
├── .gitignore
├── README.md
├── styles
├── globals.css
└── Home.module.css
└── contract
└── Profile.cdc
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holyaustin/Flow-App/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["./*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.css'
2 |
3 | export default function App({ Component, pageProps }) {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flow-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@onflow/fcl": "^1.3.2",
13 | "next": "13.2.1",
14 | "react": "18.2.0",
15 | "react-dom": "18.2.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/flow/config.js:
--------------------------------------------------------------------------------
1 | import { config } from "@onflow/fcl";
2 |
3 | config({
4 | "accessNode.api": "https://rest-testnet.onflow.org", // Mainnet: "https://rest-mainnet.onflow.org"
5 | "discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn", // Mainnet: "https://fcl-discovery.onflow.org/authn"
6 | "0xProfile": "0xba1132bc08f82fe2" // The account address where the Profile smart contract lives on Testnet
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 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Flow-App
2 | A Flow Blockchain DApp with Smart Contract built with Cardence.
3 |
4 | Deployed on testnet : https://testnet.flowscan.org/contract/A.ba1132bc08f82fe2.Profile
5 |
6 | ## Getting Started
7 |
8 | First, run the development server:
9 |
10 | ```bash
11 | npm run dev
12 | # or
13 | yarn dev
14 | # or
15 | pnpm dev
16 | ```
17 |
18 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
19 |
20 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
21 |
22 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
23 |
24 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
25 |
26 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
27 |
28 | ## Learn More
29 |
30 | To learn more about Next.js, take a look at the following resources:
31 |
32 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
33 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
34 |
35 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
36 |
37 | ## Deploy on Vercel
38 |
39 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
40 |
41 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
42 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --max-width: 1100px;
3 | --border-radius: 12px;
4 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
5 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
6 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
7 |
8 | --foreground-rgb: 0, 0, 0;
9 | --background-start-rgb: 214, 219, 220;
10 | --background-end-rgb: 255, 255, 255;
11 |
12 | --primary-glow: conic-gradient(
13 | from 180deg at 50% 50%,
14 | #16abff33 0deg,
15 | #0885ff33 55deg,
16 | #54d6ff33 120deg,
17 | #0071ff33 160deg,
18 | transparent 360deg
19 | );
20 | --secondary-glow: radial-gradient(
21 | rgba(255, 255, 255, 1),
22 | rgba(255, 255, 255, 0)
23 | );
24 |
25 | --tile-start-rgb: 239, 245, 249;
26 | --tile-end-rgb: 228, 232, 233;
27 | --tile-border: conic-gradient(
28 | #00000080,
29 | #00000040,
30 | #00000030,
31 | #00000020,
32 | #00000010,
33 | #00000010,
34 | #00000080
35 | );
36 |
37 | --callout-rgb: 238, 240, 241;
38 | --callout-border-rgb: 172, 175, 176;
39 | --card-rgb: 180, 185, 188;
40 | --card-border-rgb: 131, 134, 135;
41 | }
42 |
43 | @media (prefers-color-scheme: dark) {
44 | :root {
45 | --foreground-rgb: 255, 255, 255;
46 | --background-start-rgb: 0, 0, 0;
47 | --background-end-rgb: 0, 0, 0;
48 |
49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
50 | --secondary-glow: linear-gradient(
51 | to bottom right,
52 | rgba(1, 65, 255, 0),
53 | rgba(1, 65, 255, 0),
54 | rgba(1, 65, 255, 0.3)
55 | );
56 |
57 | --tile-start-rgb: 2, 13, 46;
58 | --tile-end-rgb: 2, 5, 19;
59 | --tile-border: conic-gradient(
60 | #ffffff80,
61 | #ffffff40,
62 | #ffffff30,
63 | #ffffff20,
64 | #ffffff10,
65 | #ffffff10,
66 | #ffffff80
67 | );
68 |
69 | --callout-rgb: 20, 20, 20;
70 | --callout-border-rgb: 108, 108, 108;
71 | --card-rgb: 100, 100, 100;
72 | --card-border-rgb: 200, 200, 200;
73 | }
74 | }
75 |
76 | * {
77 | box-sizing: border-box;
78 | padding: 0;
79 | margin: 0;
80 | }
81 |
82 | html,
83 | body {
84 | max-width: 100vw;
85 | overflow-x: hidden;
86 | }
87 |
88 | body {
89 | color: rgb(var(--foreground-rgb));
90 | background: linear-gradient(
91 | to bottom,
92 | transparent,
93 | rgb(var(--background-end-rgb))
94 | )
95 | rgb(var(--background-start-rgb));
96 | }
97 |
98 | a {
99 | color: inherit;
100 | text-decoration: none;
101 | }
102 |
103 | @media (prefers-color-scheme: dark) {
104 | html {
105 | color-scheme: dark;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import "../flow/config";
3 | import { useState, useEffect } from "react";
4 | import * as fcl from "@onflow/fcl";
5 |
6 | export default function Home() {
7 |
8 | const [user, setUser] = useState({loggedIn: null})
9 | const [name, setName] = useState('')
10 | const [transactionStatus, setTransactionStatus] = useState(null) // NEW
11 |
12 | useEffect(() => fcl.currentUser.subscribe(setUser), [])
13 |
14 | const sendQuery = async () => {
15 | const profile = await fcl.query({
16 | cadence: `
17 | import Profile from 0xProfile
18 |
19 | pub fun main(address: Address): Profile.ReadOnly? {
20 | return Profile.read(address)
21 | }
22 | `,
23 | args: (arg, t) => [arg(user.addr, t.Address)]
24 | })
25 |
26 | setName(profile?.name ?? 'No Profile')
27 | }
28 |
29 | const initAccount = async () => {
30 | const transactionId = await fcl.mutate({
31 | cadence: `
32 | import Profile from 0xProfile
33 |
34 | transaction {
35 | prepare(account: AuthAccount) {
36 | // Only initialize the account if it hasn't already been initialized
37 | if (!Profile.check(account.address)) {
38 | // This creates and stores the profile in the user's account
39 | account.save(<- Profile.new(), to: Profile.privatePath)
40 |
41 | // This creates the public capability that lets applications read the profile's info
42 | account.link<&Profile.Base{Profile.Public}>(Profile.publicPath, target: Profile.privatePath)
43 | }
44 | }
45 | }
46 | `,
47 | payer: fcl.authz,
48 | proposer: fcl.authz,
49 | authorizations: [fcl.authz],
50 | limit: 50
51 | })
52 |
53 | const transaction = await fcl.tx(transactionId).onceSealed()
54 | console.log(transaction)
55 | }
56 |
57 | // NEW
58 | const executeTransaction = async () => {
59 | const transactionId = await fcl.mutate({
60 | cadence: `
61 | import Profile from 0xProfile
62 |
63 | transaction(name: String) {
64 | prepare(account: AuthAccount) {
65 | account
66 | .borrow<&Profile.Base{Profile.Owner}>(from: Profile.privatePath)!
67 | .setName(name)
68 | }
69 | }
70 | `,
71 | args: (arg, t) => [arg("Flow Developer!", t.String)],
72 | payer: fcl.authz,
73 | proposer: fcl.authz,
74 | authorizations: [fcl.authz],
75 | limit: 50
76 | })
77 |
78 | fcl.tx(transactionId).subscribe(res => setTransactionStatus(res.status))
79 | }
80 |
81 | const AuthedState = () => {
82 | return (
83 |
84 |
Address: {user?.addr ?? "No Address"}
85 |
Profile Name: {name ?? "--"}
86 |
Transaction Status: {transactionStatus ?? "--"}
{/* NEW */}
87 |
88 |
89 |
{/* NEW */}
90 |
91 |
92 | )
93 | }
94 |
95 | const UnauthenticatedState = () => {
96 | return (
97 |
98 |
99 |
100 |
101 | )
102 | }
103 |
104 | return (
105 |
106 |
107 |
FCL Quickstart with NextJS
108 |
109 |
110 |
111 |
Flow App
112 | {user.loggedIn
113 | ?
114 | :
115 | }
116 |
117 | )
118 | }
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | width: var(--max-width);
46 | max-width: 100%;
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | }
56 |
57 | .card span {
58 | display: inline-block;
59 | transition: transform 200ms;
60 | }
61 |
62 | .card h2 {
63 | font-weight: 600;
64 | margin-bottom: 0.7rem;
65 | }
66 |
67 | .card p {
68 | margin: 0;
69 | opacity: 0.6;
70 | font-size: 0.9rem;
71 | line-height: 1.5;
72 | max-width: 30ch;
73 | }
74 |
75 | .center {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | position: relative;
80 | padding: 4rem 0;
81 | }
82 |
83 | .center::before {
84 | background: var(--secondary-glow);
85 | border-radius: 50%;
86 | width: 480px;
87 | height: 360px;
88 | margin-left: -400px;
89 | }
90 |
91 | .center::after {
92 | background: var(--primary-glow);
93 | width: 240px;
94 | height: 180px;
95 | z-index: -1;
96 | }
97 |
98 | .center::before,
99 | .center::after {
100 | content: '';
101 | left: 50%;
102 | position: absolute;
103 | filter: blur(45px);
104 | transform: translateZ(0);
105 | }
106 |
107 | .logo,
108 | .thirteen {
109 | position: relative;
110 | }
111 |
112 | .thirteen {
113 | display: flex;
114 | justify-content: center;
115 | align-items: center;
116 | width: 75px;
117 | height: 75px;
118 | padding: 25px 10px;
119 | margin-left: 16px;
120 | transform: translateZ(0);
121 | border-radius: var(--border-radius);
122 | overflow: hidden;
123 | box-shadow: 0px 2px 8px -1px #0000001a;
124 | }
125 |
126 | .thirteen::before,
127 | .thirteen::after {
128 | content: '';
129 | position: absolute;
130 | z-index: -1;
131 | }
132 |
133 | /* Conic Gradient Animation */
134 | .thirteen::before {
135 | animation: 6s rotate linear infinite;
136 | width: 200%;
137 | height: 200%;
138 | background: var(--tile-border);
139 | }
140 |
141 | /* Inner Square */
142 | .thirteen::after {
143 | inset: 0;
144 | padding: 1px;
145 | border-radius: var(--border-radius);
146 | background: linear-gradient(
147 | to bottom right,
148 | rgba(var(--tile-start-rgb), 1),
149 | rgba(var(--tile-end-rgb), 1)
150 | );
151 | background-clip: content-box;
152 | }
153 |
154 | /* Enable hover only on non-touch devices */
155 | @media (hover: hover) and (pointer: fine) {
156 | .card:hover {
157 | background: rgba(var(--card-rgb), 0.1);
158 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
159 | }
160 |
161 | .card:hover span {
162 | transform: translateX(4px);
163 | }
164 | }
165 |
166 | @media (prefers-reduced-motion) {
167 | .thirteen::before {
168 | animation: none;
169 | }
170 |
171 | .card:hover span {
172 | transform: none;
173 | }
174 | }
175 |
176 | /* Mobile */
177 | @media (max-width: 700px) {
178 | .content {
179 | padding: 4rem;
180 | }
181 |
182 | .grid {
183 | grid-template-columns: 1fr;
184 | margin-bottom: 120px;
185 | max-width: 320px;
186 | text-align: center;
187 | }
188 |
189 | .card {
190 | padding: 1rem 2.5rem;
191 | }
192 |
193 | .card h2 {
194 | margin-bottom: 0.5rem;
195 | }
196 |
197 | .center {
198 | padding: 8rem 0 6rem;
199 | }
200 |
201 | .center::before {
202 | transform: none;
203 | height: 300px;
204 | }
205 |
206 | .description {
207 | font-size: 0.8rem;
208 | }
209 |
210 | .description a {
211 | padding: 1rem;
212 | }
213 |
214 | .description p,
215 | .description div {
216 | display: flex;
217 | justify-content: center;
218 | position: fixed;
219 | width: 100%;
220 | }
221 |
222 | .description p {
223 | align-items: center;
224 | inset: 0 0 auto;
225 | padding: 2rem 1rem 1.4rem;
226 | border-radius: 0;
227 | border: none;
228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
229 | background: linear-gradient(
230 | to bottom,
231 | rgba(var(--background-start-rgb), 1),
232 | rgba(var(--callout-rgb), 0.5)
233 | );
234 | background-clip: padding-box;
235 | backdrop-filter: blur(24px);
236 | }
237 |
238 | .description div {
239 | align-items: flex-end;
240 | pointer-events: none;
241 | inset: auto 0 0;
242 | padding: 2rem;
243 | height: 200px;
244 | background: linear-gradient(
245 | to bottom,
246 | transparent 0%,
247 | rgb(var(--background-end-rgb)) 40%
248 | );
249 | z-index: 1;
250 | }
251 | }
252 |
253 | /* Tablet and Smaller Desktop */
254 | @media (min-width: 701px) and (max-width: 1120px) {
255 | .grid {
256 | grid-template-columns: repeat(2, 50%);
257 | }
258 | }
259 |
260 | @media (prefers-color-scheme: dark) {
261 | .vercelLogo {
262 | filter: invert(1);
263 | }
264 |
265 | .logo,
266 | .thirteen img {
267 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
268 | }
269 | }
270 |
271 | @keyframes rotate {
272 | from {
273 | transform: rotate(360deg);
274 | }
275 | to {
276 | transform: rotate(0deg);
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/contract/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 | }
388 |
--------------------------------------------------------------------------------