Symbol x React その1 Reactでsymbol-sdkを使うための環境構築
Reactでsymbol-sdkを使うための環境構築
概要
この記事では、Reactで作成されたWebアプリ上でsymbol-sdkというnpmパッケージを用いてSymbolというブロックチェーンを利用するための第一歩となる環境構築について説明します。
以下URLでサンプルコードを実際に動かしているので、必要に応じてご参照ください。
要約
当記事は著作権、人格権、財産権を侵害する目的はございません。
報道、批評、研究を目的としており、記事内の素材は全て下記のいずれかに該当します
・当方が独自に作成したもの
・著作権法第30条2項に基づく「付随対象著作物の利用」
・著作権法第32条1項に基づく「引用」
・著作権法第41条に基づく「時事の事件の報道のための利用」
・著作権法第63条に基づく「許諾を得た著作物の利用」
前提となる環境
- OS
- MacOS Big Sur
- MacBook Pro (13-inch, M1, 2020)
- Apple M1
 
- Node.js
- v16.11.0
 
- npm
- 8.0.0
 
Symbolとは
symbol-sdkとは
rxjsとは
Reactとは
ReactはJavascriptフレームワークです。
色々と記載はありますが、基本的にはウェブの画面を作れるようになるフレームワークです。
もちろん使い方によってはiosアプリケーションや、androidアプリケーションも作れるようになるアプリケーションです。
個人的な感想ですが、
AngularやVueやStencilなど色々フレームワークがありますが、
基本的には「好み」もしくは「案件」によりますが、正直学習コストもそんなに変わりませんし、
今回僕がReactの記事を記載するに当たっても「たまたまReactを案件で使ったことがあるから」
という理由なので、よくあるフレームワーク戦争に僕を巻き込むのはやめてください。
そういうのはTwitterで僕の目の届かないところでお願いします。
Reactのアプリケーションを作ってみる
まずはnodeのバージョンの確認をしましょう。
基本的に安定バージョンが入っていれば問題はないのですが、
時たまバージョンの都合でうまくいきませんということもあるので、
その時はまずは落ち着きましょう!

$ node -v
v16.11.0
そうすると自動的にnpmもくっついてくるはずなので一応バージョン確認しておきましょう
$ npm -v
8.0.0
あとは好きな階層にディレクトリを作成しましょう。
僕はこんな感じでディレクトリの名前をreact_symbol_typescriptとしました。

$ npx create-react-app . --template typescript --use-npm
npm を使用して色々とreactとtypescriptの最初のプロジェクトを作成していきます。
npx create-react-app .
この書き方で現在のディレクトリに作成するので、特に問題ないです。
これは僕の好みで実施しています
--template typescript
こちらはデフォルトをtypescriptにしてくれます。
Symbol SDKもTypescriptで書いているのでそっちに合わせましょう!と言ったところです。
正直TypeScriptは最初はとても難しいと思うようになるのですが、
これも要するに慣れなので、最初はいっぱいエラーが吐いてしんどいと思いますが、
慣れです。
この慣れという考えが非常に重要なので、エラーに対して悩むのではなく、
楽しんで行ってもらえますと幸いです。
Reactのアプリケーションができますと以下のメッセージが出ます
Happy hacking!
なので楽しく開発しましょう!!
初回起動
$ npm start
このコマンドでローカルのPC環境にアプリケーションを実行することができます

(追記2020年7月7日)ダウングレード対応
現在react-scriptsがversion5になっています。
こちらversionが5の状態ですとcrypto周りでエラーが発生します。(コメントいただいた方に感謝です)
ですので、色々先駆者がいて回避策とかもあるみたいですが、ここは一番簡単なreact-scriptを4のバージョンに下げます
ですのでpackage.jsonのreact-scriptの項目を4.0.3に変更してください。
    "react-scripts": "4.0.3",
そしてその後にnpm installをしていただくとreact-scriptsのバージョンは4に変わります。
$ npm install
Tailwind CSS導入
(追記2022年7月7日)
react-scriptsをバージョン4にダウングレードするとTailwind CSSもダウングレード対応した方法でのインストールが必要です。
ーーーー4以前の方はこちらになります。ーーーー
必要なモジュールのインストール
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npm install @craco/craco
package.json scriptの編集
{
    // ...
    "scripts": {
     "start": "craco start",
     "build": "craco build",
     "test": "craco test",
    // ...
    },
  }

craco.configの作成
touch craco.config.js
module.exports = {
  style: {
    postcss: {
      plugins: [
        require('tailwindcss'),
        require('autoprefixer'),
      ],
    },
  },
}

tailwind.config.js, postcss.config.jsの生成
$ npx tailwindcss init -p
Created Tailwind CSS config file: tailwind.config.js
Created PostCSS config file: postcss.config.js
tailwind.config.jsのpurge設定追加
tailwind.config.jsの中身をコピーしましょう
module.exports = {
    purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
    darkMode: false,
    theme: {
        extend: {},
    },
    variants: {
        extend: {},
    },
    plugins: [],
}

./src/index.cssの編集
@tailwind base;
@tailwind components;
@tailwind utilities;

(追記2022年7月7日)コードフォーマッターの追加
prettierの設定 : settingsでRequire Config + Format On Saveにチェック
touch .prettierrc
{
    "singleQuote": true,
    "semi": false
}


