Explorer(XDCScan)は何を表示しているの？

ブロックチェーンのトランザクションを確認するのに欠かせないExplorer。
XDCネットワークだと[XDCScan](https://xdcscan.io/)になるけど、正直、何書いてあるかよくわからない。

ブロックチェーンを理解する上でも、ブロックチェーンに何が記録されているのか、実際に記録されたデータはどのように格納されて、どうやって情報を得られるのか、Explorerと同じ値が自分で取れたら分かるんじゃないの？という軽いノリでやってみます。

:::note alert
前提
* 所詮素人。間違いはいっぱいあると思う。
* 同じブロックチェーン上のアドレスでも、ウォレット（EOA）とコントラクトは似て非なるもの（個人的感想）で、コントラクトの方が扱いやすい（と思っている）ので、今回の題材はXDCネットワークのネイティブオラクルであるPluginのデータフィード用のOracle Contract Address(OCA)をベースとする。
* サンプルとしてOCAのトランザクションを表示させるので、Plugiin Nodeをしていない人には同じ作業はしんどいかも。
* やったことは結果論であって、最適解かどうかはわからない。
:::

# ゴール
:::note
XDCScanと同じ情報を自力で取ってくる！
:::

## XDCScan
### Overview

![スクリーンショット 2024-11-02 19.11.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/0bf297e8-9447-6f35-c650-7e8cfb498845.png)

### Log
![スクリーンショット 2024-11-02 19.11.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/d0fd2c78-4ede-d746-1fba-dd31d9a921b8.png)

:::note
これらの値が自分で取ってこれるようになると、ブロックチェーンに記録された情報がきっと今より読めるようになると思う。
:::

# 環境準備
Nodejsを使っていくので、まずは環境構築。

## Nodejs環境セットアップ
```:nvmインストール
cd ~
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
```

```:環境設定反映
source ~/.nvm/nvm.sh
```

```:Nodejsインストール（このバージョンはPlugin Nodeに合わせている）
nvm install 18.12.0
nvm use 18.12.0
node --version
```

## 作業ディレクトリ作成
```:ディレクトリ名はなんでもOK
mkdir explorer_test
cd explorer_test
```

## 必要なNodejsパッケージインストール
```
npm install xdc3 readline-sync abi-decoder
```
:::note
```xdc3``` : ブロックチェーンと対話するためのライブラリ。```web3.js```のV1からフォークされている。```web3.js```でも問題ないが、最新のものは少しロジックの書き方が違うので注意。
```readline-sync``` : ターミナルからの入力を受け取るライブラリ。プログラムを対話式で動かせる。
```abi-decoder``` : Consensys（Metamaskの開発元）が提供しているブロックチェーンにハッシュ化されて記録されているデータを復元化してくれるライブラリ（超便利）
:::

# まずはOverview
まず、何をもとにデータを拾ってくるか・・・
一番手っ取り早いのは```Transaction Hash```(TxHash/TxID)。
TxHashはブロックチェーン上のトランザクションを識別する一意の番号。
特定の一意のデータを取得できるため、検証にはもってこい。

## OCAの履歴からTransaction Hashを1つ決める
![スクリーンショット 2024-10-31 19.32.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/f35dafd2-53fe-0be1-6e7f-98cb1abac394.png)

## Transaction情報を見てみる
### まずはプログラムを作る
```shell
nano explorer_1.js
```
:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_1.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);

    //explorer_1 表示してみる
    console.log(tx);
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる
```
node explorer_1.js
```
:::note
答え合わせ１
![スクリーンショット 2024-10-31 19.58.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/28073d35-49d8-1e2b-3a5b-3bfab20db3e5.png)

![スクリーンショット 2024-10-31 20.13.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/27828c0d-1c13-d5bc-b33c-f06c33ee6e47.png)
7つの情報を得ることができた。
:::

:::note
答え合わせ２
![スクリーンショット 2024-10-31 21.30.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/c541f26b-1676-dd70-ddca-1b128ebca183.png)
![スクリーンショット 2024-10-31 21.36.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/2a1fefe9-aeb8-5849-5c1b-f9a2411df788.png)
5つの情報を得ることができた。
:::

### （まとめ）Transactionの詳細を見ると何がわかるか
```js:これを使えば詳細情報を取得できる
eth.getTransaction(TxHash)
```

:::note
Transaction Hashをキーとして、トランザクションの詳細情報を確認できる。

