📘

GASを使って仮想通貨の残高を取得する

2022/01/08に公開

始めに

仮想通貨の取引所ではAPIが公開されていることが多く、Google Apps Script(GAS)でそのAPIを使って残高の取得をしてみましたので、その方法をまとめます。対象はcoincheckとbitFlyerです。
今回はGASはそのまま書くのではなくclaspを使ってTypeScriptでローカルにコードを書いてからpushして実行するやり方にしました。

APIキーの用意

残高を取得する際はAPIキーが必要になるので、それをcoincheckとbitflyerでそれぞれ用意します。

coincheck

メニューの設定アイコンタブを選択して、そこのAPIキーを選択します。

APIキー作成ページで「新たにAPIキーを追加する」を押して、残高だけチェックしてパスワードを入力してOKを押してください。

APIキーができるとアクセスキーとシークレットアクセスキーが見れるので、これをコピーしておいてください。

bitFlyer

まず「bitFlyer Lightning」に遷移します。

bitFlyer LightningページのメニューからAPIを選択します。

その画面でAPIキー作成モーダルを開き、残高だけをチェックして作成します。

作成されたAPIキーからAPI KeyとAPI Secretをコピーしておきます。

claspを使ったGAS環境の構築

環境構築は以下が参考になるので、これを見ながら作りました。

https://qiita.com/jiroshin/items/dcc398285c652554e66a

claspを使った開発の注意点

claspを使うことでローカルで開発することができ、またTypeScriptで書けるため開発効率がかなり上がると思ったのですが、色々つまづきポイントがあったのでその辺踏まえつつ開発する必要があります。

ローカルで開発できても実行はGASプロジェクトのみ

ソースコード自体はローカルで開発できますが、動作確認時にはpushしてGASプロジェクトにアップロードする必要があります。実行や実行結果の確認をCLIで行うことができますが、それはアップロード済みのものに対してなので、ローカルで動作確認してからアップロードというフローにすることができません。ローカルからGASを実行するには結構設定が必要になって手間なので、どうせpushするならWeb上で実行して確認した方が早いかなと個人的には思いました。

ファイル分割してimportしてもグローバル扱いになる

例えば以下のようにファイルを分割して使用したとします。

Main.ts
import { sum } from './Module';

function main() {
  console.log(sum(1, 2));
}
Module.ts
export function sum(a, b) {
  return a + b;
}

通常は問題ありませんが、GASにアップロードするとimport文だけが消されてしまい、実行エラーになってしまいます。

Main.gs
// Compiled using clasp-virtual-currency 1.0.0 (TypeScript 4.5.4)
var exports = exports || {};
var module = module || { exports: exports };
//import { sum } from './Module';
function main() {
    console.log((0, Module_1.sum)(1, 2));
}

Module.gs
// Compiled using clasp-virtual-currency 1.0.0 (TypeScript 4.5.4)
var exports = exports || {};
var module = module || { exports: exports };
exports.sum = void 0;
function sum(a, b) {
    return a + b;
}
exports.sum = sum;

対応としてはnamespaceを使用するのがありますが、moduleでもできたのでそちらで書きました。

Main.ts
import { Module } from './Module';

function main() {
  console.log(Module.sum(1, 2));
}
Module.ts
export module Module {
  export function sum(a, b) {
    return a + b;
  }
}

moduleでラップしておくと、そこに最終的には挿入されるので、グローバルスコープでもなんとか運用できます。

Main.gs
// Compiled using clasp-virtual-currency 1.0.0 (TypeScript 4.5.4)
var exports = exports || {};
var module = module || { exports: exports };
//import { Module } from './Module';
function main() {
    console.log(Module.sum(1, 2));
}
Module.ts
// Compiled using clasp-virtual-currency 1.0.0 (TypeScript 4.5.4)
var exports = exports || {};
var module = module || { exports: exports };
exports.Module = void 0;
var Module;
(function (Module) {
    function sum(a, b) {
        return a + b;
    }
    Module.sum = sum;
})(Module = Module || (Module = {}));

取引所APIを使って情報を取得する

以上の事前準備を元に、取引所で公開されているAPIを使って情報を取得していきたいと思います。説明ではcoincheckの方を取り扱います。bitFlyerも同じように実装することができ、詳細はソースコードの方をご参照ください。

public APIにあるレート情報を取得する

publicなAPIは特にAPIキーも不要なので単純にGASで使用できるFetch APIを使って取得します。コードがごちゃごちゃにならないようにAPIリクエスト用のファイルに書きます。

src/CoinCheckAPI.ts
export module CoinCheckAPI {
  export type RateMap = {
    btc_jpy: number;
    eth_jpy: number;
  }
  
  /**
   * 販売所のレートの取得
   */
  export function fetchRateMap(): RateMap {
    const pairs = ['btc_jpy', 'eth_jpy'] as const;
    const rateMap = Object.assign(
      {},
      ...pairs.map((pair) => {
        const url = `https://coincheck.com/api/rate/${pair}`;
        const response = UrlFetchApp.fetch(url, {
          method: 'get',
        });
        const json = JSON.parse(response.getContentText());
        return {
          [pair]: parseFloat(json.rate),
        };
      })
    );

    return rateMap;
  }
}

このメソッドを実行すると以下のようなデータが返ってきます。

{ btc_jpy: 4851186, eth_jpy: 372219.373815 }

private APIにある残高情報を取得する

APIキーをスクリプトプロパティに保存する