一度このタイミングでnpm startをしてみましょう。
エラーがなければよかったです!
npm start
一旦tailwindcssのバージョンを落とそうといった感じです。
npm uninstall tailwindcss postcss autoprefixer
npm install tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
現時点でのリポジトリはこちら
tailwindcss導入
この章のまとめ
環境構築は結構大事です。
アプリケーション作成のためのタスク管理
さてさて、これで環境構築ができました。
正直ここを突破しないと何も続きができないので是非とも突破してほしいです。
これ以降はタスクを一つずつ作成していき、そのタスクを処理していきます。
これは個人的な感想ですが、タスクを作成して一つずつ処理していけばアプリはできるはずです。
ただそのタスクを考える力やタスクを無心となって処理する力
タスク自体の設定が間違っていたときに
「詰まる」という状況になります。
今回の記事ではタスク管理にGoogleスプレッドシートを使用しました。
Googleアカウントがあれば今回の記事でのタスク一覧を以下リンクから自由に参照できますので、必要に応じてご参照ください。
デザイン確認
今回はサンプルアプリケーション(松岡さんが作成されたもの)
をReactで作成する予定なのでデザインは簡単にできます。
以下デザインチェックを実施した資料になります。
この章のまとめ
何を作るかを書き出すのはとても大事!!
機能確認
次にどういった機能が必要なのかを考えます。
機能的にはSymbol SDKを使用したものが必要なのかな?という想定で
実施します。
- アカウントの生成
- アドレス、パブリックキーの取得
- 保有モザイクの取得
- インポータンスの取得
の4つです。
なのでこの時点でsymbolの機能で作成すべき内容が決まりました。
タスク管理のオレンジの以下の部分を作成します。
機能開発のためにsymbol-sdkをインストールします
npm install symbol-sdk rxjs
これでインストールができてから動作確認をしましょう
npm start
ここでエラーが出た場合は教えてくださいね。
アカウントの生成
さてさてまずは機能開発を実施しましょう。
アドレスの生成です。
松岡さんの記事を参考にするのであればURLの後ろにアドレスを付与すればいいのですが、
今回はどうしようかな?
そうですね、テストネットを使用するので皆さん好きな方法を採用してみてください。
そもそもアカウントの生成方法はどのような方法があるのか?という話を考えます。
Symbol SDKでアカウントを作成する方法はいくつかあります。
というわけなのでまずは機能を確認するだけなのでsrc/App.tsxにSymbol SDKをimportしましょう。
import { Account } from 'symbol-sdk'

はい、というわけでアカウントの作成については
写真のようにcreateFromPrivateKeyとgenerateNewAccountの二つの方法があります。
アドレスの表示については他の方法があるのですが、まずはアカウントの生成の部分を確認していきます。
おそらくウォレットを何か作りたいなぁとなった時はまずは
- 新規アカウントを作成するか
- 秘密鍵からアカウントを作成するか?
の2つの手段がとられると思います。
新規アカウントを作る方法
Symbol SDKからアカウントを新規に作成する方法は
AccountとNetworkTypeが必要になります
import logo from './logo.svg'
import './App.css'
import { Account, NetworkType } from 'symbol-sdk'
function App() {
  const accountCreate = () => {
    const account = Account.generateNewAccount(NetworkType.TEST_NET)
    console.log(
      'Your new account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <button onClick={accountCreate}>アカウントの作成</button>
      </header>
    </div>
  )
}
export default App
さてこれを実行するとこんな感じになります

アカウントの作成という文字がボタンになっています。
まずはChromeの検証(inspector)をクリックします。

そうすると「コンソール」もしくは「console」という項目がありますのでクリックします。

これでアプリケーション内にconsoleに出力するという命令があるとここに表示されます。
現時点ではsrc/App.tsx内に存在するaccountCreateの関数にconsole.logという関数が実装されています。
ここでコンソールに出力するという命令になっています。
この関数の流れとしては
- まずはアカウントを生成してaccountという定数の中に格納します
- 次にコンソールログで'Your new account address is: アカウントのアドレスの文字列を表示'となっています
- また最後には'and its private key アカウントのプライベートキーを表示'
という処理が実施されます。
const accountCreate = () => {
    const account = Account.generateNewAccount(NetworkType.TEST_NET)
    console.log(
      'Your new account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
それではこのアカウントを生成のボタンを押してみます。
どうなるかな?
Your new account address is: TDK7FE-VBYAE7-BHNFPK-DWXTIL-HJJ7G2-U6MWMJ-6CY and its private key A03B6B24549989C381A88149E18AF8C7B2E2639C1CE919E6B659A1F3C8C307E7
となっています。

このようになっていればOKです!!
ちなみにaddress.pretty()となっているところを少しいじるとこんな感じになります
account.addressの時
Your new account address is: Address {address: 'TDJNAYAZY7EJJIVQX7UVXFDR4F7PHLRWWJGUSBY', networkType: 152}address: "TDJNAYAZY7EJJIVQX7UVXFDR4F7PHLRWWJGUSBY"networkType: 152[[Prototype]]: Object and its private key A51C696BE2102A36F766222C8B5305AD4EA52C9FD325DDCECE0A2C0D7326B7B2
account.networkTypeの時
Your new account address is: 152 and its private key 651230EBAA228E9A1C306F2DECD04C16483B049E6245B5BF3F703189351FE676
まぁ色々機能があるんで試してみてください!!

秘密鍵から生成する方法
さてアカウントを生成する方法はわかったので次は既存のアカウントを生成する方法です。
symbolブロックチェーンではアカウントの生成には秘密鍵を使用することができます。
イメージとしては他のサービスで生成したアカウントの秘密鍵を自分が作成しているアプリケーションに「復元」するイメージの方がいいかなと思っています。

それでは実装していきましょう。
この手で僕はどのような手順で実装していくのかと言いますと。
まずは
- デスクトップウォレットでアカウントを作成
- デスクトップウォレットから秘密鍵を取得
- Reactのアプリケーションで復元できるか確認
- useStateを使用して秘密鍵の文字列を入力してから復元できるか確認
この4つのタスクを処理できれば秘密鍵からアカウントを生成する方法は身につくはずです。
ではやってみましょう。

はい、こちらデスクトップウォレットです。
デスクトップウォレットからアカウントを作成します。
今回はシードアカウント1というアカウントを使用します。

そこでPrivate Keyとなっているところのshowを選択して
パスワードを入力します。
そうすると秘密鍵が表示されるのでコピーをしましょう。
さてさて、これで秘密鍵は取得できました。
次は秘密鍵からアカウントを復元する必要があります。
それでは作っていきましょう。
秘密鍵からアカウントを復元するには2つの材料が必要です。
- 秘密鍵
- ネットワークタイプ
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      "7B20E0615755D6EEDA0DAB45E5D8A4331EC603F8702D7F4E6171FB81CF83CF78",
      NetworkType.TEST_NET
    )
    console.log(
      'Your account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
関数を作成したので、これをボタンで呼び出しましょう。
return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <button onClick={accountCreate}>アカウントの作成</button>
        <button onClick={accountCreateFromPrivateKey}>秘密鍵からアカウントを作成する</button> {/* ここが追加されるよ!! */}
      </header>
    </div>
  )
さてさて、動作確認をしましょう。
できてますね。

アカウントの文字列が合っているか確認します。

合ってますね。
これで秘密鍵からアカウントを生成することができました!!
さてさてそれでは二つをコンポーネント化します。
理由としては次の作業にとても邪魔になるからです。(個人差はありますので、別に気にならない人はそれでいいかと思います)
さて、コンポーネント化ですが、さっきも言ったように
ただの整理整頓です。
今はアタッシュケースの中に乱雑に入っている感じですが、それを靴下の場所、パンツの場所、シャツの場所
みたいに整理することです。

まぁ整理するとそら取り出しやすいよね。
みたいな感じです。
ただ、これはあくまでも個人単位で実施した方がいいです。
整理整頓と一つにしても前職の障がい支援では自閉スペクトラム症の方での整理整頓のルールが異なっていたので
まずは「個人単位」です。
さて、それではこの画像のようにコンポーネント化するためのディレクトリ(フォルダ)とファイルを作成しましょう。
ターミナルでsrcの下の階層にcomponentという名前のディレクトリを作成します。
react_symbol_typescript$ mkdir src/component
次にファイルを作成します。
先ほど作成したcomponentのディレクトリの階層の下にファイルを二つ作成します。
名前はGenerateNewAccount.tsxとCreateFromPrivateKey.tsxの二つです。
react_symbol_typescript$ touch src/component/GenerateNewAccount.tsx
react_symbol_typescript$ touch src/component/CreateFromPrivateKey.tsx


import React from 'react'
const CreateFromPrivateKey = () => {
  return (
    <div>
    </div>
  )
}
export default CreateFromPrivateKey
コンポーネントの初期状態を自動で記載してくれます。(これ結構僕気に入っています。)
さて、移植しましょう。
import React from 'react'
import { Account, NetworkType } from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      '7B20E0615755D6EEDA0DAB45E5D8A4331EC603F8702D7F4E6171FB81CF83CF78',
      NetworkType.TEST_NET
    )
    console.log(
      'Your account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
  return (
    <div>
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>{' '}
      {/* ここが追加されるよ!! */}
    </div>
  )
}
export default CreateFromPrivateKey
import React from 'react'
import { Account, NetworkType } from 'symbol-sdk'
const GenerateNewAccount = () => {
  const accountCreate = () => {
    const account = Account.generateNewAccount(NetworkType.TEST_NET)
    console.log(
      'Your new account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
  return (
    <div>
      <button onClick={accountCreate}>アカウントの作成</button>
    </div>
  )
}
export default GenerateNewAccount
import logo from './logo.svg'
import './App.css'
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
  )
}
export default App
さてこれで移植ができましたのであとはsrc/App.tsxで呼び出す必要があります。
import logo from './logo.svg'
import './App.css'
import CreateFromPrivateKey from './component/CreateFromPrivateKey'
import GenerateNewAccount from './component/GenerateNewAccount'
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <CreateFromPrivateKey></CreateFromPrivateKey>
        <GenerateNewAccount></GenerateNewAccount>
      </header>
    </div>
  )
}
export default App
先ほどの違いとして、秘密鍵からアカウントを生成する方法とアカウントを新しく生成する方法の順番を入れ替えています。

