Pythonで複数アドレスにSymbol(xym)を一斉送金(グーグルスプレッドシート編)

みーちさんのスクリプトを参考にして、複数アドレスにSymbol(xym)を一斉送金する方法をご紹介しました。

上記の記事では、アドレスや送金数、メッセージをスクリプトの中に記載しましたが、今回は完結編ということで、やっとグーグルスプレッドシートからアドレス等を読み込んで動くようになっています。
今回の記事を読む前提として、前回の「グーグルスプレッドシートをPythonからから操作する準備 」を読んで、設定をしておいてください。
そうでないと、何のことだか分からないと思います。

ここからは、上記の2つの記事を参考に設定等は終わっているとして説明していきます。
まず、幾つか機能を追加してあります。大した機能ではありませんが、地味に便利かもしれません。
・ログファイルの生成
・実行の確認
それでは、以下のコードを元に設定をしていきたいと思います。

### Symbol #############################################################################
import sha3
import datetime
import json
import http.client

from binascii import unhexlify
from binascii import hexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.sym.KeyPair import KeyPair
from symbolchain.core.facade.SymFacade import SymFacade
from symbolchain.core.sym.MerkleHashBuilder import MerkleHashBuilder
from symbolchain.core.CryptoTypes import Hash256


### gspread #############################################################################
import gspread

#ServiceAccountCredentials:Googleの各サービスへアクセスできるservice変数を生成します。
from oauth2client.service_account import ServiceAccountCredentials 

#2つのAPIを記述しないとリフレッシュトークンを3600秒毎に発行し続けなければならない
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']


### ファイル操作等 ######################################################################
import os
import datetime

dt_now = datetime.datetime.now()
file_name = os.path.dirname(__file__) + '\log_' + dt_now.strftime('%Y%m%d%H%M%S') + ".txt"


### 設定 #############################################################################

# テストネット=0 メインネット=1
current_net = 0


# 送信のタイトル(この送信が何か分かるように付けるだけです。)
title = "ノード報酬還元"


# 送信元ウォレットの秘密鍵
private_key = "ここに秘密鍵を入力"


# トランザクション送信に使う手数料
fee_size    = 0.1


# 自分のノードのアドレス(折角なので自分のノードに繋ぎましょう。)
my_node_adr = "ngl-dual-001.symbolblockchain.io"


# 固定メッセージ=0 個別メッセージ=1
msg_type = 1

# 固定メッセージを選択した場合は以下のメッセージをみんなに送ります。
my_msg = "ここに固定のメッセージを入力"


# 固定XYM数=0 個別XYM数=1
xym_amount_type = 1

# 固定XYMを選択した場合は以下のXYM数をみんなに送ります。
my_xym_amount = 0.001


# 各データの列の設定(スプレッドシートの列番号を設定します)
# 例) A列=0、B列=1、C列=2、D列=3、E列=4、F列=5

#送金先の名前(認識するためなので、送金には使いません)
row_name = 1

#送金先のアドレス
row_adr = 2

#送金XYM数
row_xym_amount = 3

#送金先へのメッセージ
row_msg = 4

#送信先チェック(この列に1が入っていたら送金)
row_check = 5


#認証情報設定
#ダウンロードしたjsonファイル名をクレデンシャル変数に設定(秘密鍵、Pythonファイルから読み込みしやすい位置に置く)
json_file = 'ここにjsonファイル名を入力'


#共有設定したスプレッドシートキーを変数[SPREADSHEET_KEY]に格納する。
SPREADSHEET_KEY = 'ここにスプレッドシートキーを入力'

#スプレッドシートのシート名
my_sheet = 'シート1'

#####################################################################################

# 接続するネットワークの設定
if current_net == 0:
	facade = SymFacade('public_test')
	start_time = 1616694977
	mosaics_id = 0x091F837E059AE13C
	node_adr = "sym-test-01.opening-line.jp"

