Open5

Symbol SDK v3 TypeScript を触ってみる

蓮蒲ルナ蓮蒲ルナ

実行環境構築

まずは環境を構築。TypeScriptを直接実行できるので、bunを使う。あとasdfを使ってます。

$ mkdir ts-sdk && cd $_
$ asdf install bun latest
$ asdf local bun latest
$ bun init
$ bun install symbol-sdk
蓮蒲ルナ蓮蒲ルナ

新規アカウントの生成

まず、検証で使うための新たなアカウントを作成する。(もちろんテストネット)
ニーモニックによるアカウントと秘密鍵によるアカウントが存在する。

アカウント - Blockchain Symbol-SDK v3.2 #TypeScript - Qiita

ニーモニックによるアカウントは子孫アカウントを持つことができるが、今回はシンプルに秘密鍵によるアカウントを作成して、これを検証用に使う。

import { PrivateKey } from 'symbol-sdk';
import { Address, Network, SymbolFacade } from 'symbol-sdk/symbol';

const privateKey = PrivateKey.random();
console.log(`Private Key: ${privateKey.toString()}`);

const facade = new SymbolFacade(Network.TESTNET);

const keyPair = new facade.static.KeyPair(privateKey);
console.log(`Public Key: ${keyPair.publicKey.toString()}`);

const address = new Address(facade.network.publicKeyToAddress(keyPair.publicKey));
console.log(`Address: ${address.toString()}`);
$ bun generate_new_account.ts 
Private Key: CC741E6AFE9AE8ED92B1567A17EBC4950DAEFF7EA70D8878D27E4CB91D47B20C
Public Key: 1D0C551813FF2072B82D0E987A6AAF50EAB0D6F34CDCBA255EA804D9773E3B98
Address: TDK2E5VGKH4YSPVBYL2IPI2QFKXLDCSNHDOURRI

エクスプローラで見れるようにアドレスをメモ。

TDK2E5VGKH4YSPVBYL2IPI2QFKXLDCSNHDOURRI - Symbol Block Explorer

XYMの取得

テスト用のXYMを得るために蛇口に申請する。X(旧Twitter)認証が必要。

The XYM Faucet

Twitter account is unqualified

蛇口からXYMが貰えない…。連携したアカウントが問題なのかな?
別のアカウントで認証したら配布してくれた。あまり使いこみが浅い、または新規のXアカウントだと駄目なのかも(スパム対策?)

578BF0CF369D9079287AE93CA57AEF9C5CB40F13CE833D0D9E63635FDF93B8E3 - Symbol Block Explorer

ひとまず、これでアカウントへXYMをもたせることができた。

蓮蒲ルナ蓮蒲ルナ

トランザクションオブジェクト作成の記述

READMEに書いてある内容に従って、トランザクションペイロードを作ってみる。
symbol/sdk/javascript at main · symbol/symbol

秘密鍵は前述で作ったもので置き換え、
宛先のアカウントには色々投げ込まれているようなので、そのまま使う。
Symbol Block Explorer

Node環境でのコードを実行を試す。
一部、書き換えが必要だったので、コメントを入れている。

import { PrivateKey } from 'symbol-sdk';
import { SymbolFacade, descriptors, models } from 'symbol-sdk/symbol';

const facade = new SymbolFacade('testnet');

const transaction = facade.transactionFactory.create({
  type: 'transfer_transaction_v1',
  signerPublicKey: '1D0C551813FF2072B82D0E987A6AAF50EAB0D6F34CDCBA255EA804D9773E3B98',
  fee: 1000000n, // 絶対数指定なので 1.000000 XYM の意味
  deadline: facade.network.fromDatetime(new Date()).addHours(2).timestamp, // 有効期限を現在時刻から 2 時間後に設定
  recipientAddress: 'TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I',
  mosaics: [
    // このトランザクションで送信したいモザイクと数量。このIDは`symbol.xym`のもの
    { mosaicId: 0x72C0212E67A08BCEn, amount: 1000000n }
  ]
});

const privateKey = new PrivateKey('CC741E6AFE9AE8ED92B1567A17EBC4950DAEFF7EA70D8878D27E4CB91D47B20C');
const signature = facade.signTransaction(new facade.static.KeyPair(privateKey), transaction);