それぞれのボタンを押して動くか確認してくださいね!
useState
さて、いい感じにReactにも慣れてきたと思います。
(えっ?慣れていない?そういう時は二郎でも食べにいきましょう!!)
次は秘密鍵を入力するとそのアカウントが作成される部分を作りたいと思います。
先ほど作った秘密鍵の生成のところは毎回文字列をアプリケーションに設定してから生成しました。
それだと・・・
「使い勝手が悪い。」
となったりします(要件次第)
なので皆さんの秘密鍵を入力してそれぞれのアカウントを生成できるようにします。
ワクワクしてきましたね。
そこで使用するのがuseStateというReactの機能です。色々とややこしい説明は省きます、
ここで覚えて欲しいのは
「useStateを使用すれば変数とその内容を変更することができる」です。
今回は秘密鍵の文字列を入力してその秘密鍵を参照して関数を実行するという処理が必要です。

この2つを簡単にしてくれるのがuseStateです。
さてではuseStateを使ってみましょう。
const [privateKey, setPrivateKey] = useState("");
この左のprivateKeyが変数宣言です。
その横のsetPrivateKeyが入れ替える関数です。
右のuseState()でuseStateを使用します。
そのカッコの中で箱の形を宣言できます。
なのでこれで試しに関数とボタンを作成してみましょう。
import React, { useState } from 'react'
import { Account, NetworkType } from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const [privateKey, setPrivateKey] = useState('')
  console.log(privateKey)
  const sampleUseState = () => {
    setPrivateKey(
      '7B20E0615755D6EEDA0DAB45E5D8A4331EC603F8702D7F4E6171FB81CF83CF78'
    )
  }
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      '7B20E0615755D6EEDA0DAB45E5D8A4331EC603F8702D7F4E6171FB81CF83CF78',
      NetworkType.TEST_NET
    )
    console.log(
      'Your account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
  return (
    <div>
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      <br />
      <button onClick={sampleUseState}>useStateを試してみる</button>
    </div>
  )
}
export default CreateFromPrivateKey
このsampleUseStateという関数を作成しました。
これで実行してみます。
最初の状態では秘密鍵の中はからですが、

ボタンを押した後には値が表示されています。

こんな感じでいい具合に実施します。
というわけでこのsetStateを実施した後にアカウントが生成される処理を作ります。
次はuseStateを使った変数のprivateKeyという変数を使ってアカウントを生成してみます。
秘密鍵の文字列のところにprivateと入力します。
import React, { useState } from 'react'
import { Account, NetworkType } from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const [privateKey, setPrivateKey] = useState('')
  console.log('秘密鍵', privateKey)
  const sampleUseState = () => {
    setPrivateKey(
      '7B20E0615755D6EEDA0DAB45E5D8A4331EC603F8702D7F4E6171FB81CF83CF78'
    )
  }
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    console.log(
      'Your account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
  return (
    <div>
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      <br />
      <button onClick={sampleUseState}>useStateを試してみる</button>
    </div>
  )
}
export default CreateFromPrivateKey
できた!!

流れはuseStateのボタンを押して秘密鍵からのボタンを押します。
入力エリアを設定
というわけでuseStateの部分も理解ができたので次は入力エリアの設定をします。
まずは入力エリアを作成します。
  return (
    <div>
      <input className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" />
      <br />
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      <br />
      <button onClick={sampleUseState}>useStateを試してみる</button>
    </div>
  )
ここでtailwindcssの紹介
Tailwindcssはクラスに指定する感じでいい感じのデザインにしてくれるいい感じのものです。
最初の環境構築の時に設定したものがいい感じに効果を発揮してくれます。
なのでこのサイトへ訪れていい感じのデザインを使ってしまいましょう!!
現時点ではこのような形になります。