* ```Transaction Hash```:トランザクションハッシュ。トランザクションごとに一意に振られるID
* ```Method```:実行されたトランザクションメソッドのシグネチャ（署名）←Input Dataの頭10文字(0x + 8バイト）
* ```Block```:トランザクションが格納されているブロック番号
* ```from```:トランザクションの送信アドレス（今回だとOCAにフィード値を送り込んだNode Address）
* ```to```:トランザクションの受信アドレス（今回だとOCA）
* ```value```:送金されたネイティブトークン（XDC）
* ```Gas Price```:ガスプライス（XDC2.0になったので、12.5Gweiに設定されている）
* ```Gas Limit```:トランザクションガスリミット。Contractを実行する際、消費する最大ガス量。Contract実行時にこれ以上のガスを消費する場合、エラーになる。※これとは別にブロックガスリミットもある。これはBlockに格納されるTransaction全体の最大ガス量。ブロックガスリミットを超えない分だけのTransactionが1つのBlockに収まる
* ```Txn Type```:トランザクションタイプ。ややこしそうなので割愛。EIP1559で検索すると良いかも
* ```Nonce```:そのアドレスで実行されたトランザクションの総数
* ```Position```:（=transactionIndex）同じブロック内に格納されたトランザクションの位置（何番目のトランザクションか）
* ```Input Data```:トランザクションで送信されたデータ
:::

### ここまででまだ見つけられていない情報

![スクリーンショット 2024-11-01 22.40.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/1ffb02e2-b805-5ca5-704f-48788fb2a03d.png)

:::note
* ```Timestamp```:トランザクションが生成された日時
* ```Transaction Action```:トランザクションの処理（関数名）
* ```Usage by Txn```:トランザクションで使用されたガス量
:::

## Block情報を見てみる
BlockはTransactionを束ねたもの。複数のTransactionをBlockの単位でブロックチェーンに記録する。
## まずはプログラムを作る
```
nano explorer_2.js
```
:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_2.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);

    // TxHashが格納されているブロックの情報を取得
    const block = await xdc3.eth.getBlock(tx.blockNumber);

    //explorer_2 表示してみる
    console.log(block);

    //timestampの答え合わせ
    const blockDate = new Date(block.timestamp * 1000);
    console.log('timestamp: ' + blockDate);
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる
```
node explorer_2.js
```
:::note
答え合わせ
![スクリーンショット 2024-10-31 23.08.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/dbcef7ba-c640-1d06-12e6-11135c07df54.png)
![スクリーンショット 2024-10-31 23.17.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/fd8bdcd2-6d0a-9232-a2cf-862a534c7154.png)
UTCの違いはあるが、同じ時間を取得できた。
XDCScanに表示されているのは、ブロックが生成された時間。
:::

## Transaction Receipt情報を見てみる
トランザクションレシートはブロックチェーンにおけるトランザクションの実行結果を記録したもの。
### まずはプログラムを作る
```
nano explorer_3.js
```
:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_3.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_3 表示してみる
    console.log(txrec);
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる

```
node explorer_3.js
```

:::note
答え合わせ
![スクリーンショット 2024-11-01 21.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/b25e5425-c11b-a378-8ac5-dd932adb6792.png)
![スクリーンショット 2024-11-01 21.59.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/f85e6823-2548-8000-59ca-96e6b1e9d0a8.png)

![スクリーンショット 2024-11-01 22.19.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/06a5781a-6902-e13c-d2b2-cbbf47aa5dd7.png)
:::
:::note warn
念の為確認
①Status
トランザクションエラーでの確認
![スクリーンショット 2024-11-01 22.28.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/5af0ef63-74e7-a768-86b7-eef367917cda.png)
![スクリーンショット 2024-11-01 22.03.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/0f99e991-8dbb-19c2-dd29-cd5756352cb5.png)
正解。
```Status : true``` はXDCScanでは```Success```
```Status : false``` はXDCScanでは```Failed```

②Usage by Txn
違うトランザクションハッシュを確認。
![スクリーンショット 2024-11-01 22.17.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/0786bc75-5af7-9468-2394-430fa6e193ff.png)
![スクリーンショット 2024-11-01 22.16.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/2a4b24f8-314b-56b8-f633-1ffd161e2e1d.png)
正解。
XDCScanに表示されるのは```gasUsed```
```cumulativeGasUsed```ではない。

```cumulativeGasUsed```は**このトランザクションに関連する処理全体で使われたGasの量**だそうです。ものによってはInternal Transactionなど関連するトランザクションも含めた総量らしいです。もう少し勉強が必要なようです。
:::

:::note
ここまでで、Overviewのほとんどの情報が取得できました。
まだ得られていない情報はあと1つ
```Transaction Action```:トランザクションの処理（関数名）
![スクリーンショット 2024-11-01 22.46.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/0b34cf06-8ecb-5d31-0e26-5cc5c2d9dd4a.png)
いったんこれは後で、Logsの方へ移ります。
:::

