PythonでSymbolブロックチェーンの送金プログラムを書いてみる。

本記事は「PythonでSymbolブロックチェーン」連載の1回目です。

[PythonでSymbolブロックチェーンの送金プログラムを書いてみる。](https://qiita.com/nem_takanobu/items/f3c02caa17ad385b6155)
[Symbolブロックチェーンで一人が複数人に送金するトランザクションをPythonで作成する](https://qiita.com/nem_takanobu/items/d148dee444a74737769b)
[Symbolブロックチェーンで複数人が同時に送金するトランザクションをPythonで作成する](https://qiita.com/nem_takanobu/items/99557ff2299354aabcc4)
[Symbolブロックチェーンで複数人が同時に送金するトランザクションをPythonで作成する(2)](https://qiita.com/nem_takanobu/items/7e7bed7e612b613cf296)




ブロックチェーンSymbol from nemのパブリックチェーンからはやくも一か月以上が経ちました。


Symbolはブロックチェーンに詳しくない他分野のスペシャリストでもローコード/ノーコードでセキュアかつデプロイレスなスマートコントラクトを記述できるブロックチェーンとして日本のエンジニアや研究者からも非常に注目される存在となり、いまでは元イーサリアムのプロトコルの改善提案者がコアチームに無報酬で参入するなどプラットフォームとしてその可能性を期待されています。



Python版sdkがリリースされたということで、さっそく試してみます。

今回使用するライブラリはこちらです（Python3.7以上）。

https://pypi.org/project/symbol-sdk-core-python/

最新バージョンとの差異はこちらの記事もご参考ください。

https://qiita.com/kurikou_XymCity/items/ab9c9475c363c95930d6


まずインストールします。

```sh
pip install symbol-sdk-core-python
```

注) Windowsでインストールした人はエラーが発生することがあるようです。
以下のサイトを参考にBuild Tools for Visual Studio 2019の「C++ Build Tool」「MSCV v140」をインストールしてみてください。

https://mebee.info/2020/07/18/post-13597/



##### KeyPair生成

```py
from binascii import unhexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.symbol.KeyPair import KeyPair
b = unhexlify("896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED5123****")
prikey = PrivateKey(b)
keypair = KeyPair(prikey)
```

秘密鍵をバイナリ変換してPrivateKeyクラスを生成し、Keypairを作成します。

`ModuleNotFoundError: No module named 'symbolchain'`と表示された場合はインストールしたPythonと実行しているPythonのバージョンが異なります。python3などにインストールされている場合がありますので、実行環境を再確認してください。

`ModuleNotFoundError: No module named 'symbolchain.core.symol'`と表示された場合は古いバージョンのsymbol-sdkを参照しています。以後 symbolをsymと置き換えてお試しください。



##### PublicKey生成

```py
pubkey = keypair.public_key
print(str(keypair.public_key))
```

keypairクラスから公開鍵を導出します。

##### Symbolのファサード生成

```py
from symbolchain.core.facade.SymbolFacade import SymbolFacade
facade = SymbolFacade('testnet')
```
Symbolのパラメータがセットされたファサードクラスを生成します。NIS1版のファサードもあるようです。
`testnet` がテストネットのファサードです。メインネットは`mainnet`になります。

##### アドレス生成

```py
address = facade.network.public_key_to_address(pubkey)
print(str(address))

#すでに知っているアドレスを使用する場合
address = SymbolFacade.Address("TD2JRXVHMX3BAOBOYURVASX4UUGABWK6YKSU***")

```
Symbolファサードを利用して公開鍵からアドレスを導出します。
なお、REST APIで取得できるアドレス情報はHEX文字列なので変換する必要があります。

```py
from binascii import unhexlify
from symbolchain.core.symbol.Network import Address, Network

address = Address(unhexlify("688928C64395E16FAE78B30F970AE0249AD0B5B5B9DE1F5B"))
print(address) # NCESRRSDSXQW7LTYWMHZOCXAESNNBNNVXHPB6WY

from binascii import hexlify
address = Address("NCESRRSDSXQW7LTYWMHZOCXAESNNBNNVXHPB6WY")
print(hexlify(address.bytes))
```


##### 秘密鍵の作成から始める
ここまで、秘密鍵を知っている前提で説明しましたが、秘密鍵を作成するところからの説明も補足しておきます。

```py
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.symbol.KeyPair import KeyPair
from symbolchain.core.facade.SymbolFacade import SymbolFacade

prikey = PrivateKey.random()
keypair = SymbolFacade.KeyPair(prikey)
pubkey = keypair.public_key
facade = SymbolFacade('testnet')
address = facade.network.public_key_to_address(pubkey)
print(str(address))
> TD2JRXVHMX3BAOBOYURVASX4UUGABWK6YKSU***
```

この出力されたアドレスを蛇口サイトに入力してテスト送信用のXYMを入手しておきます。
http://faucet.testnet.symboldev.network/
(テストネットで借りたXYMが余った場合はかならず元アドレスへ返しておきましょう)

##### トランザクションの有効期限を設定

```py
import datetime
deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1637848847) * 1000
```
`1637848847` というのはSymbolが誕生したUTC秒になります。この時刻を基準にネットワーク内部で時刻が同期されています。2時間後を有効期限とします。メインネットは`1615853185`です。

##### 添付するハッシュダイジェスト作成

```py
import hashlib
digest = ""
with open('apostille.png','rb') as f:
   digest = hashlib.sha256(f.read()).hashdigest()
```

せっかくなのでトランザクションに「自分しか知らない、でも存在を証明したいファイル」のハッシュダイジェストを記録しましょう（このプロセスは不要です）。

##### トランザクション作成

```py
tx = facade.transaction_factory.create({
  'type': 'transfer',
  'signer_public_key': pubkey,
  'fee': 100000,
  'deadline': deadline,
  'recipient_address': address,
  'mosaics': [(0x091F837E059AE13C, 1000000)],
  'message': bytes(1) + digest.encode('utf8')
})
```

`0x3A8416DB2D53B6C8` がテストネットでのXYMのモザイクID(トークン)になります。メインネットは`0x6BED913FA20223F8`です。
送金量は可分性が6(小数点以下6桁)なので　1000000倍した数値を指定します。今回は1XYMを送金します。 
feeも同様にこれ以上の送金手数料は払えない、という額を指定します（今回は0.1XYM）。手数料の指定が小さいとトランザクションが混雑してきたときに承認されるための時間が長く必要になります。また、ノードごとに指定されている最小手数料よりも小さいとスパムとみなされ、トランザクションの状態を監視するステータスにも残りません。

##### 署名

```py
signature = facade.sign_transaction(keypair, tx)
tx.signature = signature.bytes
```

署名して、トランザクションに含めます。


##### JSON化

```py
from binascii import hexlify
payload = {"payload": hexlify(tx.serialize()).decode('utf8').upper()}

import json
json = json.dumps(payload)
```

トランザクションをjson化します。


##### アナウンス

```py
headers = {'Content-type': 'application/json'}
import http.client

conn = http.client.HTTPConnection("sym-test-01.opening-line.jp",3000)
conn.request("PUT", "/transactions", json,headers)

response = conn.getresponse()
print(response.status, response.reason)

```

テストネットにアナウンスして結果を受け取ります。
使用するAPIはこちらです。

https://docs.symbolplatform.com/symbol-openapi/v1.0.0/#operation/announceTransaction

##### 確認

```py
hash = facade.hash_transaction(tx)
print('https://sym-test-01.opening-line.jp:3001/transactionStatus/' + str(hash))
```

ハッシュ値を取得して状態を確認します。最初はUnconfirmedですが、30秒ほどするとConfirmが取れてSuccessになります。
Successになればエクスプローラーで確認してみましょう。

http://explorer.testnet.symboldev.network/

これでPythonを使ってSymbolブロックチェーンで送金することができました。
今回pythonで対話式に解説しましたが、実はJavaScriptを使ってブラウザのコンソールでも同じように送金を体験することができます。

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


ブロックチェーンSymbolにとって送金はまだほんの序の口です。ぜひいろいろな機能にチャレンジしてみてください。

最後に、全スクリプトを載せておきます。

```py
from binascii import unhexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.symbol.KeyPair import KeyPair
from symbolchain.core.facade.SymbolFacade import SymbolFacade
import datetime
from binascii import hexlify
import json
import http.client

facade = SymbolFacade('testnet')

b = unhexlify("896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED5123****")
prikey = PrivateKey(b)
keypair = KeyPair(prikey)
pubkey = keypair.public_key
address = facade.network.public_key_to_address(pubkey)

deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1637848847) * 1000
tx = facade.transaction_factory.create({
  'type': 'transfer',
  'signer_public_key': pubkey,
  'fee': 100000,
  'deadline': deadline,
  'recipient_address': address,
  'mosaics': [(0x3A8416DB2D53B6C8, 1000000)],
  'message': bytes(1) + "test".encode('utf8')
})

signature = facade.sign_transaction(keypair, tx)
tx.signature = signature.bytes

payload = {"payload": hexlify(tx.serialize()).decode('utf8').upper()}
jsonPayload = json.dumps(payload)
headers = {'Content-type': 'application/json'}
conn = http.client.HTTPConnection("sym-test-01.opening-line.jp",3000)
conn.request("PUT", "/transactions", jsonPayload,headers)

response = conn.getresponse()
print(response.status, response.reason)

hash = facade.hash_transaction(tx)
print('http://sym-test-01.opening-line.jp:3000/transactionStatus/' + str(hash))


```








以下はsymbol-sdk ver1.0のスクリプトです。

```py
from binascii import unhexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.sym.KeyPair import KeyPair
from symbolchain.core.facade.SymFacade import SymFacade
import datetime
from binascii import hexlify
import json
import http.client

facade = SymFacade('public_test')

b = unhexlify("896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED5123****")
prikey = PrivateKey(b)
keypair = KeyPair(prikey)
pubkey = keypair.public_key
address = facade.network.public_key_to_address(pubkey)

deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1637848847) * 1000
tx = facade.transaction_factory.create({
  'type': 'transfer',
  'signer_public_key': pubkey,
  'fee': 100000,
  'deadline': deadline,
  'recipient_address': address,
  'mosaics': [(0x3A8416DB2D53B6C8, 1000000)],
  'message': bytes(1) + "test".encode('utf8')
})

signature = facade.sign_transaction(keypair, tx)
tx.signature = signature.bytes

payload = {"payload": hexlify(tx.serialize()).decode('utf8').upper()}
jsonPayload = json.dumps(payload)
headers = {'Content-type': 'application/json'}
conn = http.client.HTTPConnection("sym-test-01.opening-line.jp",3000)
conn.request("PUT", "/transactions", jsonPayload,headers)

response = conn.getresponse()
print(response.status, response.reason)

hash = facade.hash_transaction(tx)
print('http://sym-test-01.opening-line.jp:3000/transactionStatus/' + str(hash))

```