8
2

Nuxt.jsのv2でsymbol-sdk@3.1.0を使用してトランザクションを送信してみる

Last updated at Posted at 2024-01-04

はじめに

以前、symbol-sdk@3を使用してWebアプリを作ろうとした際に、Next.jsならうまくいくことがわかりました。このアプリでは、Webpackを使っているため、他のWebpackを使っているアプリならうまく動かせるんじゃないかと思いました。

そこで今回は、Webpackを使用しているNuxt.jsのv2でやってみようと思います。

手順

それではプロジェクトを作っていきます。プロジェクト名はnuxt2です。

$ npm init nuxt-app nuxt2

いろいろオプションを選択します。今回は以下のようにしました。

create-nuxt-app v5.0.0
✨  Generating Nuxt.js project in nuxt2
? Project name: nuxt2
? Programming language: JavaScript 
? Package manager: Npm
? UI framework: None
? Template engine: HTML
? Nuxt.js modules:
? Linting tools:
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Static (Static/Jamstack hosting)
? Development tools:
? What is your GitHub username? planethouki
? Version control system: None
| Installing packages with npm

必要パッケージをインストールします。

$ npm i symbol-sdk@3 symbol-crypto-wasm-web axios

この時点で、package.jsonのdependenciesは以下のようになりました。

{
  "dependencies": {
    "axios": "^1.6.2",
    "core-js": "^3.25.3",
    "nuxt": "^2.15.8",
    "symbol-crypto-wasm-web": "^0.1.1",
    "symbol-sdk": "^3.1.0",
    "vue": "^2.7.10",
    "vue-server-renderer": "^2.7.10",
    "vue-template-compiler": "^2.7.10"
  }
}

nuxt.config.jsを以下のように編集します。

nuxt.config.js
import webpack from 'webpack'
export default {
  build: {
    extend(config, ctx) {
      config.module.rules.push({
        test: /node_modules[\\/]symbol-sdk[\\/].*\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      });
    },
    plugins: [
      new webpack.NormalModuleReplacementPlugin(
        /symbol-crypto-wasm-node/,
        '../../../symbol-crypto-wasm-web/symbol_crypto_wasm.js'
      )
    ]
  }
}

ページを作成します。

pages/index.vue
<template>
  <div>
    <div>
      <span>秘密鍵:</span>
      <input name="privateKey" v-model="privateKey">
    </div>
    <div>
      <span>ノード</span>
      <input name="nodeUrl" v-model="nodeUrl">
    </div>
    <hr />
    <div>
      <button @click="handleSend">トランザクションを送信</button>
    </div>
    <div>送信メッセ―ジ:{{ sendMessage }}</div>
    <div>トランザクションハッシュ:{{ hash }}</div>
    <hr />
    <div>
      <button @click="handleStatus">トランザクションを確認</button>
    </div>
    <div>トランザクションステータス:{{ statusMessage }}</div>
  </div>
</template>

<script>
import symbolSdk from 'symbol-sdk'
import axios from 'axios'

export default {
  name: 'IndexPage',
  data() {
    return {
      privateKey: '',
      nodeUrl: '',
      sendMessage: '',
      statusMessage: '',
      hash: ''
    }
  },
  methods: {
    async handleSend() {
      this.sendMessage = '送信中…'
      this.hash = ''
      const network = symbolSdk.symbol.Network.TESTNET
      const facade = new symbolSdk.facade.SymbolFacade(network.name)
      const privateKey = new symbolSdk.PrivateKey(this.privateKey)
      const keyPair = new facade.constructor.KeyPair(privateKey)
      const textEncoder = new TextEncoder()
      const deadline = network.fromDatetime(new Date(Date.now() + 7200000)).timestamp
      const transaction = facade.transactionFactory.create({
        type: 'transfer_transaction_v1',
        signerPublicKey: keyPair.publicKey.toString(),
        fee: 1000000n,
        deadline,
        recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
        mosaics: [
          { mosaicId: 0x72C0212E67A08BCEn, amount: 1000000n },
        ],
        message: new Uint8Array([0x00, ...textEncoder.encode('Hello, World!')])
      })
      const signature = facade.signTransaction(keyPair, transaction)
      const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature)
      const hash = facade.hashTransaction(transaction).toString()
      const sendResult = await axios.put(`${this.nodeUrl}/transactions`, JSON.parse(jsonPayload)).then((res) => res.data);
      this.sendMessage = JSON.stringify(sendResult)
      this.hash = hash
    },
    async handleStatus() {
      this.statusMessage = '取得中…'
      const statusResult = await axios
        .get(`${this.nodeUrl}/transactionStatus/${this.hash}`)
        .then((res) => res.data)
        .catch((e) => e.message)
      this.statusMessage = JSON.stringify(statusResult)
    }
  }
}
</script>

では、起動してみましょう。

$ npm run dev

見た目はこのようになりました。

image.png

それではトランザクションを送信して、確認してみると、unconfirmedになりました。

image.png

しばらくして再度確認すると、無事confirmedになりました。

image.png

苦労の足跡

上記の方法にたどり着くまでに、いろんなエラーにぶちあたりました。そのたびに、たくさんググったりChatGPTに聞いたりしました。その過程も何かの役に立つと思うので載せておきます。

プロジェクト作成

まずはプロジェクトを作成します。

$ npm init nuxt-app hoge
$ cd hoge
$ npm run dev

nuxt-appのオプションについては上記で書いていたのと同じです。