# 次にLogs
:::note
実は```getTransactionReceipt```ではLogsも取得できています。
:::
![スクリーンショット 2024-11-01 22.53.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/d12acd4c-2c8b-a33b-ead3-d0d2b6ca549a.png)

![スクリーンショット 2024-11-01 22.56.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/71da328d-9631-4676-ffa7-23d194709fb3.png)

次に少し編集して見やすくしてみます。

### まずはプログラムを作る
```
nano explorer_4.js
```

:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_4.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_4 logだけ表示してみる
    txrec.logs.forEach((log) => {
        console.log(`Index: ${log.logIndex}`);
        console.log(`  Address: ${log.address}`);
        console.log(`  Topics:`);
        log.topics.forEach((topic, index) => {
            console.log(`    [${index}] ${topic}`);
        });
        console.log(`  Data(Hex): ${log.data}`);
    });
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる
```
node explorer_4.js
```
![スクリーンショット 2024-11-02 20.01.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/2a0556ea-3277-2404-eb18-18f03b51413c.png)
![スクリーンショット 2024-11-02 20.01.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/2614e8eb-d438-9e4a-8064-0d1463d6f7f3.png)


:::note
かなり見やすくなったと思います。
でも、ここでもやっぱり```Name```(関数名っぽい）が取得できません。
:::

# さて、関数名はどうやって取得するか
ここで使うのが、```abi-decoder```です。
実は、関数名はすでに取得した情報の中に含まれていました。

やっぱりまずはプログラムを作ってみます。

### まずはプログラムを作る（Overview）
今回は事前準備があります。
```abi-decoder```を使うためには、ContractのABI(Application Binary Interface)が必要になります。
ABIについてはググってくださいm(_ _)m
ABIはどこで手に入れるか。
今回の検証ではPluginのOCAをベースにしています。
```Remix```でOCA（Operator.sol)をコンパイルするところでGetできます。
![スクリーンショット 2024-11-02 0.59.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/80936c32-9a6c-0725-1850-5aaba81dd674.png)
これをクリックしてコピーしたものを使います。

```
mkdir ABIs
nano ABIs/Operator.json
```
:::note
RemixでコピーしたABIを貼り付けてください。
<details><summary>同じもの</summary>

```json:ABIs/Operator.json
[
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "pli",
				"type": "address"
			},
			{
				"internalType": "address",
				"name": "owner",
				"type": "address"
			}
		],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": false,
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "changedBy",
				"type": "address"
			}
		],
		"name": "AuthorizedSendersChanged",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			}
		],
		"name": "CancelOracleRequest",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "specId",
				"type": "bytes32"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "requester",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "callbackAddr",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "cancelExpiration",
				"type": "uint256"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "dataVersion",
				"type": "uint256"
			},
			{
				"indexed": false,
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "OracleRequest",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			}
		],
		"name": "OracleResponse",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "acceptedContract",
				"type": "address"
			}
		],
		"name": "OwnableContractAccepted",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferRequested",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferred",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": false,
				"internalType": "address[]",
				"name": "targets",
				"type": "address[]"
			},
			{
				"indexed": false,
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "changedBy",
				"type": "address"
			}
		],
		"name": "TargetsUpdatedAuthorizedSenders",
		"type": "event"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "targets",
				"type": "address[]"
			},
			{
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			}
		],
		"name": "acceptAuthorizedReceivers",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "ownable",
				"type": "address[]"
			}
		],
		"name": "acceptOwnableContracts",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "acceptOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunc",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			}
		],
		"name": "cancelOracleRequest",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "nonce",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunc",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			}
		],
		"name": "cancelOracleRequestByRequester",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address payable[]",
				"name": "receivers",
				"type": "address[]"
			},
			{
				"internalType": "uint256[]",
				"name": "amounts",
				"type": "uint256[]"
			}
		],
		"name": "distributeFunds",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "address",
				"name": "callbackAddress",
				"type": "address"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "data",
				"type": "bytes32"
			}
		],
		"name": "fulfillOracleRequest",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "address",
				"name": "callbackAddress",
				"type": "address"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "fulfillOracleRequest2",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "getAuthorizedSenders",
		"outputs": [
			{
				"internalType": "address[]",
				"name": "",
				"type": "address[]"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "getExpiryTime",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "getPluginToken",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			}
		],
		"name": "isAuthorizedSender",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "amount",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "onTokenTransfer",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "specId",
				"type": "bytes32"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "nonce",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "dataVersion",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "operatorRequest",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "specId",
				"type": "bytes32"
			},
			{
				"internalType": "address",
				"name": "callbackAddress",
				"type": "address"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "nonce",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "dataVersion",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "oracleRequest",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "owner",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "ownerForward",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "ownerTransferAndCall",
		"outputs": [
			{
				"internalType": "bool",
				"name": "success",
				"type": "bool"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			}
		],
		"name": "setAuthorizedSenders",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "targets",
				"type": "address[]"
			},
			{
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			}
		],
		"name": "setAuthorizedSendersOn",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "ownable",
				"type": "address[]"
			},
			{
				"internalType": "address",
				"name": "newOwner",
				"type": "address"
			}
		],
		"name": "transferOwnableContracts",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "transferOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "typeAndVersion",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "pure",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "recipient",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "amount",
				"type": "uint256"
			}
		],
		"name": "withdraw",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "withdrawable",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	}
]
```
</details>
:::

