symbol-sdk@3.1.0でTypeScriptを試してみる

# はじめに
こんにちは。
symbol-sdkの3.1.0が公開されました。（執筆時点の最新版）

直前の3.0.11のリリースノートを見てみると、

> TypeScript support via JSDoc documentation

という文字が見えますね。

↓リリースノートのリンク
https://github.com/symbol/symbol/releases/tag/sdk%2Fjavascript%2Fv3.0.11

ということで、TypeScriptに対応したみたいなので、少し試してみたいと思います。

# まずは転送トランザクションを作ってみる

以前書いた記事を例に、転送トランザクションを作ってみようと思います。

https://qiita.com/planethouki/items/860d9f1095c087ccd98f

## セットアップ

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

```shell
$ npm init -y
$ npm install typescript symbol-sdk@3.1.0 axios
```

`tsconfig.json`を作ります。

```
$ npx tsc --init
```

まずは動作確認がてら、簡単なコードを書いてみます。

```typescript:test1.ts
import symbolSdk from 'symbol-sdk'

const network = symbolSdk.symbol.Network.TESTNET

console.log(network)
```

トランスパイルして実行します。

```shell
$ npx tsc
$ node test1.js
```

するとエラーになりました。

```shell
const symbol_sdk_1 = __importDefault(require("symbol-sdk"));
                                     ^
Error [ERR_REQUIRE_ESM]: require() of ES Module <略>node_modules\symbol-sdk\src\index.js  from <略>test1.js not supported.
Instead change the require of index.js in <略> to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (<略>) {
  code: 'ERR_REQUIRE_ESM'
}
```

CommonJSであるtest1.jsから、ESModuleのsymbol-sdkは参照できない、と言われました。

ですので、設定をESModuleにしていきます。`package.json`に以下の項目を追加します。

```json:package.json
{
    "type": "module`
}
```

では、もう一度トランスパイルして実行してみます。

```shell
$ npx tsc
$ node test1.js
```

するとまたエラーになりました。

```shell
Object.defineProperty(exports, "__esModule", { value: true });
                      ^

ReferenceError: exports is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '<略>\package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
```

ESModuleでexportsは使えませんよ、と言われました。調べた感じでは、トランスパイルがCommonJSで出力しているのが問題なようです。

`tsconfig.json`を以下のように修正します。

```json:tsconfig.json
{
    "compilerOptions": {
        "module": "NodeNext"
    }
}
```

それではもう一度。

```shell
$ npx tsc
$ node test1.js
```

```shell
Network {
  name: 'testnet',
  identifier: 152,
  datetimeConverter: NetworkTimestampDatetimeConverter {
    epoch: 2022-10-31T21:07:47.000Z,
    timeUnits: 1
  },
<以下略>
```

これは実行することができました。

次に以下のコードを試してみます。

```ts:test2.ts
const fee = 1000000n
```

これをトランスパイルしてみます。

```shell
$ npx tsc
```

エラーになりました。

```shell
<略> - error TS2737: BigInt literals are not available when targeting lower than ES2020.

1 const fee = 1000000n
              ~~~~~~~~
```

というわけで`tsconfig.json`を以下のようにしました。

```json:tsconfig.json
{
    "compilerOptions": {
        "target": "es2020"
    }
}
```

これで動くようになりました。


## 転送トランザクション

まずはデッドラインと鍵ペアです。

```ts
const network = symbolSdk.symbol.Network.TESTNET
const deadline = network.fromDatetime(new Date(Date.now() + 60000)).timestamp

const facade = new symbolSdk.facade.SymbolFacade(network.name)

const privateKey = new symbolSdk.PrivateKey(PRIVATE_KEY)
// const keyPair = new facade.constructor.KeyPair(privateKey)
const keyPair = new symbolSdk.symbol.KeyPair(privateKey)
```

コメントアウトの行に示すように、`new facade.constructor.KeyPair` は使えませんでした（エディターでエラー表示になりました）。代わりに、`new symbolSdk.symbol.KeyPair`を使います（探しました）。

次にトランザクションです。

```ts
const currencyMosaicId = 0x72C0212E67A08BCEn

const transaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v1',
    signerPublicKey: keyPair.publicKey.toString(),
    fee: 1000000n,
    deadline,
    recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
    mosaics: [
        { mosaicId: currencyMosaicId, amount: 1000000n },
    ]
})
```

ここは特に問題なく、javascriptと同じように書けました。ただ、TypeScriptになったものの、`transactionFactory.create`の引数は`object`型になっており、ここでは型定義の恩恵が受けられません。

そして署名です。

```ts

const signature = facade.signTransaction(keyPair, transaction)
// const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature)
const jsonPayload = symbolSdk.symbol.SymbolTransactionFactory.attachSignature(transaction, signature)
const hash = facade.hashTransaction(transaction).toString()

console.log(jsonPayload);
console.log(hash);
```

コメントアウトの行が示すように、`facade.transactionFactory.constructor.attachSignature`が使用できなかったので、`symbolSdk.symbol.SymbolTransactionFactory.attachSignature`を使用しています。

最後に送信です。

```ts
// const sendRes = await axios.put(`${NODE_URL}/transactions`, jsonPayload).then((res) => res.data)
const sendRes = await axios.put(`${NODE_URL}/transactions`, JSON.parse(jsonPayload)).then((res) => res.data)
console.log(sendRes)

await new Promise((resolve) => setTimeout(resolve, 1000))

await axios
    .get(`${NODE_URL}/transactionStatus/${hash}`).then((res) => res.data)
    .then((statusRes) => {
        console.log(statusRes)
    })
    .catch((e) => {
        console.log(e.message, e.response.data)
    });
```

コメントアウトの行が示すように、`jsonPayload`に対して、`JSON.parse`するようにしています。`jsonPayload`は`string`型なのですが、`axios`の仕様なのか、`axios.put`の第二引数に文字列型を渡すと、リクエストの`Content-Type`が`application/x-www-form-urlencoded`で飛んで行ってしまいました。

ですので、オブジェクトを渡すようにして、`application/json`でリクエストが飛ぶようにしています。

# まとめ

やってきたことをまとめます。

`package.json`に以下を追加する。

```json:package.json
{
    "type": "module`
}
```

`tsconfig.json`を以下のように設定する。

```json:tsconfig.json
{
    "compilerOptions": {
        "target": "es2020",
        "module": "NodeNext"
    }
}
```

JavaScriptのコードはそのままでは動かないため、適宜修正を行う。

となります。

symbol-sdk@3.1.0が、晴れてTypeScriptに対応しましたので、reactやvueでも使ってみようと思いました。