├── 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 | 7 | 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 | --------------------------------------------------------------------------------