さて入力エリアはできたので、次は入力した値を反映させる必要があります。
手順は以下の流れです。
- 入力値を渡せるようにする
- 入力値をconsole.logで確認する
- 実際の秘密鍵を入力してアカウントを作成してみる
では確認していきましょう。
まず実施したいことは入力した値を変数に格納します。
先ほど作成したuseStateを使用したprivateKeyに格納します。
const [privateKey, setPrivateKey] = useState('')
privateKeyの中に新たに値を更新したい時はsetPrivateKeyを使用します
onChangeのプロパティを実行して
(e)の中には更新した内容が入っています。
その中でe.target.valueが入力した内容になっています。
<input
  onChange={(e) => setPrivateKey(e.target.value)}
  className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" />

さてコンソールのところですが、秘密鍵の後には空白が入っています。
ではこの中にtestと入力してみます。

その時のコンソールを見てみます。

入力した文字が一文字ずつ更新されています
こういった形でいい具合にprivateKeyに入力した文字を格納することができました。
最後に秘密鍵を入力してみましょう。
できました!!

さて進捗を確認しましょう。
現時点でアドレスの生成は完了しました!!(おめでとう!!)
パブリックキーの取得
まずは不要なコンポーネントを非表示にします。
コメントアウトしているところは削除してもらって大丈夫です!!
import logo from './logo.svg'
import './App.css'
import CreateFromPrivateKey from './component/CreateFromPrivateKey'
// import GenerateNewAccount from './component/GenerateNewAccount'
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <CreateFromPrivateKey></CreateFromPrivateKey>
        {/* <GenerateNewAccount></GenerateNewAccount> */}
      </header>
    </div>
  )
}
export default App
import React, { useState } from 'react'
import { Account, NetworkType } from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const [privateKey, setPrivateKey] = useState('')
  console.log('秘密鍵', privateKey)
  // const sampleUseState = () => {
  //   setPrivateKey(
  //     '7B20E0615755D6EEDA0DAB45E5D8A4331EC603F8702D7F4E6171FB81CF83CF78'
  //   )
  // }
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    console.log(
      'Your account address is:',
      account.address.pretty(),
      'and its private key',
      account.privateKey
    )
  }
  return (
    <div>
      <input
      onChange={(e) => setPrivateKey(e.target.value)}
      className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" />
      <br />
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      {/* <br />
      <button onClick={sampleUseState}>useStateを試してみる</button> */}
    </div>
  )
}
export default CreateFromPrivateKey
さて、続きです。
今回はアカウントと公開鍵を取得します。
以下手順です。
- アドレスの取得方法を確認する
- アドレスを取得する
- 公開鍵を取得する方法を確認する
- 公開鍵を取得する
- アプリケーションに表示する
さて実施しましょう。
まずはアドレスの取得方法です。
アドレスは前回のところで実施しました、秘密鍵の生成から
account.address.pretty()で取得できたことを覚えていますか?

これでアドレスは取得できました。
次に公開鍵を取得します
公開鍵の取得方法は秘密鍵から生成した時に
account.publicKey
で取得できます。
const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    console.log(
      'アドレス',
      account.address.pretty(),
      '公開鍵',
      account.publicKey,
      '秘密鍵',
      account.privateKey
    )
  }

さて、これでアドレスと公開鍵が取得できましたのであとはアプリケーションに表示します。
ここで先ほど使用したuseStateを使用します。
import React, { useState } from 'react'
import { Account, NetworkType } from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const [privateKey, setPrivateKey] = useState('')
  const [address, setAddress] = useState('')
  const [publicKey, setPublicKey] = useState('')
  console.log('秘密鍵', privateKey)
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    setAddress(account.address.pretty())
    setPublicKey(account.publicKey)
  }
  return (
    <div>
      <input
        onChange={(e) => setPrivateKey(e.target.value)}
        className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
      />
      <br />
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      <p>アドレス: {address}</p>
      <p>公開鍵: {publicKey}</p>
    </div>
  )
}
export default CreateFromPrivateKey
まずは箱を設定するuseStateを二つ用意します。
  const [address, setAddress] = useState('')
  const [publicKey, setPublicKey] = useState('')
アカウントを作成するアドレスや公開鍵が取得できるので
取得した内容をsetAddressやsetPublicKeyを使用して
変数の中身を入れ替えます。
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    setAddress(account.address.pretty())
    setPublicKey(account.publicKey)
  }
あとはuseStateの変数を表示します。
変数を表示する場合は{}(カーリーブラケットもしくは波括弧)を使用します。
  <button onClick={accountCreateFromPrivateKey}>
    秘密鍵からアカウントを作成する
  </button>
  <p>アドレス: {address}</p>
  <p>公開鍵: {publicKey}</p>
こんな感じで表示できました。

保有モザイクとインポータンスの取得
さて、次は保有モザイクとインポータンスの取得です。
こういった情報はアカウント情報として全て格納されていますので(便利ですね)
なので手順としては
- アカウント情報の取得方法を確認する
- 実際に取得してみる
- 表示する
の3ステップになります。
頑張ってやっていきましょう!!
さてアカウントの取得方法はこちらです。
取得する方法を確認しましょう。
アカウント情報を取得するために必要な要素は合計で5つです。
- アドレス
- アカウント
- ノードURL
- リポジトリファクトリ
- アカウントHTTP
それでは一つずつ見ていきたいと思います。
import React, { useState } from 'react'
import {
  Account,
  NetworkType,
  Address, // 追加
  RepositoryFactoryHttp, // 追加
} from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const [privateKey, setPrivateKey] = useState('')
  const [address, setAddress] = useState('')
  const [publicKey, setPublicKey] = useState('')
  console.log('秘密鍵', privateKey)
// ここから追加
  const accountInfo = () => {
    const accountAddress = Address.createFromRawAddress(address)
    console.log("アカウントアドレス",accountAddress)
    const nodeUrl = 'http://ngl-dual-101.testnet.symboldev.network:3000'
    console.log("ノードURL",nodeUrl)
    const repositoryFactory = new RepositoryFactoryHttp(nodeUrl)
    console.log("リポジトリファクトリ",repositoryFactory)
    const accountHttp = repositoryFactory.createAccountRepository()
    console.log("アカウントHttp",accountHttp)
    accountHttp.getAccountInfo(accountAddress).subscribe(
      (accountInfo) => console.log(accountInfo),
      (err) => console.error(err),
    );
  }