```
nano explorer_5.js
```

:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_5.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);

    //explorer_5 inputだけ表示してみる
    console.log('[Input]');
    console.log(tx.input);

    console.log();

    //Inputをデコード（復号化）
    const decodeInput = abiDecoder.decodeMethod(tx.input);
    console.log('[Decoded Input]');
    console.log(decodeInput);
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる（Overview）
```
node explorer_5.js
```
![スクリーンショット 2024-11-02 1.20.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/12de0ef6-c73d-4cf0-8e44-83f2bd332c6a.png)
![スクリーンショット 2024-11-02 1.25.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/d4ab9a60-6590-4905-5ce0-f50f867e28d1.png)

不思議ですが、16進数のデータを```abi-decoder```を通すと、全く別のものが出てきました。
これでOverviewの全ての値を取得することができました。

次はLogsです。

### まずはプログラムを作る（Logs）
```
nano explorer_6.js
```

:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_6.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_6 Logsだけ表示してみる
    console.log('[Logs]');
    console.log(txrec.logs);

    console.log();

    //Logsをデコード（復号化）
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);
    console.log('[Decoded Logs]');
    console.log(decodeLogs);
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる（Logs）
```
node explorer_6.js
```
![スクリーンショット 2024-11-02 8.13.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/ea11ccf8-55b8-1695-4e92-11d3addf8a28.png)
![スクリーンショット 2024-11-02 8.13.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/59388280-728e-7088-1107-aea3ebb57196.png)

![スクリーンショット 2024-11-02 8.16.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/c3a6841a-5caf-e606-3240-b8df2fe99a4b.png)

:::note warn
3つのLogのうち1つ目しか取得できませんでした。

それぞれのLogのAddressを見てみると・・・
1つ目のAddressは今まで追いかけてきたOCAのものです。
2つ目、3つ目は・・・Oracleにリクエストを出してきた```APIConsumer```のコントラクトアドレスでした。
つまり、```APIConsumer```の```ABI```があればいけそう？
:::

### プログラムを作る（リベンジ）

じゃあ、サクッと```ABI```取ってきちゃいます。

![スクリーンショット 2024-11-02 8.43.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/77f64460-12a4-f458-2375-dd89d09be53d.png)

```
nano ABIs/APIConsumer.json
```

:::note
RemixでコピーしたABIを貼り付けてください。
<details><summary>同じもの</summary>

```json:ABIs/APIConsumer.json
[
	{
		"inputs": [],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "id",
				"type": "bytes32"
			}
		],
		"name": "ChainlinkCancelled",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "id",
				"type": "bytes32"
			}
		],
		"name": "ChainlinkFulfilled",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "id",
				"type": "bytes32"
			}
		],
		"name": "ChainlinkRequested",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferRequested",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferred",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "volume",
				"type": "uint256"
			}
		],
		"name": "RequestVolume",
		"type": "event"
	},
	{
		"inputs": [],
		"name": "acceptOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "_requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "_volume",
				"type": "uint256"
			}
		],
		"name": "fulfill",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "owner",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "requestVolumeData",
		"outputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "transferOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "volume",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "withdrawPli",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	}
]
```
</details>
:::

```
nano explorer_6-2.js
```

:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_6-2.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット（追加）
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_6 Logsだけ表示してみる
    console.log('[Logs]');
    console.log(txrec.logs);

    console.log();

    //Logsをデコード（復号化）
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);
    console.log('[Decoded Logs]');
    console.log(decodeLogs);
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる（リベンジ）
修正は```APIConsumer```のABIを読み込ませただけですが、どうでしょうか。
```
node explorer_6-2.js
```
![スクリーンショット 2024-11-02 9.00.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/51035a1b-8604-41d9-5c57-dc5978a6b95b.png)