次にprivateなAPIを使っていきますが、事前にAPIキーを環境変数のようなものに保存しておきます。GASではスクリプトプロパティがその役割を担うのでそこに登録しますが、GUIでは登録できなくなってしまったようなのでコードに書いて登録します。コードに残したくないから使っているのに登録するためにコード書くのは本末転倒な気がしますけどね。。
誤ってGitにcommitしてしまわないように、.gitignoreでenv.tsを除外しておき、そこに内容を記載して、envに書かれた内容を登録するコードを書きます。

src/env.ts
export module Env {
  export const Properties = {
    COIN_CHECK_ACCESS_KEY: 'xxxxx',
    COIN_CHECK_SECRET_KEY: 'xxxxx',
    BITFLYER_ACCESS_KEY: 'xxxxx',
    BITFLYER_SECRET_KEY: 'xxxxx',
  };
}
src/Config.ts
import { Env } from './env';

function setProperties() {
  PropertiesService.getScriptProperties().setProperties(Env.Properties);
}

function getProperties() {
  const properties = PropertiesService.getScriptProperties().getProperties();
  console.log(properties);
}

これをGASにpushしてsetPropertiesを実行してスクリプトプロパティにアクセスキーを登録しておきます。

認証する

coincheckのドキュメントを抜粋すると、以下の情報をheaderに含めることで認証できるようです。

  • ACCESS-KEY: APIキー で作成したアクセスキー
  • ACCESS-NONCE: 毎リクエストごとに増加する必要のある正の整数。通常はUNIXタイムスタンプを用います。
  • ACCESS-SIGNATURE: ACCESS-NONCE, リクエスト先URL, リクエストのボディ を全て文字列にし連結したものを、HMAC-SHA256 hash形式でシークレットキーを使って署名した結果です。
    https://coincheck.com/ja/documents/exchange/api

ACCESS-NONCEは正の整数でリクエストごとに増えていけばいいのでJavaScriptだとDate.now()を使うことが多かったのでそれを使います。
ACCESS-SIGNATUREが鬼門で、そもそもHMAC-SHA256 hash形式がGASだとどう書くのか調べるのに苦労しましたが、結論は以下のようになりました。使いまわせるようにUtilsに書きます。

src/Utils.ts
export module Utils {
  /**
   * HMAC認証値をhex文字列で返す
   */
  export function makeSignature(value: string, key: string) {
    const signBytes = Utilities.computeHmacSha256Signature(value, key);
    return signBytes.reduce((str, byte) => {
      const hex = (byte < 0 ? byte + 256 : byte).toString(16);
      return str + (hex.length === 1 ? '0' : '') + hex;
    }, '');
  }
}

残高を取得する

認証の準備も整ったので、後は実際に残高を取得します。

src/CoinCheckAPI.ts
import { Utils } from './Utils';

export module CoinCheckAPI {
  export type CurrencyMap {
    jpy: number;
    btc: number;
    eth: number;
  }
  
  const properties = PropertiesService.getScriptProperties().getProperties();
  const { COIN_CHECK_ACCESS_KEY, COIN_CHECK_SECRET_KEY } = properties;
  
  /**
   * 現在の通貨を取得する
   */
  export function fetchCurrencyMap(): CurrencyMap {
    const url = 'https://coincheck.com/api/accounts/balance';
    const timestamp = Date.now().toString();

    const response = UrlFetchApp.fetch(url, {
      method: 'get',
      headers: {
        'ACCESS-KEY': COIN_CHECK_ACCESS_KEY,
        'ACCESS-NONCE': timestamp,
        'ACCESS-SIGNATURE': Utils.makeSignature(
          timestamp + url,
          COIN_CHECK_SECRET_KEY
        ),
      },
    });
    const json = JSON.parse(response.getContentText());
    const { success, ...rest } = json;
    Object.keys(rest).forEach((key) => {
      rest[key] = parseFloat(rest[key]);
    });
    return rest;
  }
}

実行すると以下のようなデータが返ってきます(数字は適当です)。

{ jpy: 5.2,
  btc: 0.00747467,
  eth: 0.10439622,
  // 他にも色々出てきます
}

合計の日本円換算を算出する

以上の2つのAPIを取得することで、日本円換算にすると合計いくらかを算出することができます。

src/Code.ts
import { CoinCheckAPI } from './CoinCheckAPI';

function coincheck() {
  const currencyMap = CoinCheckAPI.fetchCurrencyMap();
  const rateMap = CoinCheckAPI.fetchRateMap();
  const total =
    currencyMap.jpy +
    currencyMap.btc * rateMap.btc_jpy +
    currencyMap.eth * rateMap.eth_jpy;
    
  console.log(total);
}

終わりに

以上がGASを使って仮想通貨の残高を取得するやり方でした。claspを使うことによって綺麗にファイルを分割して気持ち良く開発できると思っていましたが、意外とつまづきポイントがありなんともという気持ちでした。。とはいえそれでも比較的上手くコード分割して保守性のあるコードは書けたかなと思っています。
今回の例は残高取得だけでしたが、鬼門だった認証のサンプルを作ることができたので、これをベースに毎月の残高をスプレッドシートに書き出したり、自動で取引したりと応用が効くと思っています。
作成したコードは以下リポジトリにおいておきますので、興味がある方は色々カスタマイズしてみてください😄

https://github.com/wintyo/clasp-virtual-currency

参考記事

https://qiita.com/jiroshin/items/dcc398285c652554e66a
https://kenchan0130.github.io/post/2019-12-25-1
https://auto-worker.com/blog/?p=2365
https://teratail.com/questions/80241?link=qa_related_pc

Discussion