Symbol sdk 検証スクリプト完全版

この記事は [nem Advent Calendar 2021](https://qiita.com/advent-calendar/2021/nem) 6日目の記事です。

# はじめに

開発用コンソールで検証するためのスクリプト置き場です。今まで何度かQiitaでも動作検証のためのスクリプトを紹介してきましたが、今回はほぼすべてのメソッドを検証できる完全版です。
Twitterで「動かない」とツイートしたら秒で私からの解決リプを経験された方がいるかもしれませんが、以下のスクリプトで再現のための検証環境を構築しています。

# 接続先

- メインネット
    - https://node.xembook.net/chain/info
    - https://sym-main-01.opening-line.jp:3001/chain/info
    - https://the-symbol-node.dusan.gq:3001/chain/info
- テストネット
    - https://sym-test.opening-line.jp:3001/chain/info
    - https://sym-test-01.opening-line.jp:3001/chain/info
    - https://4.dusan.gq:3001/chain/info
    - 旧テストネット
        - http://testnet.nextage.me:3000/chain/info



# Import

gitpage 上のnem2-browserifyからsymbol-sdk-packをページに読み込みます。

```js
(script = document.createElement('script')).src = 'https://xembook.github.io/nem2-browserify/symbol-sdk-pack-1.0.3.js';
document.getElementsByTagName('head')[0].appendChild(script);
```

# Script

```js
NODE = window.origin;

//require
sym = require("/node_modules/symbol-sdk");
op  = require("/node_modules/rxjs/operators");
rxjs = require("/node_modules/rxjs");
qr = require("/node_modules/symbol-qr-library");
hd = require("/node_modules/symbol-hd-wallets");
cat = require("/node_modules/catbuffer-typescript");
sha3_256 = require('/node_modules/js-sha3').sha3_256;
sha256 = require('/node_modules/js-sha256').sha256;

//repository
repo = new sym.RepositoryFactoryHttp(NODE);
accountRepo = repo.createAccountRepository();
txRepo = repo.createTransactionRepository();
tsRepo = repo.createTransactionStatusRepository();
receiptRepo = repo.createReceiptRepository();
nwRepo = repo.createNetworkRepository();
blockRepo = repo.createBlockRepository();
chainRepo = repo.createChainRepository();
nodeRepo = repo.createNodeRepository();
finRepo = repo.createFinalizationRepository();
nsRepo = repo.createNamespaceRepository();
metaRepo = repo.createMetadataRepository();
mosaicRepo = repo.createMosaicRepository();
msigRepo = repo.createMultisigRepository();
hlRepo = repo.createHashLockRepository();
slRepo = repo.createSecretLockRepository();
resAccountRepo = repo.createRestrictionAccountRepository();
resMosaicRepo = repo.createRestrictionMosaicRepository();

//properties
(async() =>{
epochAdjustment = await repo.getEpochAdjustment().toPromise();
generationHash = await repo.getGenerationHash().toPromise();
currencyId = (await repo.getCurrencies().toPromise()).currency.mosaicId.toHex();
networkCurrency = (await repo.getCurrencies().toPromise()).currency;
networkType = await repo.getNetworkType().toPromise();
transactionFees = await nwRepo.getTransactionFees().toPromise();
averageFeeMultiplier = transactionFees.averageFeeMultiplier;
medianFeeMultiplier = transactionFees.medianFeeMultiplier;
minFeeMultiplier = transactionFees.minFeeMultiplier;
})();

//service
transactionService = new sym.TransactionService(txRepo, receiptRepo);
stateProofService = new sym.StateProofService(repo);
metaService = new sym.MetadataTransactionService(metaRepo);
mosaicResService = new sym.MosaicRestrictionTransactionService(resMosaicRepo,nsRepo)
nsService = new sym.NamespaceService(nsRepo);

//listener
wsEndpoint = NODE.replace('http', 'ws') + "/ws";
listener = new sym.Listener(wsEndpoint,nsRepo,WebSocket);
```

# リファレンス

https://docs.symbolplatform.com/symbol-sdk-typescript-javascript/1.0.3/

例えば、accountRepoについては以下のドキュメントを参考にしてください。

- Module infrastructure/AccountRepository
    - Interface AccountRepository
        - Class AccountHttp

https://docs.symbolplatform.com/symbol-sdk-typescript-javascript/1.0.3/classes/infrastructure_accounthttp.accounthttp.html

# 検証スクリプトの重要性

[Symbolの衝撃](https://qiita.com/nem_takanobu/items/5b9388dab19d1c985d3d)で、トランザクションベースのスマートコントラクトが任意のプログラム言語で構築可能であることは説明しました。得意な言語が別にあったとしても異なる経路の検証手段を持っておくことは重要です。万が一うまく動かない場合に、アプリケーション側に問題があるのかSDKにクセのある仕様があるのか、問題点を切り分けやすくなります。そのためには検証用のスクリプトはより気軽に使える必要があります。



# 利用例

ブラウザを開き、メインネット・あるいはテストネットのノードにアクセスします。
F12で開発者コンソールを開き、Import、Scriptの順にソースコードをコピペしてEnterキーで実行します。
これで準備完了です。


基本的な使い方はこちらで紹介しています。

https://qiita.com/nem_takanobu/items/e2b1f0aafe7a2df0fe1b


@nembear さんに実際の使用手順を動画で紹介していただいてます。

https://qiita.com/nembear/items/4b84506ff4a62f3f1ce1


検証スクリプトで宣言した変数名を打ち込むと、使えるメソッドがサジェストされるので大変便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29187/52a791aa-6509-bd8e-8f1b-ce022912be39.png)



# サンプル集

### アカウント周り

```js:アカウントの作成
sym.Account.generateNewAccount(networkType);

> Account {address: Address, keyPair: {…}}
    address: Address {address: 'NADOSCNQK4YT2EYUHV4JTYEONRPWNHDK22WG3OY', 
    privateKey: "9A2ED7D9351B317D220A780AD5C25F8698C4E7451C173CB4F42CEC8ECD0B8***"
    publicKey: "D6BC800058D649B9DC22AE47C390D3D0B36F6D08A517367D7A65582E8DCB2B09"
```

```js:秘密鍵からアカウントの生成
var account = 
sym.Account.createFromPrivateKey(
  "9A2ED7D9351B317D220A780AD5C25F8698C4E7451C173CB4F42CEC8ECD0B8***",
  networkType
)
```

```js:アドレス文字列からアドレスの生成
var address = 
sym.Address.createFromRawAddress(
  "NADOSCNQK4YT2EYUHV4JTYEONRPWNHDK22WG3OY"
)
```

```js:ニーモニック生成
mnemonic = hd.MnemonicPassPhrase.createRandom();
mnemonic.plain
```



### subscribe
非同期でsubscribe内に記述された処理を行います。

```js:アカウント情報の取得

address = sym.Address.createFromRawAddress(
    "NCESRRSDSXQW7LTYWMHZOCXAESNNBNNVXHPB6WY"
)
accountRepo.getAccountInfo(address)
.subscribe(x=>console.log(x))
```

```js:QRコード出力

var address = 
sym.Address.createFromRawAddress(
  "NADOSCNQK4YT2EYUHV4JTYEONRPWNHDK22WG3OY"
);

var addQR = new qr.AddressQR("alice",address);

addQR.toBase64().subscribe(x => {
  (tag= document.createElement('img')).src = x;
  document.getElementsByTagName('body')[0].appendChild(tag);
});
```
### await ... toPromise()

awaitの後ろのメソッドが完了するまで、次行へ進まずに処理を待ちます。


```js:プロパティ値の確認
//ネットワークプロパティの確認
await repo.networkProperties.toPromise();

//ネットワーク通貨の確認
(await repo.networkCurrencies.toPromise()).currency;

//ネットワーク誕生時の時刻とハッシュ値の確認
await repo.epochAdjustment.toPromise();
await repo.generationHash.toPromise();
```
```js:最新ブロック表示
//最新ブロック表示
(await blockRepo.search({order: nem.Order.Desc}).toPromise()).data[0]

//直近の承認トランザクション表示
(await txRepo.search({
  order: sym.Order.Desc,
  group:sym.TransactionGroup.Confirmed
}).toPromise()).data[0]

```

### listener

レポジトリに追加された情報を検知します。
listenerは都度subscribeされるので、 await ... toPromise()に置き換えることはできません。

```js:新しいブロック生成を検知
listener.open().then(() => {
  listener.newBlock()
  .subscribe(x=>console.log(x));
});

```

```js:指定アドレスへの連署要求を検知
var address = 
sym.Address.createFromRawAddress(
  "NADOSCNQK4YT2EYUHV4JTYEONRPWNHDK22WG3OY"
);

listener.open().then(() => {
  listener.aggregateBondedAdded(address)
  .subscribe(x=>console.log(x))
});
```

### service

複数レポジトリにまたがる処理を一括で行います。

```js:トランザクションをアナウンスして、承認結果を通知
account = sym.Account.createFromPrivateKey(
  "9A2ED7D9351B317D220A780AD5C25F8698C4E7451C173CB4F42CEC8ECD0B8***",
  networkType
);
address = sym.Address.createFromRawAddress(
  "NADOSCNQK4YT2EYUHV4JTYEONRPWNHDK22WG3OY"
);

tx = sym.TransferTransaction.create(
	sym.Deadline.create(epochAdjustment),
	address, 
	[],
	sym.EmptyMessage,
	networkType
).setMaxFee(200);
signedTx = account.sign(tx,generationHash);
listener.open().then(() => {
	txService.announce(signedTx,listener)
	.subscribe(confirmedTx=>{
		console.log(confirmedTx)
	})
});
```


```js:ハッシュロックとボンデッドTXのアナウンスを同時に処理
alice = sym.Account.createFromPrivateKey(
  "9A2ED7D9351B317D220A780AD5C25F8698C4E7451C173CB4F42CEC8ECD***",
  networkType
);

bob = sym.Account.generateNewAccount(networkType);

tx = sym.TransferTransaction.create(
    sym.Deadline.create(epochAdjustment),
    alice.address, 
    [networkCurrency.createRelative(2)],
    sym.PlainMessage.create('test'),
    networkType
);

feeTx = sym.TransferTransaction.create(
    sym.Deadline.create(epochAdjustment),
    bob.address, 
    [networkCurrency.createRelative(1)],
    sym.EmptyMessage,
    networkType
);

aggregateArray = [
    feeTx.toAggregate(alice.publicAccount),
    tx.toAggregate(bob.publicAccount),
]

aggregateTx = sym.AggregateTransaction.createBonded(
    sym.Deadline.create(epochAdjustment),
    aggregateArray,
    networkType,
    [],
).setMaxFeeForAggregate(200, 1);

signedAggregateTx = alice.sign(aggregateTx, generationHash);

hashLockTx = sym.HashLockTransaction.create(
    sym.Deadline.create(epochAdjustment),
    networkCurrency.createRelative(10),
    sym.UInt64.fromUint(480),
    signedAggregateTx,
    networkType
).setMaxFee(200);

signedLockTx = alice.sign(hashLockTx, generationHash);

listener.open().then(() => {
    transactionService.announceHashLockAggregateBonded(
      signedLockTx,
      signedAggregateTx,
      listener
    ).subscribe(aggTx => console.log(aggTx))
});

```
# さいごに

検証スクリプトの使用方法を駆け足で紹介しました。
もし、symbol-sdkがうまく動かないなどありましたら、ツイッターで #symbolsdkama とハッシュタグを入力してツイートしてみてください。先人達が救いの手を差し伸べてくれるかもしれません。

紹介した検証スクリプトは、今後さまざまな技術解説を行うときに引用します。
今回の記事が少し物足りなかったかもしれませんが、より具体的な内容の投稿を予定していますので、ご期待ください。