### もう少し整形してみる（自己満）
```
nano explorer_6-3.js
```
:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_6-3.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット（追加）
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
    //Logsをデコード（復号化）
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

    //整形
    console.log('[Decoded Logs]');
    txrec.logs.forEach(log => {
        console.log(`[${log.logIndex}]`);
        console.log(`  Address: ${log.address}`);
        let functionName = decodeLogs[log.logIndex].name;
        let nonIndexedData = '';
        decodeLogs[log.logIndex].events.forEach((param, index) => {
            if(index == 0) {
                functionName = functionName + ' (' + param.type + ' ' + param.name;
            }else {
                functionName = functionName + ', ' + param.type + ' ' + param.name;
            }
        });
        functionName = functionName + ')';
        console.log(`  Name   : ${functionName}`);
        console.log(`  Topics : `);
        log.topics.forEach((topic, index) => {
            console.log(`      [${index}] ${topic}`);
        });
        console.log(`  Data   : `);
        console.log(`    [Hex] ${log.data}`);
    });
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 実行してみる
```
node explorer_6-3.js
```
![スクリーンショット 2024-11-02 17.34.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/c714763e-a026-3ee4-d73a-255479acb142.png)
![スクリーンショット 2024-11-02 17.34.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/0cec8d5a-e6a3-bdf9-1b6a-7f9a8d23b6f4.png)

キレイに出力できました。
でも、Dataはハッシュ化された値です。

色々と触ったけど、デコード（Dec）された値を表示するには、方法は他にもあるのだろうけど、最後に紹介します。（たぶん邪道）

# 最終形

```
nano explorer_fin.js
```

:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:explorer_fin.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

//Letter Color
const RED     = '\u001b[31m';
const YELLOW  = '\u001b[33m';
const BLUE    = '\u001b[34m';
const RESET   = '\u001b[0m';

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
    // TxHashが格納されているブロックの情報を取得
    const block = await xdc3.eth.getBlock(tx.blockNumber);

    //Logsをデコード（復号化）
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

    //編集
    //timestamp
    const blockDate = new Date(block.timestamp * 1000);
    const status = txrec.status ? `${BLUE}Success${RESET}` : `${RED}Failed${RESET}`;
    const decodeInput = abiDecoder.decodeMethod(tx.input);

    //出力
    //Overview
    console.log(RED + '[Overview]' + RESET);
    console.log(YELLOW + '  TransactionHash           ' + RESET + tx.hash);
    console.log(YELLOW + '  Status and method:        ' + RESET + status + ' | ' + tx.input.slice(0,10));
    console.log(YELLOW + '  Block:                    ' + RESET + tx.blockNumber);
    console.log(YELLOW + '  Timestamp:                ' + RESET + blockDate);
    console.log(YELLOW + '  Transaction Action:       ' + RESET + decodeInput.name);
    console.log(YELLOW + '  From:                     ' + RESET + tx.from);
    console.log(YELLOW + '  To:                       ' + RESET + tx.to);
    console.log(YELLOW + '  Value:                    ' + RESET + tx.value);
    console.log(YELLOW + '  Actual Tx Cost/Fee:       ' + RESET +
                xdc3.utils.fromWei(xdc3.utils.toBN(tx.gasPrice * txrec.gasUsed), 'ether') + ' XDC');
    console.log(YELLOW + '  Gas Price:                ' + RESET + tx.gasPrice);
    console.log(YELLOW + '  Gas Limit & Usage by Txn: ' + RESET + tx.gas + ' | ' + txrec.gasUsed);
    console.log(YELLOW + '  Other Attributes:         ' + RESET);
    console.log(YELLOW + '    Txn Type: ' + RESET + tx.type);
    console.log(YELLOW + '    Nonce:    ' + RESET + tx.nonce);
    console.log(YELLOW + '    Position: ' + RESET + tx.transactionIndex);
    console.log(YELLOW + '  Input Data:               ' + RESET + tx.input);

    //Logs
    console.log(RED + '[Logs]' + RESET);
    txrec.logs.forEach(log => {
        console.log(YELLOW + '  [' + log.logIndex + ']' + RESET);
        console.log(YELLOW + '    Address: ' + RESET + log.address);
        let functionName = decodeLogs[log.logIndex].name;
        let nonIndexedData = '';
        decodeLogs[log.logIndex].events.forEach((param, index) => {
            if(index == 0) {
                functionName = functionName + ' (' + param.type + ' ' + param.name;
            }else {
                functionName = functionName + ', ' + param.type + ' ' + param.name;
            }
        });
        functionName = functionName + ')';
        console.log(YELLOW + '    Name   : ' + RESET + functionName);
        console.log(YELLOW + '    Topics : ' + RESET);
        log.topics.forEach((topic, index) => {
            console.log(YELLOW + '      [' + index + '] ' + RESET + topic);
        });
        console.log(YELLOW + '    Data   : ' + RESET);
        console.log(YELLOW + '      [Hex] ' + RESET + log.data);
    });

}
main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

