🧬

Angular,cosmos-clientによるブロックチェーンWebアプリフロントエンド開発

2021/10/08に公開

株式会社CauchyE の角田と申します。

今回はCosmos SDKで作成したブロックチェーンとやり取りができる、弊社製クライアントライブラリcosmos-clientを紹介します。cosmos-clientのTypeScript言語の実装であるcosmos-client-tsを使えば、ブロックチェーンからの情報取得やトランザクションの発行といった、チェーンとのやり取りをTypeScript言語で簡単に行うことができます。TypeScriptの他にRustでの実装もあります。

今回の記事ではcosmos-clientの動作確認として、チェーンからあるアドレスが所持するトークン一覧を取得するWebアプリを作成します。そして実際にローカル環境でCosmos SDK製ブロックチェーンを立ち上げ、トークン一覧の取得ができることを確認します。

環境構築

実行環境

  • OS
    • Ubuntu 20.04 (on WSL2)
  • Node.js
    • v14.17.2
  • npm
    • 7.19.1
  • Starport
    • v0.18.0

ローカルチェーンの準備

先にStarportを使用してローカル環境にCosmos SDK製ブロックチェーンを立ち上げておきます。Starportの詳細や、インストール方法は参考記事[1][2]を参照ください。
とても簡単にいうと、以下のコマンドを実行するとStarporがインストールされて、ローカル環境に"mars"というチェーンIDを持つブロックチェーンが立ち上がります。

$ curl https://get.starport.network/starport | bash
$ sudo mv starport /usr/local/bin/
$ starport scaffold chain github.com/cosmonaut/mars
$ cd mars
$ starport chain serve

無事チェーンが立ち上がれば、コンソールに以下のような出力があります。

Cosmos SDK's version is: stargate - v0.42.5

🛠️ Building proto...
📦 Installing dependencies...
🛠️ Building the blockchain...
💿 Initializing the app...
🙂 Created account "alice" with address "cosmos1nl3856m4mjlgmukntldmgdg7t5yc593dmxfsml" with mnemonic: "obscure merit fix melody already sell labor picnic pottery trophy guide vivid coconut remove number gaze upset man interest letter gospel material eight online"
🙂 Created account "bob" with address "cosmos1fulw7j29ptg7szyn7xdmc2pky6lmy7ytvel3cd" with mnemonic: "layer steak devote carpet chronic satoshi only lecture smart sister surface fringe vessel result good fat diet ginger resist marine forward address weird asthma"
Genesis transaction written to "/home/symbol/.mars/config/gentx/gentx-b93aa7bfadc7045da179e9e5d53575fa60aaf1be.json"
🌍 Tendermint node: http://0.0.0.0:26657
🌍 Blockchain API: http://0.0.0.0:1317
🌍 Token faucet: http://0.0.0.0:4500

後々必要になるので、AliceとBobのアドレスをメモっておきましょう。

"alice" with address "cosmos1nl3856m4mjlgmukntldmgdg7t5yc593dmxfsml"
"bob" with address "cosmos1fulw7j29ptg7szyn7xdmc2pky6lmy7ytvel3cd"

Angular

AngularでのWebアプリ開発については、取り扱っている記事が多数あるので分かりやすいものを参考にしてみて下さい。[3]
とても簡単にいうと、以下のコマンドを実行するとサンプルのWebアプリ"my-app"がlocalhost:4200に立ち上がります。

$ npm install -g @angular/cli
$ ng new my-app
$ ng serve -o

cosmos-client

いよいよcosmos-clientを使用していきます。
まずはインストール。

$ npm install --save cosmos-client

npmパッケージインストール直後の状態で、Angularでcosmos-clientを使用するとさまざまなエラーが出るかと思います。ひとつづつ解消していきます。

1. polyfills関連

polyfillsとは、書いたコードをどのブラウザでも使用できるようにする仕組みです。
詳しくは以下を参照ください。
polyfill、babelとは?jsをどのブラウザでも動くようにしてくれる。(IE対応)

polyfills.tsに以下追記

polyfills.ts
 (window as any).global = window; //追記
global.Buffer = global.Buffer || require('buffer').Buffer; //追記
global.process = require('process'); //追記

processが必要なのでnpmパッケージをインストール

$ npm i process

tsconfig.app.jsonに"node"を追記

