XRPの送金

このチュートリアルでは、xrpl.jsを使用してシンプルなXRP送金を行う方法について説明します。まずは、XRP Testnetを使用してプロセスを順に進めます。次に、そのプロセスと、本番で同様の処理を行う場合に発生する追加要件とを比較します。

前提条件

  • このページでは、xrpl.jsライブラリーを使用するJavaScriptの例を紹介します。xrpl.js入門ガイドに、xrpl.jsを使用してJavaScriptからXRP Ledgerデータにアクセスする方法の説明があります。

  • XRP Ledgerでトランザクションを送信するには、まずアドレスと秘密鍵、そしていくらかのXRPが必要となります。次のインターフェイスを使用して、XRP Test NetにあるアドレスとTestnet XRPを入手できます。

暗号鍵を作成しています…

注意: RippleはTestnetとDevnetをテストの目的でのみ運用しており、その状態とすべての残高を定期的にリセットしています。予防措置として、Testnet、DevnetとMainnetで同じアドレスを使用しないことをお勧めします。

Testnetでの送金

1. Testnetサーバーへの接続

必須の自動入力可能フィールドに入力されるようにするために、ripple-libを、アカウントの現在のステータスと共有レジャー自体を取得できるサーバーに接続する必要があります。(セキュリティを高めるために、トランザクションの署名はオフライン中に行うことを推奨します。ただしその場合は、自動入力可能フィールドに手動で入力する必要があります。)トランザクションの送信先となるネットワークに接続する必要があります。

以下のサンプルコードでは公開XRP Testnetサーバーに接続します。

// In browsers, use a <script> tag. In Node.js, uncomment the following line:
// const xrpl = require('xrpl')

// Wrap code in an async function so we can use await
async function main() {

  // Define the network client
  const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233")
  await client.connect()

  // ... custom code goes here

  // Disconnect when done (If you omit this, Node.js won't end the process)
  await client.disconnect()
}

main()
# Connect ----------------------------------------------------------------------
import xrpl
testnet_url = "https://s.altnet.rippletest.net:51234"
client = xrpl.clients.JsonRpcClient(testnet_url)
// Connect --------------------------------------------------------------------
HttpUrl rippledUrl = HttpUrl.get("https://s.altnet.rippletest.net:51234/");
XrplClient xrplClient = new XrplClient(rippledUrl);

このチュートリアルでは、以下のボタンをクリックすることでブラウザーから直接接続できます。

接続ステータス: 接続されていません

2. トランザクションの準備

通常は、XRP LedgerトランザクションをオブジェクトとしてJSONトランザクションフォーマットで作成します。以下の例に、必要最小限の送金仕様を示します。

{
  "TransactionType": "Payment",
  "Account": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
  "Amount": "2000000",
  "Destination": "rUCzEr6jrEyMpjhs4wSdQdz4g8Y382NxfM"
}