// ここまで追加
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    setAddress(account.address.pretty())
    setPublicKey(account.publicKey)
  }
  return (
    <div>
      <input
        onChange={(e) => setPrivateKey(e.target.value)}
        className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
      />
      <br />
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      <p>アドレス: {address}</p>
      <p>公開鍵: {publicKey}</p>
      {/* ここを追加 */}
      <button onClick={accountInfo}>アカウント情報を取得する</button>
      {/* ここまでを追加 */}
    </div>
  )
}
export default CreateFromPrivateKey
はい、では見ていきます。
まずはアカウントアドレスです。
このアカウントアドレスのコンソールを確認すると

オブジェクトの型で以下のようなデータになっています。
{
    "address": "TBUKFL3BMEXYBDQYBV5Y7UOWNRM3TDRZ4PNFCZQ",
    "networkType": 152
}
このアドレスとネットワーク情報があるオブジェクトが今後必要になるということです。
なのでこの部分はもし知っている場合や固定する場合はわざわざcreateFromRawAddressを呼び出す必要もなくなります。
次にノードURLです。
これはURLをそのまま打ち込んだだけですので特に問題ないかと思います。

次にリポジトリファクトリです
ノードのURLから新しくリポジトリファクトリを作成しています。
中身は僕もそんなによくわかっていませんので、下手に解説しません。
こんな感じのものが必要なんだなと思っておいてください。(僕もわかりません)
{
    "url": "http://ngl-dual-101.testnet.symboldev.network:3000",
    "networkType": {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        }
    },
    "networkProperties": {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        }
    },
    "epochAdjustment": {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        }
    },
    "generationHash": {
        "_isScalar": false
    },
    "nodePublicKey": {
        "_isScalar": false
    },
    "websocketUrl": "http://ngl-dual-101.testnet.symboldev.network:3000/ws",
    "networkCurrencies": {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        }
    }
}
そしてアカウントHTTPです。
ここも少々お待ちください。なぜこれを使うのかわかっていません。
とにかく今わかっているのは、これが必要なのでコピペしているということです。
{
    "url": "http://ngl-dual-101.testnet.symboldev.network:3000",
    "accountRoutesApi": {
        "configuration": {
            "configuration": {
                "basePath": "http://ngl-dual-101.testnet.symboldev.network:3000"
            }
        },
        "middleware": []
    }
}
さてアカウント情報を取得するというボタンを押した方は
以下の情報が取得できているはずです。
この中に保有モザイクとインポータンスが存在します。
{
    "version": 1,
    "recordId": "6179331BD42C4799D0EBB887",
    "address": {
        "address": "TBA6Z5KQ772LGYDJ2RC72PPV4HHBLPDKNQHWL5A",
        "networkType": 152
    },
    "addressHeight": {
        "lower": 506108,
        "higher": 0
    },
    "publicKey": "595D63553130DD117EFA7583F6735FBF6C7A9020BBFCFBDFEC20163BA2D479CA",
    "publicKeyHeight": {
        "lower": 506110,
        "higher": 0
    },
    "accountType": 0,
    "supplementalPublicKeys": {},
    "activityBucket": [],
    // モザイク
    "mosaics": [
        {
            "id": {
                "id": {
                    "lower": 94036284,
                    "higher": 153060222
                }
            },
            "amount": {
                "lower": 47938864,
                "higher": 0
            }
        }
    ],
    // インポータンス
    "importance": {
        "lower": 0,
        "higher": 0
    },
    "importanceHeight": {
        "lower": 0,
        "higher": 0
    }
}
さて保有モザイクとインポータンスの居場所もわかったのであとはそれを表示しましょう。
import React, { useState } from 'react'
import {
  Account,
  NetworkType,
  Address,
  RepositoryFactoryHttp,
  Mosaic,
} from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const [privateKey, setPrivateKey] = useState('')
  const [address, setAddress] = useState('')
  const [publicKey, setPublicKey] = useState('')
  const [mosaics, setMosaics] = useState<Mosaic[]>([])
  const [importance, setImportance] = useState({
    lower: 0,
    higher: 0,
  })
  console.log(mosaics)
  const accountInfo = () => {
    const accountAddress = Address.createFromRawAddress(address)
    const nodeUrl = 'http://ngl-dual-101.testnet.symboldev.network:3000'
    const repositoryFactory = new RepositoryFactoryHttp(nodeUrl)
    const accountHttp = repositoryFactory.createAccountRepository()
    accountHttp.getAccountInfo(accountAddress).subscribe(
      (accountInfo) => {
        console.log(accountInfo)
        setMosaics(accountInfo.mosaics)
        setImportance(accountInfo.importance)
      },
      (err) => console.error(err)
    )
  }
  const accountCreateFromPrivateKey = () => {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    setAddress(account.address.pretty())
    setPublicKey(account.publicKey)
  }
  return (
    <div>
      <input
        onChange={(e) => setPrivateKey(e.target.value)}
        className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
      />
      <br />
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      <p>アドレス: {address}</p>
      <p>公開鍵: {publicKey}</p>
      <button onClick={accountInfo}>アカウント情報を取得する</button>
      {mosaics[0] && importance && (
        <>
          <p>モザイク総量{mosaics[0].amount.higher}</p>
          <p>モザイク総量{mosaics[0].amount.lower}</p>
          <p>モザイクID{mosaics[0].id.id.lower}</p>
          <p>モザイクID{mosaics[0].id.id.higher}</p>
          <p>インポータンス{importance.lower}</p>
          <p>インポータンス{importance.higher}</p>
        </>
      )}
    </div>
  )
}
export default CreateFromPrivateKey
  const mosaicList = () => {
    const items = []
    for (let i = 0; i < mosaics.length; i++) {
      items.push(
        <li key={mosaics[i].id.id.higher}>
          モザイク総量{mosaics[i].amount.higher}<br/>
          モザイク総量{mosaics[i].amount.lower}<br/>
          モザイクID{mosaics[i].id.id.lower}<br/>
          モザイクID{mosaics[i].id.id.higher}
        </li>
      )
    }
    return <ul>{items}</ul>
  }
  {mosaics && importance && (
        <>
          {mosaicList()}
          <p>インポータンス{importance.lower}</p>
          <p>インポータンス{importance.higher}</p>
        </>
      )}