tsconfig.app.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": ["node"] //"node"を追記
  }
{

2. allowSyntheticDefaultImports

モジュールを一括で使用できるようにするオプションです。
インポートしているモジュールで使用されているため、このオプションを有効化する必要があります。
allowSyntheticDefaultImports

//例)
import React from "react";
//↓
import * as React from "react";
//React.***として使用可能になる。これをできるようにするオプション。

tsconfig.jsonファイルのコンパイラオプションに追記

tsconfig.json
"allowSyntheticDefaultImports": true,

3. "crypto","stream"

"crypto","stream"関連のエラーは以下の記事が参考になります。
Symbol x Angular その1 Angularでsymbol-sdkを使うための環境構築
結果だけ書くと、
npmモジュールインストールし、

$ npm install crypto-browserify stream-browserify -S

tsconfig.jsonファイルでインストールしたモジュールのパスを指定すれば良いです。

tsconfig.json
    "paths": {
      "crypto": ["./node_modules/crypto-browserify"],
      "stream": ["./node_modules/stream-browserify"]
    }

以上3点でエラーが消えているはずです。

動作確認

実際に、簡単なサンプルを実行してcosmos-clientの動きを確認します。
以下は入力したアドレスの所持するトークンとその残高を表示するサンプルです。
app.module.ts/ app.component.ts/ app.component.htmlを以下のコードに変更します。

app.module.ts

app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

app.component.ts
import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, of, Observable } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { cosmosclient, rest } from 'cosmos-client';
import { InlineResponse20027Balances } from 'cosmos-client/cjs/openapi/api';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    nodeURL = 'http://localhost:1317';
    chainID = "mars";
    sdk: cosmosclient.CosmosSDK;
    address = "";
    address$: BehaviorSubject<string> = new BehaviorSubject(this.address);
    accAddress$: Observable<cosmosclient.AccAddress | undefined>;
    balances$: Observable<InlineResponse20027Balances[] | undefined>;

    constructor() {
      this.sdk = new cosmosclient.CosmosSDK(this.nodeURL, this.chainID);
      this.address$ = new BehaviorSubject(this.address);
      //addressからaccAddressを取得
      this.accAddress$ = this.address$.pipe(
        map((address) => {
          try {
            return cosmosclient.AccAddress.fromString(address);
          } catch (error) {
            console.error(error);
            return undefined;
          }
        }),
        catchError((error) => {
          console.error(error);
          return of(undefined);
        })
      )
      //所持tokenを取得
      this.balances$ = this.accAddress$.pipe(
        mergeMap((accAddress) => {
          console.log(accAddress);
          if (accAddress === undefined) {
            console.error('Address is invalid or does not have balances!');
            return of([]);
          }
          return rest.cosmos.bank.allBalances(this.sdk, accAddress).then(res => res.data.balances);
        }),
        catchError((error) => {
          console.error(error);
          return of([]);
        })
      )
    }
    ngOnInit(): void {}
    //addressを更新
    changeAddress(address: string): void {
      this.address$.next(address);
    }
  }

app.component.html

app.component.html
<h2>my-app</h2>
<h3>input address</h3>
<input [(ngModel)]="address" placeholder="input address" >
<button (click)="changeAddress(address)">set address</button>
<!--入力したaddressを表示-->
<h3>address</h3>
<div>address : {{ address }}</div>
<!--所持tokenを表示-->
<ng-container *ngIf="balances$ | async as balances">
  <h3>balances</h3>
    <ng-container *ngFor="let balance of balances">
      <div>token: {{ balance.denom }} balance: {{ balance.amount }}</div>
    </ng-container>
</ng-container>

変更後、Webアプリを立ち上げ直してください。

<デフォルト>

入力欄にAliceのアドレスを入力し、「set address」を押すと、、

<実行結果 Alice>

Aliceの所持トークンの一覧が表示されます。

この数字が正しいことを確かめます。
/mars/config.ymlにチェーン立ち上げ時にアカウントに与えられるトークンが記載されています。

config.yml
accounts:
  - name: alice
    coins: ["20000token", "200000000stake"]
  - name: bob
    coins: ["10000token", "100000000stake"]
validator:
  name: alice
  staked: "100000000stake"

Aliceは2万Tokenと2億stakeを付与され、1億stakeをステーキングしました。

作成したサンプルWebアプリによると、Aliceは2万Tokenと1億stake所持しています。これで正しい値を読み出せていることが確認できました!

まとめ

cosmos-clientの紹介と、環境構築方法の説明、簡単なサンプルで動作確認を行いました。
cosmos-clientを使えば簡単にCosmos SDK製ブロックチェーンにアクセスできることが伝われば幸いです。

CauchyE は一緒に働いてくれる人を待ってます!

ブロックチェーンやデータサイエンスに興味のあるエンジニアを積極的に採用中です!
以下のページから応募お待ちしております。
https://cauchye.com/company/recruit

脚注
  1. https://github.com/tendermint/starport ↩︎

  2. https://docs.starport.network/guide/install.html ↩︎

  3. https://www.tohoho-web.com/ex/angular.html ↩︎

Discussion