当前位置:主页 > 列表页 > 正文

使用 JS-SDK 与 FLOW 交互

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 等下载。

在这之前,我们还需要初始化 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

启动 Dev wallet 服务

package.json 文件中,我们会看到 scripts 的配置项中有名为 dev-walletdev-wallet-win 两个脚本,现在把我们上一步模拟初始化生成的 privateKey 覆盖现有的配置。

然后执行 yarn run dev-walletyarn 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"
  
相关文章