NEMスーパーノードプログラム　毎月のエンロールメントを楽する。

## 毎月のエンロールメントがめんどくさい
ってことで、chatGPTさんというか、perplexityに相談。

もう、どうやって構築していったのかは、忘れたけれど、Pythonが得意らしいので以下のスクリプトを組んでもらった。
各個人の環境で動くかどうかは分かりません。動かないときは都度、AIさんにエラー見せて対応方法を提示してもらったと思う。

※秘密鍵が必要なので、原則的にセキュリティには気を付けてください。


```python
#!/usr/bin/env python3
import random
from symbolchain.nem.KeyPair import KeyPair
from symbolchain.facade.NemFacade import NemFacade
from symbolchain.CryptoTypes import PrivateKey
from symbolchain.nem.Network import NetworkTimestamp
from binascii import unhexlify
import requests
import json

facade = NemFacade('mainnet')

# Sender data: private key and nodehost (replace with your actual data)
sender_data = [
    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem01a.symbol-node.com"
    },
    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem02.symbol-node.com"
    },
    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem03.symbol-node.com"
    },
]

# Multiple node URLs
node_urls = [
    'https://nem01a.symbol-node.com:7891',
    'https://nem02.symbol-node.com:7891',
    'https://nem03.symbol-node.com:7891',
    'https://nem04.symbol-node.com:7891',
    'https://nem05.symbol-node.com:7891',
    'https://nem06.symbol-node.com:7891',
    'https://nem01.neluta.win:7891',
    'https://nem02.neluta.win:7891',
    'https://nem03.neluta.win:7891',
    'https://nem04.neluta.win:7891',
    'https://nem05.neluta.win:7891',
]

def get_random_node():
    return random.choice(node_urls)

def get_codeword(public_key):
    url = f"https://nem.io/supernode/api/codeword/{public_key}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.text.strip()
    else:
        raise Exception(f"Failed to get codeword for public key {public_key}")

def send_transaction(sender_private_key, recipient_address, amount, message):
    node_url = get_random_node()
    post_string = f"{node_url}/transaction/announce"
    time_string = f"{node_url}/time-sync/network-time"

    # Obtain network time
    networkTimeRequest = requests.get(time_string)
    networkTime = NetworkTimestamp(int(networkTimeRequest.json()['sendTimeStamp'] / 1000))
    
    # Set the deadline to 1 hour in the future
    deadline = networkTime.add_hours(1).timestamp

    senderKeypair = NemFacade.KeyPair(sender_private_key)
    senderPubkey = senderKeypair.public_key
    senderAddress = facade.network.public_key_to_address(senderPubkey)

    # Create a transfer transaction
    transfer = facade.transaction_factory.create({
        'type': 'transfer_transaction_v1',
        'signer_public_key': senderPubkey,
        'recipient_address': recipient_address,
        'deadline': deadline,
        'message': {
            'message_type': 'plain',
            'message': message
        },
        'amount': amount,
        'fee': 200000,  # 0.2 XEM fee
        'timestamp': networkTime.timestamp
    })

    # Sign the transaction and attach the signature
    signature = facade.sign_transaction(senderKeypair, transfer)
    tx = facade.transaction_factory.attach_signature(transfer, signature)

    # Announce the transaction to the network
    r = requests.post(post_string, json=json.loads(tx))
    return r, node_url

def send_transaction_with_retry(sender_private_key, recipient_address, amount, message, max_retries=3):
    for attempt in range(max_retries):
        try:
            response, used_node = send_transaction(sender_private_key, recipient_address, amount, message)
            if response.status_code == 200:
                return response, used_node
            else:
                print(f"Attempt {attempt + 1} failed. Status code: {response.status_code}")
        except Exception as e:
            print(f"試行回数 {attempt + 1} : {str(e)}")

        if attempt < max_retries - 1:
            print("別のノードへ接続をリトライ...")
    
    raise Exception("Failed to send transaction after maximum retries")

def main():
    # Ask for the recipient address
    recipient_address = input("エンロールメントアドレスを入力してください。: ").strip()

    # Validate the recipient address (you may want to add more robust validation)
    if not recipient_address or len(recipient_address) != 40:
        print("無効な受信者アドレスです。有効なNEMアドレスを入力してください。")
        return

    print(f"エンロールメントアドレスへ送信: {recipient_address}")
    print("-------------------------------------------")

    # Send transactions from multiple senders
    for i, sender in enumerate(sender_data):
        sender_key = sender["private_key"]
        nodehost = sender["nodehost"]

        # Derive public key from private key
        key_pair = KeyPair(sender_key)
        public_key = key_pair.public_key

        # Get the codeword directly from the API
        codeword = get_codeword(public_key)

        # Create the message
        message = f"enroll {nodehost} {codeword}"

        amount = 0  # Sending 0 XEM
        try:
            response, used_node = send_transaction_with_retry(sender_key, recipient_address, amount, message)
            print(f"Transaction {i+1}:")
            print(f"  Status Code: {response.status_code}")
            print(f"  Response: {response.json()}")
            print(f"  送信メッセージ: {message}")
            print(f"  使用ノード: {used_node}")
        except Exception as e:
            print(f"Transaction {i+1} {nodehost} リトライに失敗: {str(e)}")
        print()

if __name__ == "__main__":
    main()
```



以下の部分の*****部分が秘密鍵です。で、登録するhost名か、IPアドレスを間違わないように記述してください。このセットでいくつでも追加できます。
※ここの秘密鍵はアドレス自身の秘密鍵です。委任秘密鍵ではないです。
```python
    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem01a.symbol-node.com"
    },
```


以下の部分は送信時に接続するノードです。複数にしているのは負荷分散の為。どこのノードでもいいですよ。
```python
# Multiple node URLs
node_urls = [
    'https://nem01a.symbol-node.com:7891',
    'https://nem02.symbol-node.com:7891',
    'https://nem03.symbol-node.com:7891',
    'https://nem04.symbol-node.com:7891',
    'https://nem05.symbol-node.com:7891',
    'https://nem06.symbol-node.com:7891',
    'https://nem01.neluta.win:7891',
    'https://nem02.neluta.win:7891',
    'https://nem03.neluta.win:7891',
    'https://nem04.neluta.win:7891',
    'https://nem05.neluta.win:7891',
]
```

スクリプトを実行すると、エンロールメント用のアドレスを聞いてきますので、最新のアドレスを入力し、エンターで実行されます。

※あと、手数料を0.2xem固定にしてますが、host名が長いと足らないかもです。

ということで、月一回のイベントですがこれで楽ができます。

くれぐれも言いますが、セキュリティは本当に気を付けてください。秘密鍵が平文で羅列してるだけですので。何か良い方法があれば教えて。
私は、外部からはアクセスできないサーバー内より実行してます。