次に、とりあえずsymbol-sdk@3だけインストールします。

$ npm i symbol-sdk@3

で、nuxt.config.jsにはいったん手を付けずに、ページを作成します。

トランザクションを送信するでもなく、秘密鍵から公開鍵を導出するだけの小さなページです。

pages/index.vue
<template>
  <div>
    {{ publicKey }}
  </div>
</template>

<script>
import symbolSdk from 'symbol-sdk'

export default {
  name: 'IndexPage',
  data() {
    const PRIVATE_KEY = '45461A042F439DAEE72BA235D953EA8457D1C9C11CBBE35E895882BEF44A72E9'
    const network = symbolSdk.symbol.Network.TESTNET
    const facade = new symbolSdk.facade.SymbolFacade(network.name)
    const privateKey = new symbolSdk.PrivateKey(PRIVATE_KEY)
    const keyPair = new facade.constructor.KeyPair(privateKey)

    return {
      publicKey: keyPair.publicKey.toString(),
    }
  }
}
</script>

すると画面はこのようになります。

image.png

目を覆ってしまいたくなりますが、ひとつひとつ見ていきます。

symbol-crypto-wasm-nodeエラー

まずは一番最初。

ERROR in ./node_modules/symbol-crypto-wasm-node/symbol_crypto_wasm.js
Module not found: Error: Can't resolve 'fs' in '\node_modules\symbol-crypto-wasm-node'

symbol-crypto-wasm-nodeというワードは見たことがありますね。これです。

ですので、webpackのpluginの設定をnuxt.config.jsに書いていけばいいことになります。どのように書くかがわからないので、ドキュメントを探します。以下のようなものが見つかりました。

これを見習って、次のように書きます。

nuxt.config.js(抜粋)
import webpack from 'webpack'

export default {
  build: {
    plugins: [
      new webpack.NormalModuleReplacementPlugin(
        /symbol-crypto-wasm-node/,
        '../../../symbol-crypto-wasm-web/symbol_crypto_wasm.js'
      )
    ]
  }
}

そして次をインストールします。

$ npm i symbol-crypto-wasm-web

すると、画面は次のようになりました。

image.png

ひとつエラーが消えました。

Module parse failed: Identifier directly after number エラー

次のエラーに移りましょう。

ERROR in ./node_modules/symbol-sdk/src/BaseValue.js 10:37
Module parse failed: Identifier directly after number (10:37)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| 			throw new TypeError(`"value" (${value}) has invalid type, expected BigInt`);
| 
> 		lowerBound = isSigned ? -0x80000000_00000000n : 0n;
| 		upperBound = isSigned ? 0x7FFFFFFF_FFFFFFFFn : 0xFFFFFFFF_FFFFFFFFn;
| 	} else {

Identifier directly after number、つまり、数字の後ろに何らかの識別子が直接入っていることが問題のようです。

調べた過程はもう忘れてしまいましたが、0x80000000_00000000nのように、数字の間に_を入れることがダメである、というメッセージでした。

じゃぁ例えば、こういうものを用意したらどうなるのか。

pages/index.vue
<template>
  <div>
    {{ counter }}
  </div>
</template>

<script>
export default {
  name: 'IndexPage',
  data() {
    return {
      counter: 0x345_388 + 0x453254
    }
  }
}
</script>

これは問題なく、エラー無く表示されます。

image.png

ということは、node_modulesの中にあるコードだからダメ(_を数字の間に入れるような書き方はダメ)ということになります。

ChatGPTに、この書き方について聞いてみました。

image.png

推察すると、vueファイルのようなwebpackの処理が行われるファイルでは、数字の間に_を入れていいが、node_modules内のファイルでは、webpackの処理が何かしらスキップされるので、エラーとなってしまうのではないかと。(たぶん正確性に欠ける表現とは思いますが。)

というわけで、何とかnode_modules内のファイルに対してwebpackの処理ができないかを探していました。

いろいろChatGPTに聞いたりした結果、以下のような回答が参考になりました。

image.png

image.png

image.png

ChatGPTに教えてもらった以下のwebpackの設定を見て、これを流用すれば、/node_modules/symbol-sdk以下のファイルに対してwebpackの処理がなされるのでは?

module: {
  rules: [
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: ['@bable/preset-env'],
      }
    }
  ]
}

これをnuxt.config.jsで書くために、以下のドキュメントを参考にしました。

そして、いろいろ試してみて、最終的にこのような形式にたどり着いた感じです。

nuxt.config.js(抜粋)
export default {
  build: {
    extend(config, ctx) {
      config.module.rules.push({
        test: /node_modules[\\/]symbol-sdk[\\/].*\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      });
    }
  }
}

これを導入すると、プロジェクト作成のところで書いたコードが、うまく動くようになりました。

image.png

他にものこのようなエラーがあったのですが、それもなくなりました。(static修飾子が解釈されない)

ERROR in ./node_modules/symbol-sdk/src/symbol/models.js 11:13
Module parse failed: Unexpected token (11:13)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| 
| export class Amount extends BaseValue {
> 	static SIZE = 8;
| 
| 	constructor(amount = 0n) {

static修飾子はそんなに古い表現ではなさそうだけど、webpackで何かしら処理してやらないとダメなんですね。

image.png

おわりに

Nuxt.jsのv2で使えることがわかりました。ただ、これがキレイな方法かどうかはわからないので、ほかに情報などがあれば、そちらも試してみるのもいいかもしれない。

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2