RippleAPI入門ガイド

このチュートリアルでは、Node.js RippleAPI(XRP LedgerにアクセスするためのJavaScript API)を使用して、XRP Ledgerに接続されるアプリケーションを開発するための基本事項を説明します。

このガイドで使用しているスクリプトと構成ファイルは、Ripple開発者ポータルのGitHubリポジトリで入手できます

環境の設置

RippleAPIを使用するための最初のステップは、開発環境の設置です。

Node.jsとnpmのインストール

RippleAPIはNode.jsランタイム環境向けのアプリケーションとして構築されているため、最初のステップはNode.jsのインストールです。RippleAPIでは、Node.js v6以降が必要です。Node.js v10 LTSを使用することをお勧めします。

このステップは、オペレーティングシステムによって内容が異なります。使用しているオペレーティングシステムのパッケージマネージャーを使用してNode.jsをインストールする場合の公式の手引き に準拠することをお勧めします。Node.jsとnpm(Node Package Manager)のパッケージが分かれている場合、両方をインストールします(これに該当するのは、Arch Linux、CentOS、Fedora、RHELの場合です)。

Node.jsのインストール後、nodeバイナリーのバージョンはコマンドラインから確認できます。

node --version

プラットフォームによっては、バイナリーの名前がnodejsとなっています。

nodejs --version

Yarnのインストール

RippleAPIでは、Yarnを使用して依存関係を管理します。Yarn v1.13.0を使用することをお勧めします。

このステップは、オペレーティングシステムによって内容が異なります。使用しているオペレーティングシステムのパッケージマネージャーを使用してYarnをインストールする場合の公式の手引き に準拠することをお勧めします。

Yarnのインストール後、yarnバイナリーのバージョンはコマンドラインから確認できます。

yarn --version

RippleAPIと依存関係のインストール

以下のステップに従い、Yarnを使用してRippleAPIと依存関係のインストールを完了します。

1. プロジェクトの新規ディレクトリーを作成

my_ripple_experimentといった名前でフォルダーを作成します。

mkdir my_ripple_experiment && cd my_ripple_experiment

コードに対する変更を追跡できるよう、このディレクトリーにGit リポジトリーを作成します(省略可)。

git init

作業内容のバージョン管理や共有を目的として、GitHubにリポジトリーを作成 してもかまいません。設置後、ローカルマシンにリポジトリーのクローンを作成 し、そのディレクトリーにcdします。

2. プロジェクトの新規package.jsonファイルを作成