const jsonPayload = facade.transactionFactory.static.attachSignature(transaction, signature);;

// 追記。標準出力と標準エラー出力に JSON 形式で出力。
process.stdout.write(jsonPayload);
process.stderr.write(facade.hashTransaction(transaction).toString());

より簡素なトランザクションオブジェクト作成の記述

const typedDescriptor = new descriptors.TransferTransactionV1Descriptor(
  new Address('TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I'),
  [
    new descriptors.UnresolvedMosaicDescriptor(
      new models.UnresolvedMosaicId(0x72C0212E67A08BCEn),
      new models.Amount(1000000n)
    )
  ]
);
const transaction = facade.createTransactionFromTypedDescriptor(
  typedDescriptor,
  new PublicKey('1D0C551813FF2072B82D0E987A6AAF50EAB0D6F34CDCBA255EA804D9773E3B98'), // signerPublicKey — 署名公開鍵
  100, // feeMultiplier — 手数料乗数
  2 * 3600 // deadlineSeconds — 有効期限秒数
);

文字列による指定type: 'transfer_transaction_v1',を排除でき、有効期限も現時点からの秒数を指定するだけでよくなります。

また、具体的なトランザクションタイプのオブジェクトの定義と、トランザクションとして共通の定義の処理を分離できるようになっています。

署名済みトランザクションを作成

コードを実行して、ペイロードとトランザクションハッシュをファイルとして保存する。

$ bun sending_a_transaction.ts > payload.json 2> transactionHash.json
$ cat payload.json
{"payload": "B0000000000000003110845134F864EA1823BACB2CF410E1E6EF6CD53F6142CC8B7400CCD8F5C763EA5D4275D512CD77B53B479B6BD7708FCE6464D5A70A9E02D5BCB1CF3583470B1D0C551813FF2072B82D0E987A6AAF50EAB0D6F34CDCBA255EA804D9773E3B98000000000198544140420F000000000060514D9610000000988E1191A25A88142C2FB3F69787576E3DC713EFC1CE4DE90000010000000000CE8BA0672E21C07240420F0000000000"}
$ cat transactionHash.txt
98890C54F3AAA90B71B64292D5EE6309E34F89EED6B5ED1BA289D8CEC7704EBE

署名済みのトランザクションデータが得られた。

ネットワークへトランザクションの送信

Finally, send the payload to the desired network using the specified node endpoint:
Symbol: PUT /transactions

これをエンドポイントの/transactionsへPUTする。

sym-test-01.opening-line.jp:3001
ノードはこちらを使わせてもらう。

$ curl -X PUT -H "Content-Type: application/js
on" -d @payload.json https://sym-test-01.opening-line.jp:3001/transactions
{"message":"packet 9 was pushed to the network via /transactions"}

トランザクションの受認可否結果は非同期に行われるため、このレスポンスからは判断できない。

トランザクションの状態を確認

エンドポイントの/transactionStatus/{TransactionHash}へ問い合わせて、トランザクションの状態を確認する。

$ curl "https://sym-test-01.opening-line.jp:3001/transactionStatus/$(cat transactionHash.txt)"
{"group":"unconfirmed","code":"Success","hash":"98890C54F3AAA90B71B64292D5EE6309E34F89EED6B5ED1BA289D8CEC7704EBE","deadline":"71243353244","height":"0"}

トランザクションが承認されると承認後のエンドポイントの/transactions/confirmed/{TransactionHash}を見るか、エクスプローラでも確認できるようになる。

$ curl "https://sym-test-01.opening-line.jp:3001/transactions/confirmed/$(cat transactionHash.txt)"
{"meta":{"height":"2100850","hash":"98890C54F3AAA90B71B64292D5EE6309E34F89EED6B5ED1BA289D8CEC7704EBE","merkleComponentHash":"98890C54F3AAA90B71B64292D5EE6309E34F89EED6B5ED1BA289D8CEC7704EBE","index":0,"timestamp":"71233950045","feeMultiplier":5681},"transaction":{"size":176,"signature":"3110845134F864EA1823BACB2CF410E1E6EF6CD53F6142CC8B7400CCD8F5C763EA5D4275D512CD77B53B479B6BD7708FCE6464D5A70A9E02D5BCB1CF3583470B","signerPublicKey":"1D0C551813FF2072B82D0E987A6AAF50EAB0D6F34CDCBA255EA804D9773E3B98","version":1,"network":152,"type":16724,"maxFee":"1000000","deadline":"71241126240","recipientAddress":"988E1191A25A88142C2FB3F69787576E3DC713EFC1CE4DE9","mosaics":[{"id":"72C0212E67A08BCE","amount":"1000000"}]},"id":"679F2AC16583EA846923CA2E"}%

