仮想通貨APIをお触りしてみる

# 背景
一昔前まで週刊SPAとか胡散臭い雑誌(主観)くらいでしか聞かなかった仮想通貨。
BitCoinの価値が100万円まで上がって以降あまり情報見てなかったが、最近は1,000万円を越えたらしい。

いきなりまとまった金額を投資するのも怖い、、、ので
GMOコインで原資1万円で遊んでみる(本当に遊ぶわけではなく)

## APIとは何か
・アプリケーション・プログラミング・インターフェース（Application Programming Interface）の略
・自作アプリに組み込み、提供元のサービスにアクセスする事ができる
### 例
・LINE、Slack などに自動投稿するbotを作成
・Google Mapをサイト内に表示
## 何故GMOコインなのか
・入出金で手数料がかからない
・APIを提供していてその利用手数料が無料
・国内でも信頼度が高い
### 対応言語
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214861/bdb34cdc-8ff1-410e-2252-50e7ae2a74ae.png)
今回はNode.jsで処理を確認する

■手数料

https://coin.z.com/jp/corp/guide/fees/

■信頼度

https://coinpost.jp/?p=534225
## 何故原資1万円なのか
GMOコインでは、BitCoinの場合500円から取引(※成行注文)ができる
検証の為500円×2回では心許なく、最大20回買注文約定させられる金額であれば十分と判断
## 税金の話
所得税(更に細分化して雑所得)に分類され、年間の利確益が
195万円以下: 税率5%
330万円以下: 税率10%
..と累進課税される。

ちなみに、利確益が20万円未満であれば、確定申告が不要となる。
なお、年末調整で処理はできないらしい。
### 利確の補足
20万円で何か(証券や暗号資産)を購入して
30万円で売却した → 利益は10万円なので納税不要
40万円で売却した → 利益は20万円なので翌年に納税が必要
## GMOコインのAPI
通信プロトコルで2種類、
トークン有無で2種類の合計4種類存在する。

### HTTP
#### Public API: リクエスト単位で接続を閉じる
トークンが不要、取引レートや板情報などを取得する。
#### private API
トークンが必要、資産情報や注文内容などプライベートな内容を取得する。
### WebSocket: 双方向でデータ送受信(を繰り返す)
#### Public Socket API
トークンが不要、1分に1回取引レートや板情報などの情報を取得し3回連続でサーバから応答がない場合接続を閉じる
#### Private Socket API
トークンが必要、1分に1回注文内容などプライベートな内容を取得し3回連続でサーバから応答がない場合接続を閉じる
# 試しにAPIを叩いてみる
## APIドキュメント
https://api.coin.z.com/docs/#outline
## 1分ごとに価格を通知する
```socketTest.js
const WebSocket = require("ws");
// バイナリの変換
const { Buffer } = require('node:buffer');
const ws = new WebSocket("wss://api.coin.z.com/ws/public/v1");

// 1分に1回pingを送る
ws.on("open", () => {
    const message = JSON.stringify(
    {
        "command": "subscribe",
        "channel": "ticker",
        "symbol": "BTC"
    });
    ws.send(message);
});

ws.on("message", (data) => {
    // レスポンス: 16進数の配列
    console.log("WebSocket message: ", data);

    // utf-8に変換
    var text_decoder = new TextDecoder("utf-8");
    var str = text_decoder.decode(Uint8Array.from(data).buffer);
    console.log(str);
});
```
現在のレートを定期的に知るためにPublic Socket APIを利用する。
ドキュメントの通りに実行すると
```
>node socketTest.js 
ebSocket message:  <Buffer 7b 22 63 68 61 6e 6e 65 6c 22 3a 22 74 69 63 6b 65 72 22 2c 22 61 73 6b 22 3a 22 31 30 39 31 30 30 35 33 22 2c 22 62 69 64 22 3a 22 31 30 39 30 38 39 ... 131 more bytes>
```
16進数(バイナリ)なので、utf-8に変換をかけると
```
>node socketTest.js 
{"channel":"ticker","ask":"10910053","bid":"10908942","high":"10990000","last":"10908942","low":"10650050","symbol":"BTC","timestamp":"2024-06-03T14:23:17.076Z","volume":"194.0242"}
```
BTCのレートが取得できた
#### ちなみに
BID（ビッド）: 売り注文できる金額
ASK（アスク）: 買い注文できる金額
らしい。
## 指定時間ごとに価格を通知する
1分に1回では使い勝手が悪い。
タスクスケジューラcron のNode.js版を用いて頻度を調整する。
```index.js
const axios = require('axios');
const cron = require('node-cron');

const endPoint = 'https://api.coin.z.com/public';
const path     = '/v1/ticker?symbol=BTC';

function getAPI(){
  axios.get(endPoint + path)
    .then(function (response) {
      // 板情報の中央の値(最後に取引されたレート)を取得
      console.log(response.data);
    })
    .catch(function (error) {
    console.log(error);
    })
  .finally(function () {});
}

// node-cronで定期実行 3秒に1回
cron.schedule('*/3 * * * * *', () => getAPI());
```
■node-cronの仕様

https://www.npmjs.com/package/node-cron#cron-syntax

```console.log(response.data)とすると
>node index.js
{
  status: 0,
  data: [
    {
      ask: '11100364',
      bid: '11100024',
      high: '11128001',
      last: '11100025',
      low: '11057145',
      symbol: 'BTC',
      timestamp: '2024-06-06T08:48:05.829Z',
      volume: '160.5883'
    }
  ],
  responsetime: '2024-06-06T08:48:06.034Z'
}
...
```
```console.log(response.data.data[0].last)とすると
>node index.js      
11107200
11107200
11100375
11100375
11100375
...
```
## 資産情報を確認する
直前の例と同様にレスポンスの中身を掘っていく
```privateAPI_test.js
const axios  = require('axios');
const crypto = require('crypto');

const apiKey    = 'YOUR_API_KEY';
const secretKey = 'YOUR_SECRET_KEY';

const timestamp = Date.now().toString();
const method    = 'GET';
const endPoint  = 'https://api.coin.z.com/private';

// 各通貨の資産残高取得
const path      = '/v1/account/assets';

const text = timestamp + method + path;
const sign = crypto.createHmac('sha256', secretKey).update(text).digest('hex');
const options = {
    "headers": {
        "API-KEY": apiKey,
        "API-TIMESTAMP": timestamp,
        "API-SIGN": sign
    }
};

axios.get(endPoint + path, options)
  .then(function (response) {
  
    // 評価額合計
    let sum = 0;
    response.data.data.forEach(function(element){
      // 全仮想通貨のうち保有している銘柄に絞り込み
      if(element.amount != 0){
        // 日本円とそれ以外で表記を分ける目的
        if(element.symbol == "JPY"){
          sum += Math.floor(element.available);
          console.log("日本円残高: " + element.available + "円");
        } else {
          sum += Math.floor(element.available * element.conversionRate);
          console.log(element.symbol + "残高: " + element.available + " 評価額: " 
            + element.available * element.conversionRate + "円(為替レート: " + element.conversionRate + ")");
        }
      }
    });
    console.log("→ 評価額合計: " + sum + "円");
  })
  .catch(function (error) {
    console.log(error);
  });
```
実行結果
```
> node privateAPI_test.js
日本円残高: 8120円
BTC残高: 0.00016393 評価額: 1770.0826982800002円(為替レート: 10797796)
XRP残高: 3 評価額: 239.25900000000001円(為替レート: 79.753)
→ 評価額合計: 10129円
```
値の参照が検証できた。