2021-04-09 01:25 | 出处: FLOW福洛链
本文假设读者是熟悉 JavaScript 和 React 的开发者,对 Flow 有着一定的了解,或者熟悉 Flow 智能合约语言 Cadence 相关的概念。
我们将通过本文熟悉并搭建本地开发环境,使用 JavaScript 根据现有的 JS-SDK 完成对链的调用与交互。
包含以下内容:
教程内容参照了原文 flow-js-sdk quick start 内容根据最新的代码和示例略有增补。
为了方便读者理解,我们直接使用 flow-js-sdk 官方提供的代码库作为基础,并针对原有的示例略有一些调整,请参照 fork 的仓库 react-fcl-demo 来完成部署和演示
git clone https://github.com/caosbad/react-fcl-demo.git
cd react-fcl-demo
yarn
首先将远程仓库克隆到本地,然后在实例项目中安装依赖,yarn 会将 package.json 文件中的项目依赖 @onflow/fcl @onflow/sdk @onflow/six-set-code @onflow/dev-wallet 等下载。
@onflow/fcl -- 基于 DApp 开发者的需求,对 @onflow/sdk 的一层封装。@onflow/sdk -- 使用 JavaScript 进行 build, resolve, send 和 decode 工具,与 Flow 链进行交互。@onflow/dev-wallet -- 提供给测试与开发的本地钱包环境。在这之前,我们还需要初始化 Flow 的本地模拟器启动 wallet-dev 服务
模拟器是帮助我们在本机启动一个本地的 Flow 网络,类似于以太坊的 ganache,模拟器的下载安装步骤可以参考这里 instructions.
// Linux and macOS sh -ci "¥(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" // Windows iex "& { ¥(irm ‘https://storage.googleapis.com/flow-cli/install.ps1‘) }"
// --init 参数是在第一次启动的时候添加,如果已经初始化过,就直接执行 start 命令 flow emulator start --init
在示例项目的目录里执行 init 命令后,我们会发现目录下多出了一个 flow.json 文件,类似于以下的结构:
{ "accounts": { "service": { "address": "f8d6e0586b0a20c7", "privateKey": "84f82df6790f07b281adb5bbc848bd6298a2de67f94bdfac7a400d5a1b893de5", "sigAlgorithm": "ECDSA_P256", "hashAlgorithm": "SHA3_256" } } }
模拟器启动之后你会看到启动成功的日志,模拟器提供了 gRPC 和 http 通信的接口
接下来在新的终端启动 Dev wallet
在 package.json 文件中,我们会看到 scripts 的配置项中有名为 dev-wallet 和 dev-wallet-win 两个脚本,现在把我们上一步模拟初始化生成的 privateKey 覆盖现有的配置。
然后执行 yarn run dev-wallet 或 yarn run dev-wallet-win
成功之后,将会看到以下的日志:
这里启动了多个服务,同时注意 Service Address 和 Private Key 与模拟器生成的一致。
环境已经配置成功,接下来就是启动示例项目:
yarn start
确保模拟器和 Dev wallet 也在启动的状态,我们可以看到页面上的一些示例操作,下面我们从代码层面了解一些交互的细节
// src/demo/GetLatestBlock.tsx import { decode, send, getLatestBlock } from "@onflow/fcl" const GetLatestBlock = () => { const [data, setData] = useState(null) const runGetLatestBlock = async (event: any) => { event.preventDefault() const response = await send([ getLatestBlock(), ]) setData(await decode(response)) // 解码返回的数据,并更新 state }
// 返回结果 { "id": "de37aabaf1ce314da4a6e2189d9584b71a7f302844b4ed5fb1ca3042afbad3d0", // 区块的 id "parentId": "1ae736bdea1065a98262348d5a7a2141d2b21a76ac2184b3e1181088de430255", // 上一个区块的 id "height": 2, "timestamp": { "wrappers_": null, "arrayIndexOffset_": -1, "array": [ 1607256408, 195959000 ], "pivot_": 1.7976931348623157e+308, "convertedPrimitiveFields_": {} }, "collectionGuarantees": [ { "collectionId": "49e27fcf465075e6afd9009478788ba801fefa85a919d48df740e541cc514497", "signatures": [ {} ] } ], "blockSeals": [], "signatures": [ {} ] }
这里需要我们输入用户地址来完成查询,
// src/demo/GetAccount.tsx const runGetAccount = async (event: any) => { const response = await fcl.send([ fcl.getAccount(addr), // 通过地址获取用户信息 ]) setData(await fcl.decode(response)) }
{ "address": "01cf0e2f2f715450", // 地址 "balance": 0, "code": {}, "keys": [ { "index": 0, "publicKey": "7b3f982ebf0e87073831aa47543d7c2a375f99156e3d0cff8c3638bb8d3f166fd0db7c858b4b77709bf25c07815cf15d7b2b7014f3f31c2efa9b5c7fdac5064d", // 公钥 "signAlgo": 2, "hashAlgo": 3, "weight": 1000, "sequenceNumber": 1 } ] }
执行脚本我们可以理解为是一种无需用户授权的查询操作
// src/demo/ScriptOne.tsx const scriptOne = ` pub fun main(): Int { return 42 + 6 } ` const runScript = async (event: any) => { const response = await fcl.send([ fcl.script(scriptOne), ]) setData(await fcl.decode(response)) // 48 }
这里我们可以看到在智能合约里可以定义复杂的数据结构, 并且通过 typescript 的类型进行数据的解构,能够将复杂的数据与前端的应用层友好的关联。
// src/model/Point.ts 这里定义了结构数据的类型 class Point { public x: number; public y: number; constructor (p: Point) { this.x = p.x this.y = p.y } } export default Point; // src/demo/ScriptTwo.tsx const scriptTwo = ` pub struct SomeStruct { pub var x: Int pub var y: Int init(x: Int, y: Int) { self.x = x self.y = y } } pub fun main(): [SomeStruct] { return [SomeStruct(x: 1, y: 2), SomeStruct(x: 3, y: 4)] } `; fcl.config() .put("decoder.SomeStruct", (data: Point) => new Point(data)) // 这里定义了 fcl 对数据的解构方式 const runScript = async (event: any) => { event.preventDefault() const response = await fcl.send([ // 脚本的执行可以认为是一种读操作,不需要用户授权 fcl.script(scriptTwo), ]) setData(await fcl.decode(response)) } // class 中的 public 和 脚本中的 pub 替换
这里需要注意几点:
// 输出结果 Point 0 { "x": 1, "y": 2 } -- Point 1 { "x": 3, "y": 4 } --
确保我们本地运行了 Dev wallet 服务
在 demo 的页面点击 Sign In/Up Dev wallet 将会弹出授权页面:
接着点击授权,会进入到更新 profile 的界面
保存并应用之后,Dev wallet 会将 profile 的信息存入数据库中,订阅函数将会执行回调,将 user 的信息作为参数传递回来
// src/demo/Authenticate.tsx const signInOrOut = async (event: any) => { event.preventDefault() if (loggedIn) { fcl.unauthenticate() // logout } else { fcl.authenticate() // sign in or sign up ,这里会呼出 Dev wallet 的窗口 } } // line:38 fcl.currentUser().subscribe((user: any) => setUser({...user})) // fcl.currentUser() 这里提供了监听方法,并动态获取 User 数据
对应用开发者来说,fcl 帮助我们管理用户的登录状态和所需要的授权操作,会在下文发送交易的章节详述。
// user 返回值 { "VERSION": "0.2.0", "addr": "179b6b1cb6755e31", // 用户的地址 "cid": "did:fcl:179b6b1cb6755e31", "loggedIn": true, // 登录状态 "services": [ // 服务数据 { "type": "authz", "keyId": 0, "id": "asdf8701#authz-http-post", "addr": "179b6b1cb6755e31", "method": "HTTP/POST", "endpoint": "http://localhost:8701/flow/authorize", "params": { "userId": "37b92714-2713-41b0-9749-fc08b3fdd827" } }, { "type": "authn", "id": "wallet-provider#authn", "pid": "37b92714-2713-41b0-9749-fc08b3fdd827", "addr": "asdf8701", "name": "FCL Dev Wallet"您可能感兴趣的文章:
相关文章