ちょっとここの解説が頼りないので
リポジトリファクトリはSymbolのAPIに繋ぐためのクライアントを作るためのクラスです。
コンストラクタ引数にAPIのエンドポイントの設定するので、これを使うことでAPIに接続するための設定を共通化できます
AccountHttpはアカウントの情報を取得するエンドポイントに接続するためのクライアントです
この辺りはREST APIの知識にも繋がってくるので一度学習されることをお勧めします。
必要な情報をまとめる
lowerやhigherなどが出てきたのでその情報を加工できるようにしましょう。
モザイクはtoHex()でまとめる
インポータンスはstringで表示する
モザイクの総量はstringで表示する
const mosaicList = () => {
  const items = []
  for (let i = 0; i < mosaics.length; i++) {
    items.push(
      <li key={mosaics[i].id.id.lower}>
        モザイクID: {mosaics[i].id.id.toHex()}------モザイクの総量: {mosaics[i].amount.toString()}
      </li>
    )
  }
  return <ul>{items}</ul>
}
// -----中略--------
{mosaics && importance && (
  <>
    {mosaicList()}
    <p>インポータンス: {importance.toString()}</p>
  </>
)}
useStateを使えるようにする
import React, { useState, useEffect, useCallback } from 'react'
  const accountInfo = useCallback(() => {
    const accountAddress = Address.createFromRawAddress(address)
    const nodeUrl = 'http://ngl-dual-101.testnet.symboldev.network:3000'
    const repositoryFactory = new RepositoryFactoryHttp(nodeUrl)
    const accountHttp = repositoryFactory.createAccountRepository()
    accountHttp.getAccountInfo(accountAddress).subscribe(
      (accountInfo) => {
        console.log(accountInfo)
        setPublicKey(accountInfo.publicKey)
        setMosaics(accountInfo.mosaics)
        setImportance(accountInfo.importance)
      },
      (err) => console.error(err)
    )
  }, [address])
  useEffect(() => {
    accountInfo()
  }, [address, accountInfo])
使い方としてはある変数が変更になったときに動きましょう!!
といった感じです。
useEffectの中でaccountInfoを呼び出しているのでaccountInfoをuseCallbackで
囲ってあげます。
そうすると警告は消えます。
ここまでのソースコードになります
import React, { useState, useEffect, useCallback } from 'react'
import {
  Account,
  NetworkType,
  Address,
  RepositoryFactoryHttp,
  Mosaic,
} from 'symbol-sdk'
const CreateFromPrivateKey = () => {
  const [privateKey, setPrivateKey] = useState('')
  const [address, setAddress] = useState(
    'TCUKQQFP6XTIIA2WLHUUGHVFPE62OIMGWUP7SHY' // ここに初期アドレスを設定しておいてください。
  )
  const [publicKey, setPublicKey] = useState('')
  const [mosaics, setMosaics] = useState<Mosaic[]>([])
  const [importance, setImportance] = useState({
    lower: 0,
    higher: 0,
  })
  const mosaicList = () => {
    const items = []
    for (let i = 0; i < mosaics.length; i++) {
      items.push(
        <li key={mosaics[i].id.id.lower}>
          モザイクID: {mosaics[i].id.id.toHex()}------モザイクの総量: {mosaics[i].amount.toString()}
        </li>
      )
    }
    return <ul>{items}</ul>
  }
  const accountInfo = useCallback(() => {
    const accountAddress = Address.createFromRawAddress(address)
    // ちょっとノードをhttpsに対応しているものにしています。
    const nodeUrl = 'https://sym-test.opening-line.jp:3001/'
    const repositoryFactory = new RepositoryFactoryHttp(nodeUrl)
    const accountHttp = repositoryFactory.createAccountRepository()
    accountHttp.getAccountInfo(accountAddress).subscribe(
      (accountInfo) => {
        console.log(accountInfo)
        setPublicKey(accountInfo.publicKey)
        setMosaics(accountInfo.mosaics)
        setImportance(accountInfo.importance)
      },
      (err) => console.error(err)
    )
  }, [address])
  useEffect(() => {
    accountInfo()
  }, [address, accountInfo])
  function accountCreateFromPrivateKey() {
    const account = Account.createFromPrivateKey(
      privateKey,
      NetworkType.TEST_NET
    )
    setAddress(account.address.pretty())
    setPublicKey(account.publicKey)
  }
  return (
    <div>
      <input
        onChange={(e) => setPrivateKey(e.target.value)}
        className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
      />
      <br />
      <button onClick={accountCreateFromPrivateKey}>
        秘密鍵からアカウントを作成する
      </button>
      <p>アドレス: {address}</p>
      <p>公開鍵: {publicKey}</p>
      {mosaics && importance && (
        <>
          {mosaicList()}
          <p>インポータンス: {importance.toString()}</p>
        </>
      )}
    </div>
  )
}
export default CreateFromPrivateKey
これで秘密鍵を入力すると
アカウントが作成されて色々情報も自動的に更新されるようになりました。
またuseEffectやuseCallbackなどは結構解説が大変なのですが、
Udemyなどでいい教材たくさんあるのでセール期間中に購入して実施されることをお勧めします。
デザイン編
さて、ここまでは機能の確認をしました。
次はデザインを確認していきます。
一つずつ作っていきます。
一つずつ作っていきましょう!!
デザイン
TailwindCSSでの心構え。
- 上手い人のを参考にする(初めは丸パクリでもOK)
- cssを書くのではなくclassNameで設定する
- Heroiconsはなぜかセットで使われることが多いからその構成も真似してしまえ!!
です。
これは僕は誰かに教わったと言うより、こう言う風にしています。
正直なところ今までやってきた活動でデザインを求められることは多いですが、
まずは機能検証をしたい。
とにかくSymbolを使ってみたい
といった方も多いかと思います。
下手しい最初はソースコードコピペでいいんかなとは思います。
今はできないけど、色々と作っていくとちょっとずつできるようになってくるのではないかなぁと思っています。
ホームコンポーネントの実装
htmlタグでしか記載していませんので、特に解説は致しませんが、
重要なポイントとしてアイコンを使用するときは
heroiconsのjsxをコピーしてくるだけでOKといった感じです。
<svg
  xmlns="http://www.w3.org/2000/svg"
  className="h-6 w-6"
  fill="none"
  viewBox="0 0 24 24"
  stroke="currentColor"
>
  <path
    strokeLinecap="round"
    strokeLinejoin="round"
    strokeWidth={2}
    d="M20.618 5.984A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016zM12 9v2m0 4h.01"
  />