elif current_net == 1:
	facade = SymFacade('public')
	start_time = 1615853185
	mosaics_id = 0x6BED913FA20223F8
	node_adr = my_node_adr

#-----------------------------------------------------

json_file = os.path.dirname(__file__) + "\\" + json_file
credentials = ServiceAccountCredentials.from_json_keyfile_name(json_file, scope)


#OAuth2の資格情報を使用してGoogle APIにログインします。
gc = gspread.authorize(credentials)

#共有設定したスプレッドシートを開く
worksheet = gc.open_by_key(SPREADSHEET_KEY).worksheet(my_sheet)


#シートの全てのセルの値を受け取る
import_value = worksheet.get_all_values()

#-----------------------------------------------------
f = open(file_name,'w')
str_s = "「" + str(title) + "」\n\n"
print(str_s.replace('\n',''))
f.write(str_s)


# 送信元の設定
alicePrikey     = PrivateKey(unhexlify(private_key))
aliceKeypair    = KeyPair(alicePrikey)
alicePubkey     = aliceKeypair.public_key
aliceAddress    = facade.network.public_key_to_address(alicePubkey)

str_s = "送信元:" + str(aliceAddress) + "\n\n"
print(str_s.replace('\n',''))
f.write(str_s)

total_xym = 0
total_counts = 0

# 受信先の設定
addressTx = []
for address in import_value:
   if address[row_check] == "1":
       name = address[1]
       my_adr = address[row_adr]
       if xym_amount_type == 1:
           my_xym_amount = float(address[row_xym_amount])
       if msg_type == 1:
           my_msg = address[row_msg]

       tx  = facade.transaction_factory.create_embedded({
           'type': 'transfer',
           'signer_public_key': alicePubkey,
           'recipient_address' : SymFacade.Address(my_adr.replace('-','')),
           'mosaics': [(mosaics_id, int(my_xym_amount * 1000000))],
           'message': bytes(1) + my_msg.encode('utf8')
       })
       addressTx.append(tx)

       total_xym = total_xym + my_xym_amount
       total_counts = total_counts + 1

       #トランザクションの内容を出力
       str_s = str(name) + "," + str(my_adr) + "," + str(my_xym_amount) + "," + str(my_msg.replace('\n','')) + "\n"
       print(str_s.replace('\n',''))
       f.write(str_s)

str_s = "送信件数:" + str(total_counts) + "\n" + "合計XYM数:" + str(total_xym) + "\n"
print(str_s)
f.write(str_s)


#実行の確認
while True:
  print('実行しますか?実行する場合はyを入力してください。それ以外はキャンセルします。')
  ipt_txt = input()
  if ipt_txt == 'y':
      run_tx = 1
      break
  else:
      run_tx = 0
      break


#現在時刻を取得
dt_now = datetime.datetime.now()


#実行の場合
if run_tx == 1:

  # マークルハッシュの作成
  hash_builder = MerkleHashBuilder()
  for tx in addressTx:
      hash_builder.update(Hash256(sha3.sha3_256(tx.serialize()).digest()))
  merkle_hash = hash_builder.final()

  # アグリゲートトランザクションの作成
  deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - start_time) * 1000
  aggregate = facade.transaction_factory.create({
      'type': 'aggregateComplete',
      'signer_public_key': alicePubkey,
      'fee': int(fee_size * 1000000),
      'deadline': deadline,
      'transactions_hash': merkle_hash,
      'transactions': addressTx
  })
  signature = facade.sign_transaction(aliceKeypair, aggregate)
  aggregate.signature = signature.bytes

  # ネットワークへアナウンス
  payload = {"payload": hexlify(aggregate.serialize()).decode('utf8').upper()}
  jsonPayload = json.dumps(payload)
  headers = {'Content-type': 'application/json'}
  conn = http.client.HTTPConnection(node_adr, 3000)
  conn.request("PUT", "/transactions", jsonPayload, headers)
  response = conn.getresponse()

  str_s = str(response.status) + "," + str(response.reason) + "\n"
  print(str_s.replace('\n',''))
  f.write(str_s)

  # 確認
  hash = facade.hash_transaction(aggregate)
  str_s = 'http://' + str(node_adr) + ':3000/transactionStatus/' + str(hash) + "\n"
  print(str_s.replace('\n',''))
  f.write(str_s)

  str_s = "実行しました\n" + str(dt_now)
  print(str_s)
  f.write(str_s)