### 動かしてみる
```
node explorer_fin.js
```
![スクリーンショット 2024-11-02 19.01.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/20656fdc-c6e7-6592-bd29-62b3f1bbdb3e.png)
![スクリーンショット 2024-11-02 19.09.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/cef99ca1-253b-783d-711c-da5791698d2c.png)
![スクリーンショット 2024-11-02 19.11.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/909abc76-7b59-c85e-7ab5-321fffd85527.png)
![スクリーンショット 2024-11-02 19.11.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/a869e976-956e-fde1-cbfb-a7b6b4ad0b26.png)

:::note
満足！！
:::

# もうちょっと・・・
やっぱりログの値もデコードしたものを表示したい。

色々と調べていると、
* ```Topics```は**イベントに関連付けられたインデックス付きパラメータである**
* ```Data```は**イベントのABIエンコードされた非インデックスパラメータを指す**

そもそも、スマートコントラクトはsolidityという言語でプログラムされ、ログはそのプログラムの中で出力されるように設計されている。
（ここらへん、もっと勉強しないとちゃんと説明できない。）

:::note
Remix
![スクリーンショット 2024-11-02 20.24.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/cb721837-9ba4-2c15-8036-e120fbe60759.png)
![スクリーンショット 2024-11-02 20.30.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/239db4c3-eaf8-1bf1-49e6-3b5fd382e2bd.png)

つまり、emitされることにより、↓このログが残る
![スクリーンショット 2024-11-02 20.45.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/c00e7f5f-ecab-9988-67a7-58fc8d33b552.png)
:::

少し脱線したけど、**Dataに格納されるのは非インデックスパラメータ**が重要。
デコードしたログをもう少し見てみる。
```
nano explorer_6-4.js
```
:::note
```explorer_6-2.js```を少し編集したもの
```js:explorer_6-4.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット（追加）
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //Logsをデコード（復号化）
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);
    console.log('[Decoded Logs]');
    decodeLogs.forEach(log => {
        console.log(log);
    });
}

main();
```
:::

![スクリーンショット 2024-11-02 21.34.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/e15293c5-e897-f03c-ea6e-2b5bc7d5852a.png)

:::note alert
邪道な方法で直していきます。
:::

## エンコードされたDataを表示する
```
nano -l node_modules/abi-decoder/index.js
```
:::note
161行目に下記を追加します。
```js:161行目
          indexed: param.indexed,
```
![スクリーンショット 2024-11-02 21.41.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/0d02a875-d13e-364d-f9ab-de370d0657f4.png)
:::