</svg>
import React from 'react'
import Github from '../images/logo-github.png'
import Symbol from '../images/logo-symbol-color.png'
import NumTus from '../images/logo-nemtus-color.png'
import Twitter from '../images/logo-twitter-color.png'
const Home = () => {
  return (
    <>
      <div className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline">
        Home
      </div>
      <div className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline">
        <div className="bg-grey-light hover:bg-grey text-grey-darkest font-bold py-2 px-2 rounded inline-flex items-center">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            className="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M20.618 5.984A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016zM12 9v2m0 4h.01"
            />
          </svg>
          <span className="ml-6">Note</span>
        </div>
        <p>
          ・ブロックチェーンSymbolとSPAフレームワークReactを用いて作成しているWebアプリケーションのサンプルです。
        </p>
      </div>
      <div className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline">
        <div className="bg-grey-light hover:bg-grey text-grey-darkest font-bold py-2 px-2 rounded inline-flex items-center">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            className="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M20.618 5.984A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016zM12 9v2m0 4h.01"
            />
          </svg>
          <span className="ml-6">Disclaimer</span>
        </div>
        <p>
          ・本サイトのご利用に際しては以下の点にご注意ください。
          <br />
          ・本サイトではSymbolを用いたWebアプリケーションの実装例を示すことに主眼をおいています。
          <br />
          ・本サイトでの実装は十分にテストされていない可能性があることを踏まえ、本サイトのご利用は、実装例を参考にしたり少額のXYMでの動作確認にとどめ、多額のXYMやクリティカルな情報を扱う操作を実行なさならいことを推奨します。
          <br />
          ・本サイトのご利用を通じて生じ得るいかなる問題に対してもNEMTUSとして一切の責任を取ることはできませんのでご注意ください。
          <br />
        </p>
      </div>
      <div className="shadow rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline">
        <div className="bg-grey-light hover:bg-grey text-grey-darkest font-bold py-2 px-2 rounded inline-flex items-center">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            className="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M20.618 5.984A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016zM12 9v2m0 4h.01"
            />
          </svg>
          <span className="ml-6">Reference</span>
        </div>
        <a
          href="https://github.com/nemtus/symbol-sample-react"
          target="_blank"
          rel="noopener noreferrer"
        >
          <button className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 mt-2 rounded w-full flex flex-grow items-center px-3">
            <img src={Github} alt="github" />
            <span className="ml-6">リポジトリ</span>
          </button>
        </a>
        <a
          href="https://docs.symbolplatform.com/ja/"
          target="_blank"
          rel="noopener noreferrer"
        >
          <button className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded w-full flex flex-grow items-center px-3 mt-2">
            <img src={Symbol} alt="github" />
            <span className="ml-6">Symbolドキュメント</span>
          </button>
        </a>
        <a
          href="https://nemtus.com/"
          target="_blank"
          rel="noopener noreferrer"
        >
          <button className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded w-full flex flex-grow items-center px-3 mt-2">
            <img src={NumTus} alt="github" className="w-6" />
            <span className="ml-6">nemtus</span>
          </button>
        </a>
        <a
          href="https://twitter.com/NemtusOfficial"
          target="_blank"
          rel="noopener noreferrer"
        >
          <button className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded w-full flex flex-grow items-center px-3 mt-2">
            <img src={Twitter} alt="github" />
            <span className="ml-6">Twitter</span>
          </button>
        </a>
      </div>
    </>
  )
}
export default Home
サイドバーの実装
ここで重要なポイントはアカウントページとホームページをどう切り換えるかです。
useStateでisHomeという変数を持たせてそれがtrueならこのページを表示
falseならこのページを表示みたいな切り分けをしています。
今回はページルーターを実装するのが手間だったのと、表示するコンポーネントを切り替えるだけ
だったのでこのような実装にしています。
まずは動かしたい内容を決めてどのようにするかがポイントです。
ページの内容を変更したい場合はルータだけでなくこういう方法もあるということです。
const [isHome, setIsHome] = useState(false)
// trueの場合
{isHome && <CreateFromPrivateKey></CreateFromPrivateKey>}
// falseの場合
{!isHome && <Home></Home>}
サイドバーの非表示、表示の切り替えも同様です。
isClosedをfalseの状態で非表示
trueの状態で表示となっています。
const [isClosed, setClosed] = useState(false)
{!isClosed && (
<aside
  aria-hidden={isClosed}
  className="bg-gray-800 w-64 min-h-screen flex flex-col text-white"
>
  <div className="border-r border-b px-4 h-10 flex items-center justify-between">
    <span className="text-blue py-2">Symbol-React</span>
  </div>
  <div className="border-r py-4 flex-grow relative">
    <nav>
      <ul className="mt-12">
        <li className="flex w-full justify-between text-white hover:text-gray-500 cursor-pointer items-center mb-6 m-4">
          <button className="flex items-center" onClick={() => {setIsHome(false)}}>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              className="h-6 w-6"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
              />
            </svg>
            <span className="text-sm  ml-2">Home</span>
          </button>
        </li>
        <li className="flex w-full justify-between text-white hover:text-gray-500 cursor-pointer items-center  m-4">
          <button className="flex items-center" onClick={() => {setIsHome(true)}}>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              className="h-6 w-6"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"
              />
            </svg>
            <span className="text-sm  ml-2">Account</span>
          </button>
        </li>
      </ul>
    </nav>
  </div>
</aside>
)}
ヘッダーの部分のボタンの切り替えなどは三項演算子を使用しています。
Reactを触っていたり、上手い人の実装を真似しているとこの実装が多いので参考にしています
<header className="bg-white border-b h-10 flex items-center justify-center">
  {isClosed ? (
    <button
      tabIndex={1}
      className="w-10 p-1"
      aria-label="Open menu"
      title="Open menu"
      onClick={() => setClosed(false)}
    >
      <svg
        aria-hidden="true"
        fill="none"
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth="2"
        viewBox="0 0 24 24"
        stroke="currentColor"
      >
        <path d="M4 6h16M4 12h16M4 18h16"></path>
      </svg>
    </button>
  ) : (
    <button
      tabIndex={1}
      className="w-10 p-1"
      aria-label="Close menu"
      title="Close menu"
      onClick={() => setClosed(true)}
    >
      <svg
        fill="none"
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth="2"
        viewBox="0 0 24 24"
        stroke="currentColor"
      >
        <path d="M6 18L18 6M6 6l12 12"></path>
      </svg>
    </button>
  )}
  <div className="flex flex-grow items-center px-3">
    <h1 className="text-lg col-start-1 col-end-3">{!isHome ? "Home" : "Account"}</h1>
    <div className="flex-grow"></div>
    <a
      href="https://docs.symbolplatform.com/ja/"
      target="_blank"
      rel="noopener noreferrer"
    >
      <img src={Symbol} alt="symbol" className="col-end-7 col-span-2" />
    </a>
    <a
      href="https://nemtus.com/"
      target="_blank"
      rel="noopener noreferrer"
    >
      <img src={NemTus} alt="nemtus" className="w-6 m-2" />
    </a>
  </div>