else:
  str_s = "キャンセルしました\n" + str(dt_now)
  print(str_s)
  f.write(str_s)

f.close()

まずは上記のスクリプトをコピーしていただいて、ここから設定していきます。みーちさんのスクリプトと同様に「設定」の部分のみで大丈夫です。(なハズ)
なお、設定の際に数字を入力する時には必ず半角の数字を入力してください。

まずはテストネットとメインネットを選択します。テストネットの場合は0、本番のメインネットの場合は1を入力してください。

# テストネット=0 メインネット=1
current_net = 0

次にこの送信のタイトルを設定します。何用の送信かを自分で識別する為用なので、何でも大丈夫です。

# 送信のタイトル(この送信が何か分かるように付けるだけです。)
title = "ノード報酬還元"

次に秘密鍵の入力です。テストネット用とメインネット用はそれぞれ別の秘密鍵だと思いますので、それに合わせたものを入力してください。

# 送信元ウォレットの秘密鍵
private_key = "ここに秘密鍵を入力"

次にトランザクション手数料です。本当ならば、ここも自動計算で入れたいところですが、そこまで分かっていないので、これは手入力でお願いします。少なすぎると時間切れになって送金できないようです。

# トランザクション送信に使う手数料
fee_size    = 0.1

次は自分のノードのアドレスです。このスクリプトをご利用になる方はノード運営者が多いのではないかと思います。折角ですから、ご自分のノードに繋いで送金すれば、嬉しいのではないでしょうか。ちなみにデフォルトではNGLのアドレスを入れてありますが、このままだと混んでいて送れないエラーが出ることもあるので、自分のノードが無い場合でも適当な方のノードの繋ぐ方が良いと思います。
ちなみに、テストネットの場合は固定のノードにしてありますので、ここの欄は無効となります。

# 自分のノードのアドレス(折角なので自分のノードに繋ぎましょう。)
my_node_adr = "ngl-dual-001.symbolblockchain.io"

次にメッセージの設定です。全員に同じメッセージを送る場合は0、スプレッドシートから個別のメッセージを読み込んで、それを送りたい場合には1を入力してください。
固定メッセージの場合は、下の段の「ここに固定メッセージを入力」という部分を変更してください。

# 固定メッセージ=0 個別メッセージ=1
msg_type = 1

# 固定メッセージを選択した場合は以下のメッセージをみんなに送ります。
my_msg = "ここに固定のメッセージを入力"

次に送金XYM数です。こちらも先ほどのメッセージと一緒で、固定の送金数の場合は0、スプレッドシートから読み込んで個別の数を送金したい場合は1を入力してください。
そして、固定の場合は下の部分に送金数を設定してください。数字は半角でお願いします。

# 固定XYM数=0 個別XYM数=1
xym_amount_type = 1

# 固定XYMを選択した場合は以下のXYM数をみんなに送ります。
my_xym_amount = 0.001

次はスプレッドシート関係の設定です。ここでは、スプレッドシートのどの列から読み込むかを設定します。
一番左のA列が0、次のB列が1という感じになっています。「送信先の名前」はアドレスだけだと見づらいので識別用の名前などが入っている列を指定してください。そして「送信先チェック」というのは、送金先のフラグとなっていて、ここに1(半角)が入っている行のアドレスを読み込んで送金します。

# 各データの列の設定(スプレッドシートの列番号を設定します)
# 例) A列=0、B列=1、C列=2、D列=3、E列=4、F列=5

#送金先の名前(認識するためなので、送金には使いません)
row_name = 1

