Node.js + Web3.jsを使用した機能検証用アドレス新規生成、残高照会、送金

はじめに
--
本記事はLinux／Unixサーバーを対象にGUIを使用できない環境で機能検証するためにEthereumまたはEthereum互換の別チェーン（例：Polygon、Fantomなど）上にアドレス新規生成、残高照会、送金の確認手順を解説します。ブロックチェーンの機能検証にお時間を多くかけたくない方におすすめです。

本記事では、Japan Open Chainという日本発のブロックチェーンをサンプルとして使用します。

※ Japan Open Chainについてはこちらの記事をご参考ください。
→ <a href="https://note.com/joc_official/n/n7c20014a5fa9">解説レポート〜「1分でわかる！Japan Open Chain」</a>

必要なソフトウェア：
・Linux／Unix環境（本記事ではUbuntuを使用する）
・Node.js／npm
・ether.js
・fs.js
・readline.js
・path.js
・dotenv.js
・Web3.js
・秘密鍵（Metamaskなどからエクスポートしたものを使用）
・RPCサーバーURL (または別のETH互換チェーン)

※ 対象ブロックチェーンのRPCサーバーURLが不明な場合は下記サイトをご活用ください。
→ <a href="https://chainlist.org/"> [Chainlist] Helping users connect to EVM powered networks</a>

環境構築
--
手順1. Node.js と npm のインストール
--
まず、Node.jsとnpmがインストールされているか確認します。
```
which node
which npm
```
インストールされていない場合は、以下のコマンドでインストールします。
```
sudo apt update
sudo apt install nodejs npm
```

手順2. プロジェクトディレクトリ作成
--
以下のコマンドで任意のディレクトリを作成して移動します。
```
mkdir tx-test && cd tx-test
```

手順3. プロジェクト初期化
--
プロジェクトの初期化を行います。
```
npm init -y
```

手順4. 必要な依存関係のインストール
---
以下のコマンドでインストールします。
```
npm install web3
npm install path
npm install fs
npm install dotenv
npm install readline
npm install -g solc
```
アドレス新規生成
--
こちらのサンプルコードはアドレスを新規作成し、アドレスと秘密鍵をテキストファイルに保存するためのものです。

xxx_generate_address.jsというファイルと作成し、以下のスクリプトを記述します。
```joct_generate_address.js
// Web3.jsをインポート
const { Web3 } = require('web3');
// ファイルシステムモジュールをインポート
const fs = require('fs');  
// パス操作のために path モジュールをインポート
const path = require('path');  

// Web3.js インスタンスを生成
const rpcURL = 'https://rpc-3.testnet.japanopenchain.org'; // RPCサーバーのURLを書き換え
const web3 = new Web3(new Web3.providers.HttpProvider(rpcURL));

// 新しいウォレットを作成
const newWallet = web3.eth.accounts.create();

// 現在の時間を取得
const now = new Date();
const timestamp = now.toISOString().replace(/[:.]/g, '-');

// 出力内容をコンソールに表示
console.log('Execution Time:', timestamp);
console.log('New Wallet Address:', newWallet.address);
console.log('Private Key:', newWallet.privateKey);

// ファイル名を生成
const fileName = `${timestamp}-joct-generate-address-log.txt`;

// ファイルに出力内容を書き込む
const output = `Execution Time: ${timestamp}\nNew Wallet Address: ${newWallet.address}\nPrivate Key: ${newWallet.privateKey}\n`;

// 'log' フォルダ内にファイルを保存するためのパスを作成
const folderPath = path.join(__dirname, 'log');
const filePath = path.join(folderPath, fileName);

// 'log' フォルダが存在しない場合は作成
if (!fs.existsSync(folderPath)) {
  fs.mkdirSync(folderPath);
}

// '`${timestamp}-wallet-info.txt' というファイルに書き込む
fs.writeFile(filePath, output, (err) => {
  if (err) {
    console.error('Error writing to file', err);
  } else {
    console.log(`Wallet information saved to ${filePath}`);
  }
});
```
記述できたら、以下のコマンドを実行します。
```
node joct_generate_address.js // 各自のjsファイル名に書き換えてください
```
下記内容が出力されたら、アドレス新規生成確認は完了です。
```
Execution Time: 1234556
New Wallet Address: 0x1343423423523523
Private Key: 0x3cer44544545454545454
```





残高照会
--
こちらのサンプルコードは対象アドレスの残高を確認し、ウォレットアドレスと残高をテキストファイルに保存するためのものです。

xxx_get_balance_account_rl.jsというファイルと作成し、以下のスクリプトを記述します。

```joct_get_balance_rl.js
// Web3.jsをインポート
const { Web3 } = require('web3');
// 標準入力を受け取るために readline モジュールをインポート
const readline = require('readline'); 
// ファイルシステムモジュールをインポート
const fs = require('fs');
// パス操作のために path モジュールをインポート
const path = require('path');  

// Web3.js インスタンスを生成
const rpcURL = 'https://rpc-3.testnet.japanopenchain.org'; // RPCサーバーのURLを書き換え
const web3 = new Web3(new Web3.providers.HttpProvider(rpcURL));

// readlineインターフェースを作成
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