</header>
以下全文です。
import React, { useState } from 'react'
import CreateFromPrivateKey from './CreateFromPrivateKey'
import Symbol from '../images/logo-symbol-color.png'
import NemTus from '../images/logo-nemtus-color.png'
import Home from './Home'
const SideBar = () => {
  const [isClosed, setClosed] = useState(false)
  const [isHome, setIsHome] = useState(false)
  return (
    <div className="flex bg-gray-100 w-full">
      {!isClosed && (
        <aside
          aria-hidden={isClosed}
          className="bg-gray-800 w-64 min-h-screen flex flex-col text-white"
        >
          <div className="border-r border-b px-4 h-10 flex items-center justify-between">
            <span className="text-blue py-2">Symbol-React</span>
          </div>
          <div className="border-r py-4 flex-grow relative">
            <nav>
              <ul className="mt-12">
                <li className="flex w-full justify-between text-white hover:text-gray-500 cursor-pointer items-center mb-6 m-4">
                  <button className="flex items-center" onClick={() => {setIsHome(false)}}>
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
                      />
                    </svg>
                    <span className="text-sm  ml-2">Home</span>
                  </button>
                </li>
                <li className="flex w-full justify-between text-white hover:text-gray-500 cursor-pointer items-center  m-4">
                  <button className="flex items-center" onClick={() => {setIsHome(true)}}>
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"
                      />
                    </svg>
                    <span className="text-sm  ml-2">Account</span>
                  </button>
                </li>
              </ul>
            </nav>
          </div>
        </aside>
      )}
      <main className="flex-grow flex flex-col min-h-screen w-full">
        <header className="bg-white border-b h-10 flex items-center justify-center">
          {isClosed ? (
            <button
              tabIndex={1}
              className="w-10 p-1"
              aria-label="Open menu"
              title="Open menu"
              onClick={() => setClosed(false)}
            >
              <svg
                aria-hidden="true"
                fill="none"
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path d="M4 6h16M4 12h16M4 18h16"></path>
              </svg>
            </button>
          ) : (
            <button
              tabIndex={1}
              className="w-10 p-1"
              aria-label="Close menu"
              title="Close menu"
              onClick={() => setClosed(true)}
            >
              <svg
                fill="none"
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path d="M6 18L18 6M6 6l12 12"></path>
              </svg>
            </button>
          )}
          <div className="flex flex-grow items-center px-3">
            <h1 className="text-lg col-start-1 col-end-3">{!isHome ? "Home" : "Account"}</h1>
            <div className="flex-grow"></div>
            <a
              href="https://docs.symbolplatform.com/ja/"
              target="_blank"
              rel="noopener noreferrer"
            >
              <img src={Symbol} alt="symbol" className="col-end-7 col-span-2" />
            </a>
            <a
              href="https://nemtus.com/"
              target="_blank"
              rel="noopener noreferrer"
            >
              <img src={NemTus} alt="nemtus" className="w-6 m-2" />
            </a>
          </div>
        </header>
        {isHome && <CreateFromPrivateKey></CreateFromPrivateKey>}
        {!isHome && <Home></Home>}
      </main>
    </div>
  )
}
export default SideBar
実装イメージとして
以下のイメージ図を参考にしていただけますと幸いです。

まとめ
お疲れ様でした。
色々いっぱい解説したので、まずはポイントを
- 環境構築は面倒なので休憩しながらやりましょう。(慣れるまでの辛抱です)
- 実際にアプリケーションを作るとSymbolの実装は結構少ないです(だからなんか寂しいんです)
- どちらかというとReactやTailwindCSSの方が面倒です(慣れるまでの辛抱です)
- 本人のペースを崩さす勉強していってください(他人のマウントを相手している時間はあなたにはありません。)
となります。
いや〜疲れましたね。ここまでやってちょっとわかった!!となってもらえれば嬉しいです。
結局のところReactやTypescriptの解説が多かった気がしますが、
要するにデータはsymbol-sdkで取得して、表示はReactでといったところです。
Reactの解説が多かったのはsymbol-sdkの解説がそこまで不要なほどに作りがいいと思っています。(なのでちょっと寂しいのですが)
解説量が多い=いい解説
ではなく、
この記事を読んでアプリを作ってみる人が増える=いい解説
と思っていますので、ぜひとも何かしら作ってみるチャレンジをしていただけますと幸いです。
最初はコピペでもいいんです。とにかく楽しんでもらえると幸いです。
疲れた時は、音楽でも聞いてリラックスしましょう。
最後に
もしNEMTUSに対しNEMやSymbol関連記事の寄稿や、サンプルとして公開したアプリについて何かありましたら、以下GitHubにて記事やサンプルアプリを公開しておりますので、お気軽にIssueやPull Request等、連携くださいますと幸いです。
NEMTUSとして、NEM, Symbolに関する様々な技術情報を継続的に発信していきたいと考えていますので、今後ともどうぞよろしくお願いします。
記事作成者
- 名前
- 松本一将
 
- 所属
- 株式会社Opening Line
 
- 略歴
- 障がい者向けアプリケーションを作ろうとして、頑張った結果ブロックチェーンの企業で働いています。
 
- SNS
- twitter: https://twitter.com/kazumasamatsumo
 






Discussion
が発生した場合は、
で解決します。
react-scriptsのダウングレードです。
最初にアカウントを作った際→トークンを受け取ったことが無いアドレスは、アドレスの情報を参照するRest APIを叩いてもエラーになります。
モザイク情報を確認するには、faucetでテストtokenを取得する必要があります。 ここで、テストtokenを取得したのち
symbol-cliで進捗を確認
*hashのIDはmosaicを要求した際に、ブラウザに出ます。
そちらを確認後、アカウント情報を確認すると、
mosaic情報も入ってきます。