XRP送金に対して指定する必要がある必要最小限の指示は次のとおりです。

  • これが送金であることを示すインディケーター("TransactionType": "Payment"
  • 送信元アドレス("Account"
  • XRPを受け取るアドレス("Destination")。このアドレスは送信元アドレスと同じものではいけません。
  • 送金するXRP額("Amount")。通常、XRPの「drop数」を示す整数として指定します。1,000,000ドロップは1 XRPです。

技術上、一部の追加のフィールドは実行可能なトランザクションに含める必要があり、また、省略可能なフィールドでも、LastLedgerSequenceなどは含めることを強く推奨します。autofill()メソッド は、トランザクションの残りのフィールドに適切なデフォルトを自動的に入力します。上記の送金を準備する際の例を示します。

// Prepare transaction -------------------------------------------------------
  const prepared = await client.autofill({
    "TransactionType": "Payment",
    "Account": wallet.address,
    "Amount": xrpl.xrpToDrops("22"),
    "Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"
  })
  const max_ledger = prepared.LastLedgerSequence
  console.log("Prepared transaction instructions:", prepared)
  console.log("Transaction cost:", xrpl.dropsToXrp(prepared.Fee), "XRP")
  console.log("Transaction expires after ledger:", max_ledger)
# Prepare transaction ----------------------------------------------------------
my_payment = xrpl.models.transactions.Payment(
    account=test_wallet.address,
    amount=xrpl.utils.xrp_to_drops(22),
    destination="rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
)
print("Payment object:", my_payment)
// Prepare transaction --------------------------------------------------------
// Look up your Account Info
AccountInfoRequestParams requestParams = AccountInfoRequestParams.builder()
  .account(classicAddress)
  .ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build();
AccountInfoResult accountInfoResult = xrplClient.accountInfo(requestParams);
UnsignedInteger sequence = accountInfoResult.accountData().sequence();

// Request current fee information from rippled
FeeResult feeResult = xrplClient.fee();
XrpCurrencyAmount openLedgerFee = feeResult.drops().openLedgerFee();

// Get the latest validated ledger index
LedgerIndex validatedLedger = xrplClient.ledger(
  LedgerRequestParams.builder()
  .ledgerSpecifier(LedgerSpecifier.VALIDATED)
  .build()
)
  .ledgerIndex()
  .orElseThrow(() -> new RuntimeException("LedgerIndex not available."));

// LastLedgerSequence is the current ledger index + 4
UnsignedInteger lastLedgerSequence = validatedLedger.plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue();

// Construct a Payment
Payment payment = Payment.builder()
  .account(classicAddress)
  .amount(XrpCurrencyAmount.ofXrp(BigDecimal.ONE))
  .destination(Address.of("rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"))
  .sequence(sequence)
  .fee(openLedgerFee)
  .signingPublicKey(randomKeyPair.publicKey())
  .lastLedgerSequence(lastLedgerSequence)
  .build();
System.out.println("Constructed Payment: " + payment);
送金する額:
XRP

3. トランザクションの指示への署名

xrpl.jsのWallet.sign()メソッド を使用して、トランザクションに署名します。最初の引数は、署名するJSONトランザクションの文字列バージョンです。

// Sign prepared instructions ------------------------------------------------
  const signed = wallet.sign(prepared)
  console.log("Identifying hash:", signed.hash)
  console.log("Signed blob:", signed.tx_blob)
# Sign transaction -------------------------------------------------------------
signed_tx = xrpl.transaction.autofill_and_sign(
        my_payment, client, test_wallet)
max_ledger = signed_tx.last_ledger_sequence
tx_id = signed_tx.get_hash()
print("Signed transaction:", signed_tx)
print("Transaction cost:", xrpl.utils.drops_to_xrp(signed_tx.fee), "XRP")
print("Transaction expires after ledger:", max_ledger)
print("Identifying hash:", tx_id)
// Sign transaction -----------------------------------------------------------
// Construct a SignatureService to sign the Payment
SignatureService<PrivateKey> signatureService = new BcSignatureService();

// Sign the Payment
SingleSignedTransaction<Payment> signedPayment = signatureService.sign(randomKeyPair.privateKey(), payment);
System.out.println("Signed Payment: " + signedPayment.signedTransaction());

署名処理の結果は、署名を含むトランザクションオブジェクトになります。通常、XRP Ledger APIは、署名済みトランザクションがトランザクションの正規のバイナリーフォーマット(「ブロブ」と呼ばれる)の16進数表現になることを想定しています。

署名APIは、トランザクションのID、つまり識別用ハッシュを返します。この識別用ハッシュは、後でトランザクションを検索する際に使用します。識別用ハッシュは、このトランザクションに固有の64文字の16進文字列です。

4. 署名済みブロブの送信

トランザクションをネットワークに送信します。

// Submit signed blob --------------------------------------------------------
  const tx = await client.submitAndWait(signed.tx_blob)
# Submit transaction -----------------------------------------------------------
try:
    tx_response = xrpl.transaction.submit_and_wait(signed_tx, client)
except xrpl.transaction.XRPLReliableSubmissionException as e:
    exit(f"Submit failed: {e}")
// Submit transaction ---------------------------------------------------------
SubmitResult<Payment> paymentSubmitResult = xrplClient.submit(signedPayment);
System.out.println(paymentSubmitResult);

このメソッドは、ローカルでトランザクションを適用しようと試みたときの一時的な結果を返します。この結果は、トランザクションが検証済みレジャーに含まれた時点で変わる_可能性があります_。当初は成功していたトランザクションが最終的に失敗となったり、当初失敗していたトランザクションが最終的に成功する場合があります。しかしながら、一時的な結果はほとんどの場合は最終結果と一致するため、ここでtesSUCCESSが表示されたらひとまず安心しても問題ありません。😁

他の結果が表示された場合は、以下の点を確認します。

  • 送信元および送信先の正しいアドレスを使用しているか。
  • トランザクションの他のフィールドへの入力漏れ、ステップのスキップ、その他の入力ミスがないか。
  • トランザクションの送信に必要なTest Net XRPが十分にあるか。送金できるXRPの額は、必要準備金によって制限されています。現時点では、20XRPに加えて、レジャー内に保有している各「オブジェクト」につき5XRPずつ追加となります。(Test Net Faucetを使用して新しいアドレスを生成した場合は、保有するオブジェクトはありません。)
  • テストネットワークのサーバーに接続しているか。

他の可能性については、トランザクション結果の完全なリストを参照してください。

送信中...

5. 検証の待機

ほとんどのトランザクションは送信後の次のレジャーバージョンに承認されます。つまり、4~7秒でトランザクションの結果が最終的なものになる可能性があります。XRP Ledgerがビジーになっているか、ネットワーク接続の品質が悪いためにトランザクションをネットワーク内で中継する処理が遅延した場合は、トランザクション確定までにもう少し時間がかかることがあります。(トランザクションの有効期限を設定する方法については、信頼できるトランザクションの送信を参照してください。)

// Wait for validation -------------------------------------------------------
  // submitAndWait() handles this automatically, but it can take 4-7s.
# Wait for validation ----------------------------------------------------------
# submit_and_wait() handles this automatically, but it can take 4-7s.
// Wait for validation --------------------------------------------------------
TransactionResult<Payment> transactionResult = null;

boolean transactionValidated = false;
boolean transactionExpired = false;
while (!transactionValidated && !transactionExpired) {
  Thread.sleep(4 * 1000);

  LedgerIndex latestValidatedLedgerIndex = xrplClient.ledger(
    LedgerRequestParams.builder()
    .ledgerSpecifier(LedgerSpecifier.VALIDATED)
    .build()
  )
    .ledgerIndex()
    .orElseThrow(() -> new RuntimeException("Ledger response did not contain a LedgerIndex."));

  transactionResult = xrplClient.transaction(TransactionRequestParams.of(signedPayment.hash()), Payment.class);

  if (transactionResult.validated()) {
    System.out.println("Payment was validated with result code " + transactionResult.metadata().get().transactionResult());
    transactionValidated = true;
  } else {
    boolean lastLedgerSequenceHasPassed = FluentCompareTo.is(latestValidatedLedgerIndex.unsignedIntegerValue())
      .greaterThan(UnsignedInteger.valueOf(lastLedgerSequence.intValue()));
    if (lastLedgerSequenceHasPassed) {
      System.out.println("LastLedgerSequence has passed. Last tx response: " + transactionResult);
      transactionExpired = true;
    } else {
      System.out.println("Payment not yet validated.");
    }
}
トランザクションのID: (無)
最新の検証レジャーインデックス: 接続されていません
送信時のレジャーインデックス: (まだ送信されません)
トランザクションのLastLedgerSequence: (準備されません)

6. トランザクションステータスの確認

トランザクションが行った内容を正確に把握するために、トランザクションが検証済みレジャーバージョンに記録されたときにトランザクションの結果を調べる必要があります。例えば、txメソッドを使用して、トランザクションのステータスを確認できます。

// Check transaction results -------------------------------------------------
  console.log("Transaction result:", tx.result.meta.TransactionResult)
  console.log("Balance changes:", JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2))
# Check transaction results ----------------------------------------------------
import json
print(json.dumps(tx_response.result, indent=4, sort_keys=True))
print(f"Explorer link: https://testnet.xrpl.org/transactions/{tx_id}")
metadata = tx_response.result.get("meta", {})
if metadata.get("TransactionResult"):
    print("Result code:", metadata["TransactionResult"])
if metadata.get("delivered_amount"):
    print("XRP delivered:", xrpl.utils.drops_to_xrp(
                metadata["delivered_amount"]))
// Check transaction results --------------------------------------------------
System.out.println(transactionResult);
System.out.println("Explorer link: https://testnet.xrpl.org/transactions/" + signedPayment.hash());
transactionResult.metadata().ifPresent(metadata -> {
  System.out.println("Result code: " + metadata.transactionResult());
  metadata.deliveredAmount().ifPresent(deliveredAmount ->
    System.out.println("XRP Delivered: " + ((XrpCurrencyAmount) deliveredAmount).toXrp()));
  }
);

注意: APIは、まだ検証されていないレジャーバージョンからの暫定的な結果を返す場合があります。例えば、rippled APIのtxメソッドを使用した場合は、応答内の"validated": trueを探して、データが検証済みレジャーバージョンからのものであることを確認してください。検証済みレジャーバージョンからのものではないトランザクション結果は、変わる可能性があります。詳細は、結果のファイナリティーを参照してください。

本番環境の場合の相違点

本番XRP LedgerでXRPを送金する場合も、大部分の手順は同じです。ただし、必要なセットアップでは重要な相違点がいくつかあります。

実際のXRPアカウントの取得

このチュートリアルでは、Test Net XRPがすでに資金供給されているアドレスをボタンで取得しましたが、それが可能だったのはTest Net XRPに何の価値もないからです。実際のXRPでは、XRPを所有している他者からXRPを入手する必要があります。(たとえば、取引所で購入する方法など。)xrpl.jsのWallet()クラス を使用して、本番またはTestnetで機能するアドレスとシークレットを生成できます。

const wallet = new xrpl.Wallet()
console.log(wallet.address) // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
console.log(wallet.seed) // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
from xrpl.wallet import Wallet
my_wallet = Wallet.create()
print(my_wallet.address) # Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
print(my_wallet.seed)            # Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
WalletFactory walletFactory = DefaultWalletFactory.getInstance();
SeedWalletGenerationResult generationResult = walletFactory.randomWallet(false);
Wallet wallet = generationResult.wallet();
System.out.println(wallet.classicAddress()); // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
System.out.println(generationResult.seed()); // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs

警告: ローカルマシンで安全な方法で生成したアドレスとシークレットのみを使用してください。別のコンピューターでアドレスとシークレットを生成して、ネットワーク経由でそれらを自分に送信した場合は、ネットワーク上の他の人がその情報を見ることができる可能性があります。その情報見ることができる人は、あなたと同じようにあなたのXRPを操作できます。また、Test Netと本番で同じアドレスを使用しないことも推奨します。指定したパラメーターによっては、一方のネットワークに向けて作成したトランザクションが、もう一方のネットワークでも実行可能になるおそれがあるためです。

アドレスとシークレットを生成しても、直接XRPを入手できるわけではありません。単に乱数を選択しているだけです。また、そのアドレスでXRPを受け取ってアカウントに資金供給する必要があります。XRPを取得する方法として最も一般的なのは、取引所から購入し、所有しているアドレスに入れる方法です。詳細は、XRP Overviewを参照してください。

本番XRP Ledgerへの接続

XRP Ledgerと同期しているサーバーを指定する必要があります。多くの場合は公開サーバーを、以下のスニペットなどで使用できます。

const xrpl = require('xrpl')
const api = new xrpl.Client('wss://xrplcluster.com')
api.connect()
from xrpl.clients import JsonRpcClient
client = JsonRpcClient("https://xrplcluster.com")
final HttpUrl rippledUrl = HttpUrl.get("https://xrplcluster.com");
XrplClient xrplClient = new XrplClient(rippledUrl);

ヒント: ローカル接続では、WebSocketプロトコルのTLSで暗号化されたバージョン(wss)ではなく、暗号化されていないバージョン(ws)を使用します。この方式は、通信が同じマシンの中だけで行われてマシンの外に出て行かないという点で安全で、TLS証明書が不要であるため設定が簡単です。外部ネットワークとの接続では、必ずwssを使用してください。

次のステップ

このチュートリアルを完了後は、以下を試してみてください。