エクスプローラで確認する場合。

98890C54F3AAA90B71B64292D5EE6309E34F89EED6B5ED1BA289D8CEC7704EBE - Symbol Block Explorer

残高から1XEMを送信するトランザクションを実行できた。

蓮蒲ルナ蓮蒲ルナ

エラーを監視する

アカウントに関するエラーを監視してキャッチする。
実装の参考ドキュメント

新しいブロックの監視 — Symbol Documentation

monitor.tsとして次の内容を作成する。

import WebSocket from 'ws';

const host = 'sym-test-01.opening-line.jp:3001';
const socket = new WebSocket('wss://' + host + '/ws');

socket.on('open', () => {
  console.info('Connection opened');
});

socket.on('close', () => {
  console.info('Connection closed');
});

socket.on('error', (error) => {
  console.info(error);
});

socket.on('message', (msg: string) => {
  try {
    const response = JSON.parse(msg);
    if ('uid' in response) {
      console.info(response);
      const subscribeTo = {
        uid: response.uid,
        subscribe: "status/TDK2E5VGKH4YSPVBYL2IPI2QFKXLDCSNHDOURRI"
      };
      socket.send(JSON.stringify(subscribeTo));
    } else {
      console.log(response);
    }
  } catch (error) {
    console.error(error);
  }
});

アカウントTDK2E5VGKH4YSPVBYL2IPI2QFKXLDCSNHDOURRIのステータスが流れてくるストリームの購読を開始する。

$ bun monitor.ts
Connection opened
{
  uid: "TFWURWRAQ67RYCW44UZRT3QRUZCWET5K",
}

この状態で、前述のトランザクションのコードで、例えば残高を超えるXYMの送信トランザクションを作成し、送信してみる。
送信するために、monitor.tsとは別のコンソールを立ち上げて、実行する。

// 送信量を9,000XYMへ書き換える。
  mosaics: [
    { mosaicId: 0x72C0212E67A08BCEn, amount: 9000n * 1000000n }
  ]

送信すると、

$ bun sending_a_transaction.ts > payload.json
$ curl -X PUT -H "Content-Type: application/json" -d @payload.json https://sym-test-01.opening-line.jp:3001/transactions
{"message":"packet 9 was pushed to the network via /transactions"}

// ---- 以下、monitor.tsを実行中のコンソール

{
  topic: "status/TDK2E5VGKH4YSPVBYL2IPI2QFKXLDCSNHDOURRI",
  data: {
    hash: "E92702FE716F7BA91B77B7A1E46AB84BC4040FCDABB9B0F0D5A096A37FEACF26",
    code: "Failure_Core_Insufficient_Balance",
    deadline: "71247615572",
  },
}

Failure_Core_Insufficient_Balance(残高不足)というエラーメッセージが流れてくる。

RESTが返すステータスの定義コード。
symbol/client/rest/src/catapult-sdk/model/status.js at 10c09a2ac1b26fcb29e23ed9e911c9d123678d11 · symbol/symbol

他の購読のためのチャネル名の参考コード。
symbol-sdk-typescript-javascript/src/infrastructure/Listener.ts at 1162c1cb7c4c73cef6d770c0ffbac8131b3a3d24 · symbol/symbol-sdk-typescript-javascript

蓮蒲ルナ蓮蒲ルナ

ブラウザで確認できるように

WebSocket Client

前述のコードをブラウザで動くように加筆修正。
アドレスをいれて、「Connect」ボタンを押下すれば購読が始まる。(Chromeでしか動作確認してない)
とりあえずコンソールを使わなくても見れる程度の代物。