[Symbol] Pythonでxymを送金する

# 概要
- 秘密鍵を有するウォレットから任意のアドレスへモザイク(xym)の送金プログラムです
    - <font color="red">**注意**</font> : 秘密鍵は絶対に公開しないでください
- 全コードは[Githubにて公開中](https://github.com/ancte0721/symbol-python-training)
    - 本記事は「04_mosaicTransfer」
    - バグや次回の要望あればコメントください
- 投げXYMは「**NDLS6GYOIPHATATNAVVOUNJXBD6X4BXU6IRBHIY**」まで
- ノード運営しています。あなたの委任お待ちしております。
    - **symbol-node.takagi-tech.com**
    - ハーベスト報酬は技術検証や記事執筆、ノード維持費に使わせていただきます。

## 前回記事
https://qiita.com/nobu_kyutech/items/cac214d7c98217e802f9

# 事前準備
## APIエンドポイント
公式ドキュメントはこちら。v1.0.0で動作確認しています。

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

## Symbol SDK for Python

注意:Python3.7以上

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

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

# 秘密鍵とウォレットの生成
(秘密鍵が既知であるウォレットを使う場合はこのステップは不要です)
また、メインネットの場合は、 `SymFacade('public')` となります。

```python
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.sym.KeyPair import KeyPair
from symbolchain.core.facade.SymFacade import SymFacade
from binascii import hexlify

facade = SymFacade('public_test')
new_privatekey = PrivateKey.random()
new_keypair = SymFacade.KeyPair(new_privatekey)
new_publickey = new_keypair.public_key
new_address = facade.network.public_key_to_address(new_publickey)

print("privateKey:" + str(new_privatekey))
print("address:" + str(new_address))
```

上記は、
1. 秘密鍵の生成
1. 公開鍵の生成
1. 公開鍵からウォレットアドレスの生成
を行っています。

```
privateKey:BE48D752961910610E3763D5F90538C0946CD95D6AC3DC8C7B1253879D******
address:TAJEWDXCCMQLWTKVWDAX2EW7KS3L2NT6BOOIDSQ
```
これで秘密鍵とウォレットアドレスの生成・確認ができました。
くれぐれも秘密鍵は他人に知られないように管理しましょう。

## テストネットで任意のアドレスへのxymの入金
テストネットではウォレットを作成してもxymがありませんので、[蛇口サイト](http://faucet.testnet.symboldev.network/)から自分のアドレスに対して、使いたいxym数量を入金します。
(使い終わったら返しましょう)


# 各種設定の確認

今回は下記の条件で送金します。
注意点として、送金量は可分性が6(小数点以下6桁)なので、`AMOUNT`や`MAX_FEE`に指定する値は1000000倍した数値を指定します。
また、手数料はノードの最低手数料以上でなければスパムとみなされ、トランザクションが受理されません。

```python
# 送信元秘密鍵
FROM_PRIVATEKEY = "BE48D752961910610E3763D5F90538C0946CD95D6AC3DC8C7B1253879******"
# 送信先ウォレットアドレス
TO_ADDRESS = "TBMAGI3FR6ZFDXB5CHYTCE5EBKBSQP2CZPS7CGA"
# 送金量 (可分性が6なので1000000倍した数値を指定。この例だと2枚)
AMOUNT = 2000000
# 最大手数料 (この例だと0.1枚)
MAX_FEE = 100000
# メッセージ
MESSAGE = "I love symbol. こんにちは"
# Symbol誕生のUTC秒 (メインネットは1615853185)
BIRTHTIME = 1616694977
# トランザクションの有効期限(単位はhour。この例だと2時間)
EXP_TIME = 2
# XYMのモザイクID (メインネットは0x6BED913FA20223F8)
MOSAIC_ID = 0x091F837E059AE13C
# トランザクションの送信先ノード(この例はテストネットのノード)
NODEURL = "http://sym-test-01.opening-line.jp:3000"
```

# トランザクションへの署名とノードへアナウンス

先ほど設定した条件で
1. トランザクションを生成
1. トランザクションへの署名
1. ノードへアナウンス
を行います。

```python
import datetime
import urllib.request
import json
from binascii import unhexlify, hexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.sym.KeyPair import KeyPair
from symbolchain.core.facade.SymFacade import SymFacade

facade = SymFacade('public_test')
keypair = KeyPair(PrivateKey(unhexlify(FROM_PRIVATEKEY)))

# トランザクションの有効期限
deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=EXP_TIME)).timestamp()) - BIRTHTIME) * 1000

# トランザクション内容
tx = facade.transaction_factory.create({
  'type': 'transfer',
  'signer_public_key': keypair.public_key,
  'fee': MAX_FEE,
  'deadline': deadline,
  'recipient_address': SymFacade.Address(TO_ADDRESS),
  'mosaics': [(MOSAIC_ID, AMOUNT)],
  'message': MESSAGE
})

# トランザクションに署名
signature = facade.sign_transaction(keypair, tx)
tx.signature = signature.bytes
tx_hash = facade.hash_transaction(tx)

# ノードへアナウンス
payload = {"payload": hexlify(tx.serialize()).decode('utf8').upper()}
req = urllib.request.Request(NODEURL + "/transactions",
                             json.dumps(payload).encode(),
                             {'Content-type': 'application/json'},
                             method='PUT')
with urllib.request.urlopen(req) as res:
    print("tx hash:" + str(tx_hash))
    print("status code:" + str(res.getcode()))
```

正しいトランザクションがノードに受理された場合は、下記のように `202` がAPIから返ってきます。

```
tx hash:33FF21C6C86D519387D3669AB242CE0ADD6506DF4E6F5FDDAB03AF084DBCB257
status code:202
```

# トランザクションの承認状況の確認

先ほど生成したトランザクションのハッシュ値(ここでの例だと`33FF21C6C86D519387D3669AB242CE0ADD6506DF4E6F5FDDAB03AF084DBCB257`)を使い、トランザクションの承認状況を問い合わせます。

```python
req = urllib.request.Request(NODEURL + "/transactionStatus/" + str(tx_hash))
with urllib.request.urlopen(req) as res:
    data = json.load(res)
    print(json.dumps(data, indent=2))
```

```
{
  "group": "confirmed",
  "code": "Success",
  "hash": "216C5629F9B10AB5C91673CBA221C61E22436731884FB1B2E3C13EC1C606DE43",
  "deadline": "9998763000",
  "height": "255648"
}
```

承認されるまでは`"group": "unconfirmed"`となります。
手数料の指定が小さいとトランザクションが混雑してきたときに承認されるまでの時間が長くなります。
無事に承認されると、`"group": "confirmed"`が返ってきます。これで送金が完了です。
[エクスプローラー](http://explorer.testnet.symboldev.network/transactions/216C5629F9B10AB5C91673CBA221C61E22436731884FB1B2E3C13EC1C606DE43)でも確認できます。



# トランザクション内容の確認

[前々回の記事](https://qiita.com/nobu_kyutech/items/690ac286f27a3a84e57f)を参考に、トランザクション詳細やメッセージの確認を行います。

```python
# メッセージ確認
req = urllib.request.Request(NODEURL + '/accounts/' + TO_ADDRESS)
with urllib.request.urlopen(req) as res:
    accountInfo = json.load(res)
ADDRESS48 = accountInfo['account']['address']

url = NODEURL + '/transactions/confirmed'
params = {
    'address': TO_ADDRESS,
    'order': 'desc',
}

req = urllib.request.Request('{}?{}'.format(url, urllib.parse.urlencode(params)))
with urllib.request.urlopen(req) as res:
    data = json.load(res)

for d in data['data']:
    mosaic = None
    # モザイクの送受信があるか
    if "mosaics" in d['transaction']:
        for xym in d['transaction']['mosaics']:
            message = ""
            if "message" in d['transaction']:
                message = d['transaction']['message']
            
            if xym['id'] == '091F837E059AE13C':
                mosaic = xym['amount']
            else:
                continue
            # 受け取り人が自分であるか
            if d['transaction']['recipientAddress'] == ADDRESS48:
                isRecipient = True
            else:
                isRecipient = False
            print("height:{}, {}, {}xym, message[{}]".format(d['meta']['height'], '受信' if isRecipient else "送信", int(mosaic)/1000000, unhexlify(message).decode('utf-8')))
```

```
height:255648, 受信, 2.0xym, message[I love symbol. こんにちは]
height:255642, 受信, 1.0xym, message[I love symbol. こんにちは]
height:251422, 受信, 1.0xym, message[I love symbol.こんちゃす]
height:251381, 受信, 1.0xym, message[I love symbol.]
height:251373, 受信, 1.0xym, message[I love symbol.]
height:248840, 送信, 1.0xym, message[hello]
height:248824, 受信, 300.0xym, message[]
```
上記のように直近の送受信とメッセージが表示されれば取得成功です。

**全コードは[こちら](https://github.com/ancte0721/symbol-python-training/blob/master/04_mosaicTransfer.ipynb)**

# 参考情報
- [PythonでSymbolブロックチェーンの送金プログラムを書いてみる。](https://qiita.com/nem_takanobu/items/f3c02caa17ad385b6155)