```
nano explorer_fin_mod.js
```
:::note
プログラムの中身。
※TODOの部分を書き換えてください。
```js:nano explorer_fin_mod.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

//Letter Color
const RED     = '\u001b[31m';
const YELLOW  = '\u001b[33m';
const BLUE    = '\u001b[34m';
const RESET   = '\u001b[0m';

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
    // TxHashが格納されているブロックの情報を取得
    const block = await xdc3.eth.getBlock(tx.blockNumber);

    //Logsをデコード（復号化）
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

    //編集
    //timestamp
    const blockDate = new Date(block.timestamp * 1000);
    const status = txrec.status ? `${BLUE}Success${RESET}` : `${RED}Failed${RESET}`;
    const decodeInput = abiDecoder.decodeMethod(tx.input);

    //出力
    //Overview
    console.log(RED + '[Overview]' + RESET);
    console.log(YELLOW + '  TransactionHash           ' + RESET + tx.hash);
    console.log(YELLOW + '  Status and method:        ' + RESET + status + ' | ' + tx.input.slice(0,10));
    console.log(YELLOW + '  Block:                    ' + RESET + tx.blockNumber);
    console.log(YELLOW + '  Timestamp:                ' + RESET + blockDate);
    console.log(YELLOW + '  Transaction Action:       ' + RESET + decodeInput.name);
    console.log(YELLOW + '  From:                     ' + RESET + tx.from);
    console.log(YELLOW + '  To:                       ' + RESET + tx.to);
    console.log(YELLOW + '  Value:                    ' + RESET + tx.value);
    console.log(YELLOW + '  Actual Tx Cost/Fee:       ' + RESET +
                xdc3.utils.fromWei(xdc3.utils.toBN(tx.gasPrice * txrec.gasUsed), 'ether') + ' XDC');
    console.log(YELLOW + '  Gas Price:                ' + RESET + tx.gasPrice);
    console.log(YELLOW + '  Gas Limit & Usage by Txn: ' + RESET + tx.gas + ' | ' + txrec.gasUsed);
    console.log(YELLOW + '  Other Attributes:         ' + RESET);
    console.log(YELLOW + '    Txn Type: ' + RESET + tx.type);
    console.log(YELLOW + '    Nonce:    ' + RESET + tx.nonce);
    console.log(YELLOW + '    Position: ' + RESET + tx.transactionIndex);
    console.log(YELLOW + '  Input Data:               ' + RESET + tx.input);

    //Logs
    console.log(RED + '[Logs]' + RESET);
    txrec.logs.forEach(log => {
        console.log(YELLOW + '  [' + log.logIndex + ']' + RESET);
        console.log(YELLOW + '    Address: ' + RESET + log.address);
        let functionName = decodeLogs[log.logIndex].name;
        let nonIndexedData = '';
        decodeLogs[log.logIndex].events.forEach((param, index) => {
            if(index == 0) {
                functionName = functionName + ' (' + param.type + ' ' + param.name;
            }else {
                functionName = functionName + ', ' + param.type + ' ' + param.name;
            }

            //非インデックスパラメータ判定
            if(!param.indexed) {
                if(nonIndexedData == '') {
                    nonIndexedData = `${param.name}:${param.value}`;
                }else {
                    nonIndexedData = nonIndexedData + ', ' + `${param.name}:${param.value}`;
                }
            }
        });
        functionName = functionName + ')';
        console.log(YELLOW + '    Name   : ' + RESET + functionName);
        console.log(YELLOW + '    Topics : ' + RESET);
        log.topics.forEach((topic, index) => {
            console.log(YELLOW + '      [' + index + '] ' + RESET + topic);
        });
        console.log(YELLOW + '    Data   : ' + RESET);
        console.log(YELLOW + '      [Hex] ' + RESET + log.data);
        //非インデックスをDataとして表示
        console.log(YELLOW + '      [Dec] ' + RESET + nonIndexedData);
    });

}
main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::
![スクリーンショット 2024-11-02 21.54.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/5030e959-0c7c-05f4-dab2-e9f3d1be1f83.png)
![スクリーンショット 2024-11-02 21.55.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/5b90406f-012a-8fbf-83a8-a6060afef02f.png)

:::note
ほぼ完コピ？
:::

# 最後の最後
入力されたトランザクションハッシュからトランザクション情報を出力できるようにして終わり！！
```
nano explorer_completed.js
```
:::note
プログラムの中身。
※例外処理入れてないからねっ！！

```js:explorer_completed.js
const Xdc3 = require("xdc3");
const abiDecoder = require('abi-decoder');
const readlineSync = require('readline-sync');

//Letter Color
const RED     = '\u001b[31m';
const YELLOW  = '\u001b[33m';
const BLUE    = '\u001b[34m';
const RESET   = '\u001b[0m';

const RPC = "https://erpc.xinfin.network";

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット
abiDecoder.addABI(apiConsumerABI);