// ウォレットの残高を取得する関数
async function getBalance(walletAddress) {
   try {
       const balance = await web3.eth.getBalance(walletAddress);
       const balanceInEther = web3.utils.fromWei(balance, 'ether');
       console.log('Balance in JOCT:', balanceInEther);
       console.log('Balance in Wei:', balance);
       
       // 出力内容をファイルに書き込む
       const now = new Date();
       const timestamp = now.toISOString().replace(/[:.]/g, '-');
       const output = `Wallet Address: ${walletAddress}\nBalance in JOCT: ${balanceInEther}\nBalance in Wei: ${balance}\n`;
       
       // 'log' フォルダ内にファイルを保存するためのパスを作成
       const folderPath = path.join(__dirname, 'log');
       const fileName = `${timestamp}-joct-get-balance-log.txt`;
       const filePath = path.join(folderPath, fileName);

       // 'log' フォルダが存在しない場合は作成
       if (!fs.existsSync(folderPath)) {
         fs.mkdirSync(folderPath);
       }

       fs.writeFile(filePath, output, (err) => {
         if (err) {
           console.error('Error writing to file', err);
         } else {
           console.log(`Balance information saved to ${filePath}`);
         }
       });

   } catch (error) {
       console.error('Error getting balance:', error);
   }
}

// ユーザーにウォレットアドレスを入力させる
rl.question('Please enter your wallet address: ', (walletAddress) => {
    getBalance(walletAddress);
    // 入力が終わったらインターフェースを閉じる
    rl.close();  
});
```
記述できたら、以下のコマンドを実行します。
```
node joct_get_balance_rl.js // 各自のjsファイル名に書き換えてください
```
下記内容が出力されたら、残高照会確認は完了です。
```
Wallet Address: 0x00000000000000A
Balance in JOCT: 1JOCT
Balance in Wei: 100000000000000
```



送金
--
こちらのサンプルコードは対象アドレスから指定した額を送金先アドレスに送金し、ログをテキストファイルに保存するためのものです。

xxx_send_transaction_rl.jsというファイルと作成し、以下のスクリプトを記述します。

```joct_send_transaction_rl.js
// Web3.jsをインポート
const { Web3 } = require('web3');
// ファイルシステムモジュールをインポート
const fs = require('fs');
// パス操作のために path モジュールをインポート
const path = require('path');  
// 標準入力を受け取るために readline モジュールをインポート
const readline = require('readline');  

// Web3.js インスタンスを生成
const rpcURL = 'https://rpc-3.testnet.japanopenchain.org'; // RPCサーバーのURLを書き換え
const web3 = new Web3(new Web3.providers.HttpProvider(rpcURL));

// readlineインターフェースを作成
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

// トランザクションを送信する関数
async function sendTransaction(senderAddress, privateKey, receiverAddress, amount) {
   try {
       // nonce を取得
       const nonce = await web3.eth.getTransactionCount(senderAddress, 'latest');

       // トランザクションオブジェクトを作成
       const tx = {
           from: senderAddress,
           to: receiverAddress,
           value: web3.utils.toWei(amount, 'ether'), // 必要に応じて修正
           gas: 2000000, // 必要に応じて修正
           gasPrice: await web3.eth.getGasPrice(),
           nonce: nonce,
           chainId: 10081 // 必要に応じて修正
       };

       // トランザクションに署名
       const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);

       // トランザクションを送信
       const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
       console.log('Transaction receipt:', receipt);

       // 出力内容をファイルに書き込む
       const now = new Date();
       const timestamp = now.toISOString().replace(/[:.]/g, '-');
       const output = `
SenderAddress: ${senderAddress}\n
PrivateKey: ${privateKey}\n
ReceiverAddress: ${receiverAddress}\n
Amount: ${amount}\n
Timestamp: ${timestamp}\n
Transaction Receipt:\n
    Transaction Hash: ${receipt.transactionHash}\n
    Block Number: ${receipt.blockNumber}\n
    Status: ${receipt.status ? 'Success' : 'Failure'}\n
    Gas Used: ${receipt.gasUsed}\n
    Cumulative Gas Used: ${receipt.cumulativeGasUsed}\n
    Logs: ${JSON.stringify(receipt.logs, null, 2)}\n
       `;
       
       // 'log' フォルダ内にファイルを保存するためのパスを作成
       const folderPath = path.join(__dirname, 'log');
       const fileName = `${timestamp}-joct-send-transaction-log.txt`;
       const filePath = path.join(folderPath, fileName);

       // 'log' フォルダが存在しない場合は作成
       if (!fs.existsSync(folderPath)) {
         fs.mkdirSync(folderPath);
       }

       fs.writeFile(filePath, output, (err) => {
         if (err) {
           console.error('Error writing to file', err);
         } else {
           console.log(`Transaction receipt saved to ${filePath}`);
         }
       });

   } catch (error) {
       console.error('Error sending transaction:', error);
   }
}

// ユーザーから入力を受け取るプロンプトを作成
function promptUser() {
    rl.question('Please enter sender address: ', (senderAddress) => {
        rl.question('Please enter private key: ', (privateKey) => {
            rl.question('Please enter receiver address: ', (receiverAddress) => {
                rl.question('Please enter send amount : ', (amount) => {
                // トランザクションを送信
                sendTransaction(senderAddress, privateKey, receiverAddress, amount);
                // readlineインターフェースを閉じる
                rl.close();
               });
            });
        });
    });
}

promptUser();
```
記述できたら、以下のコマンドを実行します。
```
node joct_send_transaction_rl.js // 各自のjsファイル名に書き換えてください
```
下記内容が出力されたら、送金確認は完了です。
```
SenderAddress: 0x0000000000000A
PrivateKey: 0xxxxxxxxxxxxxxxxxxxxxA
ReceiverAddress: 0x0000000000000B
Amount: 0.01JOC
Timestamp: 17582142124
Transaction Receipt:
    Transaction Hash: 0x00000sdasdfn ajnsfjafa
    Block Number: 12
    Status: Success
    Gas Used: 100000000
    Cumulative Gas Used: 2000000
    Logs: XXXXXXXX
```

最後
---
本記事は対象ブロックチェーンの機能検証を実施するため、一番シンプルな検証用スクリプトをご紹介させていただきました。検証にかける時間を短縮したい方はお役に立てれば幸いです。