次の内容が含まれている、以下のテンプレートを使用します。

  • RippleAPI自体(ripple-lib
  • (省略可)コード品質を確認するためのESLint eslint
{
  "name": "my_ripple_experiment",
  "version": "0.0.1",
  "license": "MIT",
  "private": true,
  "//": "Change the license to something appropriate. You may want to use 'UNLICENSED' if you are just starting out.",
  "dependencies": {
    "ripple-lib": "*"
  },
  "devDependencies": {
    "eslint": "*"
  }
}

3. Yarnを使用してRippleAPIと依存関係をインストール

Yarnを使用して、プロジェクトで作成したpackage.jsonファイルに定義されているRippleAPIと依存関係をインストールします。

yarn

これで、RippleAPIと依存関係がローカルフォルダーnode_modules/にインストールされます。

インストールプロセスの終了時に、いくつかの警告が表示される場合があります。以下の警告は無視してかまいません。

warning eslint > file-entry-cache > flat-cache > circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor.

npm WARN optional Skipping failed optional dependency /chokidar/fsevents: 

npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.0.6

最初のRippleAPIスクリプト

スクリプトget-account-info.jsは、ハードコーディングされたアカウントに関する情報をフェッチします。このスクリプトを使用して、RippleAPIが動作することをテストします。

'use strict';
const RippleAPI = require('ripple-lib').RippleAPI;

const api = new RippleAPI({
  server: 'wss://s1.ripple.com' // Public rippled server
});
api.connect().then(() => {
  /* begin custom code ------------------------------------ */
  const myAddress = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn';

  console.log('getting account info for', myAddress);
  return api.getAccountInfo(myAddress);

}).then(info => {
  console.log(info);
  console.log('getAccountInfo done');

  /* end custom code -------------------------------------- */
}).then(() => {
  return api.disconnect();
}).then(() => {
  console.log('done and disconnected.');
}).catch(console.error);

スクリプトの実行

以下のコマンドを使用して、最初のRippleAPIスクリプトを実行します。

node get-account-info.js

出力:

getting account info for rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn
{ sequence: 359,
  xrpBalance: '75.181663',
  ownerCount: 4,
  previousInitiatedTransactionID: 'E5C6DD25B2DCF534056D98A2EFE3B7CFAE4EBC624854DE3FA436F733A56D8BD9',
  previousAffectingTransactionID: 'E5C6DD25B2DCF534056D98A2EFE3B7CFAE4EBC624854DE3FA436F733A56D8BD9',
  previousAffectingTransactionLedgerVersion: 18489336 }
getAccountInfo done
done and disconnected.

スクリプトの内容解説

このスクリプトでは、RippleAPI固有のコードに加え、JavaScriptにおける近年の開発成果である構文や規定も利用しています。今回のサンプルコードを小さめのチャンクに分割して、個別に説明していきます。

スクリプトの冒頭

'use strict'; 
const RippleAPI = require('ripple-lib').RippleAPI;

先頭行では、strictモード を有効にしています。このモードを使用するかどうかは任意に選択できますが、JavaScriptで陥りやすいいくつかの落とし穴を回避する上で役立ちます。

2行目では、Node.jsのrequire関数を使用して、RippleAPIを現在のスコープにインポートしています。RippleAPIは、ripple-libがエクスポートするモジュール の1つです。

APIのインスタンス化

const api = new RippleAPI({
  server: 'wss://s1.ripple.com' // Public rippled server
});

このセクションでは、RippleAPIクラスの新規インスタンスを作成し、変数apiに代入しています(constキーワード は、値apiを何らかの別の値に再代入できないことを意味します。ただし、オブジェクトの内部状態は変化する可能性があります)。

コンストラクターへの引数の1つはoptionsオブジェクトであり、このオブジェクトにはさまざまなオプションが用意されています。serverパラメーターでは、どのrippledサーバーに接続するのかを指定しています。

  • このserver設定例では、セキュアなWebSocket接続を使用して、Ripple社が運営している公開サーバーの1つに接続しています。
  • serverオプションを記述しない場合、RippleAPIは、ネットワーク接続の不要なメソッドのみが提供されるオフラインモードで実行されます。
  • 代わりにXRP Ledgerテストネットサーバーを指定すると、本番環境のXRP Ledgerではなく、別空間のテストネットワークに接続できます。
  • 独自のrippledを運用している場合は、ローカルサーバーに接続するよう指示できます。例えば、代わりにserver: 'ws://localhost:5005'と記述します。

接続とPromise

api.connect().then(() => {

connect()メソッドは、特殊なJavaScriptオブジェクトであるPromise を返す多くのRippleAPIメソッドの1つです。Promiseは、XRP Ledgerを照会するなど、値を後ほど返す非同期操作の実行を目的としています。

何らかの式(api.connect()など)からPromiseが返された場合、Promiseのthenメソッドを呼び出して、コールバック関数を渡します。関数を引数として渡すことはJavaScriptでは常套的な手法であり、JavaScriptの関数が第一級オブジェクト であることを利用しています。

Promiseは、自身の非同期動作を完了すると、渡されたコールバック関数を実行します。thenメソッドからの戻り値は別のPromiseオブジェクトであるため、別のthenメソッドへの、または同様にコールバックを受け付けるPromiseのcatchメソッドへの「チェーン」にすることができます。catchに渡すコールバックは、何らかの問題が生じた場合に呼び出されます。

この例では、非同期関数を手軽に定義できる手段であるarrow関数 を使用しています。この方法は、1回限りの関数をコールバックとして大量に定義する場合に便利です。()=> {...}という構文は、 {...}/>function() {...}とほぼ等価です。パラメーターを1つ取る非同期関数が必要な場合は、代わりにinfo => {...}などの構文を使用できます。この構文は、 {...}/>function(info) {...}という構文とほぼ同一です。

カスタムコード

  /* begin custom code ------------------------------------ */
  const myAddress = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn'; 

  console.log('getting account info for', myAddress); 
  return api.getAccountInfo(myAddress); 

}).then(info => {
  console.log(info); 
  console.log('getAccountInfo done'); 

  /* end custom code -------------------------------------- */

ここが、スクリプトで実行する処理を記述するために変更を加える部分です。

このサンプルコードでは、XRP Ledgerアカウントのアドレスを使用してXRP Ledgerアカウントを参照しています。さまざまなアドレスを指定してコードを実行し、結果が変化することを確認してみてください。

console.log()関数はNode.jsとWebブラウザーの両方に組み込まれているもので、結果をコンソールに出力します。この例では大量のコンソール出力を得られるので、コードによって実行される処理の内容を簡単に理解できます。

このサンプルコードは、(RippleAPIが接続を終了した時点で呼び出される)コールバック関数の中が始点となっています。その関数がRippleAPIのgetAccountInfoメソッドを呼び出すと、結果が返されます。

getAccountInfo APIメソッドは別のPromiseを返すものであるため、}).then( info => {の行で、このPromiseの非同期処理が完了した時点で実行される別の非同期コールバック関数を定義しています。前述の例とは異なり、このコールバック関数は、getAccountInfo APIメソッドからの非同期戻り値を保持するinfoという引数を1つ取ります。このコールバック関数の残りの部分は、その戻り値をコンソールに出力するものです。

クリーンアップ

}).then(() => {
  return api.disconnect(); 
}).then(() => {
  console.log('done and disconnected.'); 
}).catch(console.error);

サンプルコードの残りの部分は、概ねボイラープレートコードとしての性質を持ちます。1行目は前のコールバック関数を終了するもので、次に、終了時に実行される別のコールバックへのチェーンを作成しています。そのメソッドはXRP Ledgerからの明示的な切断を実行し、終了時にコンソールへの書き込みを実行する別のコールバックが記述されています。スクリプトでRippleAPIイベントを待機する場合は、イベントの待機を終了するまで切断を実行しないでください。

catchメソッドで、このPromiseチェーンを終了します。ここに記述しているコールバックは、いずれかのPromiseまたはそのコールバック関数でエラーが発生した場合に実行されます。ここでは、カスタムのコールバックを定義するのではなく、コンソールへの書き込みを実行する標準のconsole.error関数を渡しています。より高機能のコールバック関数をここに定義して、特定のタイプのエラーをインテリジェントにキャッチすることもできます。

検証の待機

XRP Ledger(または任意の分散されたシステム)を使用する上で最大の課題の1つとなるのが、最終的かつ不変のトランザクション結果を把握することです。ベストプラクティスに従っている場合も、トランザクションが最終的に受け入れられるか拒否されるまで、コンセンサスプロセスを待機しなければならないことに変わりはありません。以下のサンプルコードは、トランザクションの最終的な結果を待機する方法を示しています。

'use strict';
/* import RippleAPI and support libraries */
const RippleAPI = require('ripple-lib').RippleAPI;

/* Credentials of the account placing the order */
const myAddr = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn';
const mySecret = 's████████████████████████████';

/* Define the order to place here */
const myOrder = {
  'direction': 'buy',
  'quantity': {
    'currency': 'FOO',
    'counterparty': 'rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v',
    'value': '100'
  },
  'totalPrice': {
    'currency': 'XRP',
    'value': '1000'
  }
};

/* Milliseconds to wait between checks for a new ledger. */
const INTERVAL = 1000;
/* Instantiate RippleAPI. Uses s2 (full history server) */
const api = new RippleAPI({server: 'wss://s2.ripple.com'});
/* Number of ledgers to check for valid transaction before failing */
const ledgerOffset = 5;
const myInstructions = {maxLedgerVersionOffset: ledgerOffset};


/* Verify a transaction is in a validated XRP Ledger version */
function verifyTransaction(hash, options) {
  console.log('Verifying Transaction');
  return api.getTransaction(hash, options).then(data => {
    console.log('Final Result: ', data.outcome.result);
    console.log('Validated in Ledger: ', data.outcome.ledgerVersion);
    console.log('Sequence: ', data.sequence);
    return data.outcome.result === 'tesSUCCESS';
  }).catch(error => {
    /* If transaction not in latest validated ledger,
       try again until max ledger hit */
    if (error instanceof api.errors.PendingLedgerVersionError) {
      return new Promise((resolve, reject) => {
        setTimeout(() => verifyTransaction(hash, options)
        .then(resolve, reject), INTERVAL);
      });
    }
    return error;
  });
}


/* Function to prepare, sign, and submit a transaction to the XRP Ledger. */
function submitTransaction(lastClosedLedgerVersion, prepared, secret) {
  const signedData = api.sign(prepared.txJSON, secret);
  return api.submit(signedData.signedTransaction).then(data => {
    console.log('Tentative Result: ', data.resultCode);
    console.log('Tentative Message: ', data.resultMessage);
    /* The tentative result should be ignored. Transactions that succeed here can ultimately fail,
       and transactions that fail here can ultimately succeed. */

    /* Begin validation workflow */
    const options = {
      minLedgerVersion: lastClosedLedgerVersion,
      maxLedgerVersion: prepared.instructions.maxLedgerVersion
    };
    return new Promise((resolve, reject) => {
      setTimeout(() => verifyTransaction(signedData.id, options)
    .then(resolve, reject), INTERVAL);
    });
  });
}


api.connect().then(() => {
  console.log('Connected');
  return api.prepareOrder(myAddr, myOrder, myInstructions);
}).then(prepared => {
  console.log('Order Prepared');
  return api.getLedger().then(ledger => {
    console.log('Current Ledger', ledger.ledgerVersion);
    return submitTransaction(ledger.ledgerVersion, prepared, mySecret);
  });
}).then(() => {
  api.disconnect().then(() => {
    console.log('api disconnected');
    process.exit();
  });
}).catch(console.error);

このコードは注文トランザクションを作成して送信するものですが、他のタイプのトランザクションにも同様の原則があてはまります。トランザクションを送信した後、setTimeoutを使用して所定の時間が経過するまで待機し、新しいPromiseでレジャーをもう一度照会して、トランザクションが検証済みとなっているかどうかを確認します。検証済みとなっていない場合は、検証済みレジャーの中にトランザクションが見つかるか、返されたレジャーがLastLedgerSequenceパラメーターの値よりも大きくなるまで、このプロセスを繰り返します。

まれなケースとして(特に、大きな遅延や電源喪失が発生した場合)、トランザクションを送信してから、maxLedgerVersionがネットワークから渡されたと判断するまでの間に、レジャーのいずれかのバージョンがrippledサーバーで欠落することがあります。この場合、トランザクションが失敗したのか、欠落したバージョンのレジャーに含まれているのかを最終的に確定することはできません。RippleAPIは、この場合、MissingLedgerHistoryErrorを返します。

rippledサーバーの管理者である場合は、欠落しているレジャーを手動で要求できます。管理者でない場合は、別のサーバーを使用してレジャー履歴を確認してみるという方法が考えられます(Rippleは、この目的で、すべての履歴が記録される公開サーバーをs2.ripple.comで運用しています)。

詳細は、信頼できるトランザクションの送信を参照してください。

WebブラウザーでのRippleAPI

RippleAPIは、ブラウザー互換のバージョンをコンパイルし、RippleAPIスクリプトよりも前にlodash を依存関係として含めた場合、Webブラウザーでも使用できます。

ブラウザー互換バージョンのRippleAPIのビルド

RippleAPIをブラウザーで使用するには、ブラウザー互換バージョンをビルドする必要があります。以下の手順では、RippleAPIをコンパイルして、Webページに含めることのできる単一のJavaScriptファイルを生成します。

1. RippleAPI gitリポジトリーのコピーをダウンロード

Git がインストールされている場合は、リポジトリーのクローンを作成して、masterブランチをチェックアウトできます。このブランチには、常に最新の公式リリースが収められています。

git clone https://github.com/ripple/ripple-lib.git
cd ripple-lib
git checkout master

または、特定のリリースのアーカイブ(.zipまたは.tar.gz)をRippleAPIリリースページ からダウンロードし、抽出します。

2. Yarnのインストール

Yarnのインストールに関する手順に従います。

3. Yarnを使用して依存関係をインストール

yarn

4. Gulpを使用して単一のJavaScript出力をビルド

RippleAPIには、gulp パッケージを使用してすべてのソースコードをコンパイルし、ブラウザー互換バージョンのJavaScriptファイルを生成するためのコードが付属しています。Gulpは依存関係の1つとして自動的にインストールされるため、実行するだけで済みます。RippleAPIの構成上、これは容易に実行できるようになっています。

yarn run build

出力:

> ripple-lib@0.16.5 build /home/username/ripple-lib
> gulp

[14:11:02] Using gulpfile /home/username/ripple-lib/gulpfile.js
[14:11:02] Starting 'build'...
[14:11:03] Starting 'build-debug'...
[14:11:03] Starting 'build-min'...
[14:11:18] Finished 'build-debug' after 15 s
[14:11:18] Finished 'build' after 16 s
[14:11:18] Finished 'build-min' after 15 s
[14:11:18] Starting 'default'...
[14:11:18] Finished 'default' after 19 μs

完了までに、しばらく時間がかかる場合があります。最終的に、ビルドプロセスによって、目的のファイルが含まれた新しいbuild/フォルダーが作成されます。

build/ripple-<VERSION NUMBER>.jsファイルは、ブラウザーですぐに使用できる(ビルドしたバージョンの)RippleAPIの直接エクスポートです。名前の末尾が-min.jsのファイルも同じものですが、読み込みを高速化するため、内容が縮小 されています。

ブラウザーでのRippleAPIのデモ

以下のHTMLファイルは、RippleAPIのブラウザーバージョンで公開rippledサーバーに接続し、そのサーバーに関する情報のレポートを生成するための基本的な使用方法を示しています。Node.jsの「require」構文を使用するのではなく、ブラウザーバージョンを使用して、RippleAPIクラスが含まれているrippleという名前のグローバル変数を作成します。

この例を使用するには、最初にRippleAPIのブラウザー互換バージョンをビルドした後、結果として生成される出力ファイルのいずれかを、このHTMLファイルと同一のフォルダーにコピーします(縮小バージョンとフルサイズバージョンのどちらを使用してもかまいません)。この例にある2番目の<script>タグを変更して、ビルドしたバージョンのRippleAPIに対応する適切なファイル名が使用されるようにします。

browser-demo.html:

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
  <script type="application/javascript" src="ripple-1.1.2-min.js"></script>
  <script>
    console.log(ripple);
    var api = new ripple.RippleAPI({server:'wss://s1.ripple.com/'});
    api.connect().then(function() {
        return api.getServerInfo();
    }).then(function(server_info) {
    document.body.innerHTML += "<p>Connected to rippled server!</p>" +
"      <table>" +
"        <tr><th>Version</th>" +
"          <td>" + server_info.buildVersion + "</td></tr>" +
"        <tr><th>Ledgers available</th>" +
"          <td>" + server_info.completeLedgers + "</td></tr>" +
"        <tr><th>hostID</th>" +
"          <td>" + server_info.hostID + "</td></tr>" +
"        <tr><th>Most Recent Validated Ledger Seq.</th>" +
"          <td>" + server_info.validatedLedger.ledgerVersion + "</td></tr>" +
"        <tr><th>Most Recent Validated Ledger Hash</th>" +
"          <td>" + server_info.validatedLedger.hash + "</td></tr>" +
"        <tr><th>Seconds since last ledger validated</th>" +
"          <td>" + server_info.validatedLedger.age + "</td></tr>" +
"      </table>";
    });
  </script>
  <style type="text/css">
    td, th { border: 1px solid black; padding: 5px; }
    table { border-collapse: collapse; }
  </style>
</head>
<body></body>
</html>

このデモHTMLでは、Lodash v4.17.11をCloudflare上のCDNJSから読み込んだ後、ripple-lib v1.1.2を読み込んでいますが、Lodashのバリアントをローカルにダウンロードして読み込んでもかまいません。