#送金先のアドレス
row_adr = 2

#送金XYM数
row_xym_amount = 3

#送金先へのメッセージ
row_msg = 4

#送信先チェック(この列に1が入っていたら送金)
row_check = 5

上記の例として、こちらのスプレッドシートを参考にしてみてください。

https://docs.google.com/spreadsheets/d/11vr8iHBn5wWvzjU3Qg13oSEICOiruhi_-MPZJtEvvBQ/edit?usp=sharing

最後にスプレッドシートに関する設定です。スプレッドシートに関しては、前回の部分を参考にしてください。jsonファイルはスクリプトと同じフォルダに入れてください。シート名は何もいじらなければ「シート1」だと思うので、そのままで大丈夫だと思います。「シート2」や違うシートを選択したい場合は、ここを設定してください。

#認証情報設定
#ダウンロードしたjsonファイル名をクレデンシャル変数に設定(秘密鍵、Pythonファイルから読み込みしやすい位置に置く)
json_file = 'ここにjsonファイル名を入力'


#共有設定したスプレッドシートキーを変数[SPREADSHEET_KEY]に格納する。
SPREADSHEET_KEY = 'ここにスプレッドシートキーを入力'

#スプレッドシートのシート名
my_sheet = 'シート1'

一応、上記の設定がキチンと出来ていれば動くと思います。
実行すると、送付先の一覧と件数、合計XYM数などが表示されますので、確認してオッケーならば「y(半角)」を入力してEnterをしてください。
無事にトランザクションが送信できれば、202の実行結果が出ると思います。キャンセルする場合には、y以外の何かを入力すれば、キャンセルになります。

実行の場合も、キャンセルの場合も、同じフォルダにログファイルが作成されます。これを見ればどこの送金したかが記録として残りますので、少しはお役に立つかもしれません。

いろいろと設定等が煩雑ですが、設定が完了して使い慣れれば比較的便利に使えるのではないかと思います。宜しければ、ご活用ください。

ファイルをダブルクリックで実行

既にご存じの方も多くいると思いますが、Windowsの場合にはpythonファイル(.py)をダブルクリックすると、そのスクリプトを実行してくれます。実行するとコマンドプロンプトが立ち上り、実行後にすぐにコマンドプロンプト自体が消えてしまうため、実態があまり見えずに不安でそのような使い方をしていた方は少ないのではないでしょうか。
今回、ログファイルの書き出しと実行の確認を追加したことで、ダブルクリックでの実行も使いやすくなるのではないかと思います。ただ、あまり送信先が多いとコマンドプロンプトでは上の方が消えてしまいますが、、、。
そのような使い方も出来るので、宜しければ試してみてください。

注意事項

スプレッドシートにてフィルタを使う事で送信一覧の編集も楽になるのではないかと想定しています。
例えば、「種別」という列を作り1はノード1、2はノード2、と言うように分分けたり、イベント毎のアドレスが分かるようにするなどが考えられると思いますが、注意が必要なのはフィルタにて絞り込みをしている場合にも、隠れたセルの「チェック」の欄に1が入っていると、そこは送信先に含まれてしまいます。なので、一度全てフィルタをオフにしてチェックを外してから送信先を選択する方が間違いないと思います。

免責事項

これまでご紹介して来ましたスクリプトについて、ご利用の際にはあくまで自己責任の範囲でご利用ください。Symbolはこのように外部ファイルから簡単に操作が出来てしまいますが、内容が金銭に関わるものなので、テストネットにて十分にテストの上でご利用ください。
皆さんの一括送金のお役に少しでも立てば嬉しいです。

Special Thanks

思ったよりも時間が掛ってしまいましたが、みーちさんXEMbookさんたぬハックさんの記事を参考に何とかここまで出来ました。ありがとうございました。

https://nemlog.nem.social/blog/62080

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

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

https://tanuhack.com/operate-spreadsheet/


この記事が気に入ったらサポートをしてみませんか?