async function main() {
    try {
        console.log(BLUE);
        const TXHASH = readlineSync.question('Input Transaction Hash(Plugin OCA was executed): ');
        console.log(RESET);

        if(TXHASH == '') {
            console.log(RED + 'Transaction Hash is a required field.' + RESET);
            return;
        }

        // 接続先のノードを設定
        const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
        // TxHashからトランザクションを取得
        const tx = await xdc3.eth.getTransaction(TXHASH);

        if(tx == null) {
            console.log(RED + 'Invalid Transaction Hash.' + RESET);
            return;
        }

        // TxHashからトランザクションレシートを取得
        const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
        // TxHashが格納されているブロックの情報を取得
        const block = await xdc3.eth.getBlock(tx.blockNumber);

        //Logsをデコード（復号化）
        const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

        //編集
        //timestamp
        const blockDate = new Date(block.timestamp * 1000);
        const status = txrec.status ? `${BLUE}Success${RESET}` : `${RED}Failed${RESET}`;
        const decodeInput = abiDecoder.decodeMethod(tx.input);

        if(decodeInput == undefined) {
            console.log(RED + 'This Transaction Hash does not contain any Plugin OCA transactions.' + RESET);
            return;
        }

        //出力
        //Overview
        console.log(RED + '[Overview]' + RESET);
        console.log(YELLOW + '  TransactionHash           ' + RESET + tx.hash);
        console.log(YELLOW + '  Status and method:        ' + RESET + status + ' | ' + tx.input.slice(0,10));
        console.log(YELLOW + '  Block:                    ' + RESET + tx.blockNumber);
        console.log(YELLOW + '  Timestamp:                ' + RESET + blockDate);
        console.log(YELLOW + '  Transaction Action:       ' + RESET + decodeInput.name);
        console.log(YELLOW + '  From:                     ' + RESET + tx.from);
        console.log(YELLOW + '  To:                       ' + RESET + tx.to);
        console.log(YELLOW + '  Value:                    ' + RESET + tx.value);
        console.log(YELLOW + '  Actual Tx Cost/Fee:       ' + RESET +
                    xdc3.utils.fromWei(xdc3.utils.toBN(tx.gasPrice * txrec.gasUsed), 'ether') + ' XDC');
        console.log(YELLOW + '  Gas Price:                ' + RESET + tx.gasPrice);
        console.log(YELLOW + '  Gas Limit & Usage by Txn: ' + RESET + tx.gas + ' | ' + txrec.gasUsed);
        console.log(YELLOW + '  Other Attributes:         ' + RESET);
        console.log(YELLOW + '    Txn Type: ' + RESET + tx.type);
        console.log(YELLOW + '    Nonce:    ' + RESET + tx.nonce);
        console.log(YELLOW + '    Position: ' + RESET + tx.transactionIndex);
        console.log(YELLOW + '  Input Data:               ' + RESET + tx.input);

        //Logs
        console.log(RED + '[Logs]' + RESET);
        txrec.logs.forEach(log => {
            console.log(YELLOW + '  [' + log.logIndex + ']' + RESET);
            console.log(YELLOW + '    Address: ' + RESET + log.address);
            let functionName = decodeLogs[log.logIndex].name;
            let nonIndexedData = '';
            decodeLogs[log.logIndex].events.forEach((param, index) => {
                if(index == 0) {
                    functionName = functionName + ' (' + param.type + ' ' + param.name;
                }else {
                    functionName = functionName + ', ' + param.type + ' ' + param.name;
                }

                //非インデックスパラメータ判定
                if(!param.indexed) {
                    if(nonIndexedData == '') {
                        nonIndexedData = `${param.name}:${param.value}`;
                    }else {
                        nonIndexedData = nonIndexedData + ', ' + `${param.name}:${param.value}`;
                    }
                }

            });
            functionName = functionName + ')';
            console.log(YELLOW + '    Name   : ' + RESET + functionName);
            console.log(YELLOW + '    Topics : ' + RESET);
            log.topics.forEach((topic, index) => {
                console.log(YELLOW + '      [' + index + '] ' + RESET + topic);
            });
            console.log(YELLOW + '    Data   : ' + RESET);
            console.log(YELLOW + '      [Hex] ' + RESET + log.data);
            //非インデックスをDataとして表示
            console.log(YELLOW + '      [Dec] ' + RESET + nonIndexedData);
        });

    }catch(e) {
        console.log("["+e.name+"]"+e.message);
    }
}

main();
```
保存は ```Ctrl```+```x```の後、```y```キー押下
:::

# 付録
今回、関数名を取るところが（知らない状態だと）まず1つ目の難関でしたが、ABIを使ってデコードするようにしました。
調査の過程で、関数名を取るだけであれば、ABIがなくてももっと簡単に取得できる方法がありました。

https://www.4byte.directory/

![スクリーンショット 2024-11-02 22.54.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/c501ff10-2878-3963-5874-a356bac4e053.png)

:::note
Overview
![スクリーンショット 2024-11-02 23.00.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/dd840cf2-2fbe-a9c3-a423-9edaff3a4079.png)
![スクリーンショット 2024-11-02 23.01.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/e795a639-38b4-b36e-69d6-75c6a0f240ff.png)
:::

:::note
Logs
0x9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a64
↓
OracleResponse
![スクリーンショット 2024-11-02 23.04.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/eafc9e3b-b1e3-4ed4-c452-166c46b6f542.png)
![スクリーンショット 2024-11-02 23.05.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/467d5a1c-dc7b-da20-64c5-9c1e4726a665.png)

0x673b38182859a514d734bdfcb49d3a05c4a641dec9311d9894c9717bcb79af76
↓
RequestVolume
![スクリーンショット 2024-11-02 23.08.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/7f909120-7f13-6213-0a9a-1d3c9c8e0f6b.png)
![スクリーンショット 2024-11-02 23.09.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090248/50a252f2-bb74-5618-a025-ecf390303bca.png)


:::