🌉

ブリッジ送金を試してみた

2023/04/06に公開

XRP Ledgerとは

XRP LedgerはBitcoinやEthereumなどと同じ分散型のパブリックブロックチェーンです。PoWやPoSとは異なる独自のコンセンサスアルゴリズムが用いられています。
コミュニティによりAMMやHooks(スマコン)などさまざまな機能が開発されており、2022年にはNFT機能がメインネットで有効化されました。
本記事ではXLS-38dとして提案されているCross-Chain Bridgeの機能を試してみます。

サイドチェーンとは

サイドチェーンとは、メインチェーンとは別に存在するチェーンのことです。XRP Ledgerにおけるサイドチェーンは、XRP Ledgerのメインチェーンとは別に存在するチェーンのことです。メインチェーンからサイドチェーンへ送金を行う場合、チェーンが異なるため直接的な送金はできません。そのため、メインチェーンとサイドチェーンを繋ぐブリッジと呼ばれる仕組みが必要になります。

XLS-38dではその仕組みを提案しています。

XLS-38dの詳細は以下の記事を参照してください。

https://zenn.dev/tequ/articles/orverview-xchain

ドキュメントは以下のリンクから確認できます。
https://opensource.ripple.com/docs/xls-38d-cross-chain-bridge/cross-chain-bridges/

Devnet環境

先日、XRP Ledgerのサイドチェーン機能がDevnet環境で利用可能になりました。今回はこのDevnet環境を利用して、サイドチェーン機能(ブリッジ送金)を試してみます。

https://dev.to/ripplexdev/xrp-ledger-sidechains-available-on-new-devnet-24ep

ブリッジオブジェクトの確認

APIメソッドの ledger_entry を利用して、ロックチェーンと発行チェーンそれぞれのブリッジオブジェクトを確認し手みましょう。

import { Client } from "xrpl";

const lockClient = new Client(
  "wss://sidechain-net1.devnet.rippletest.net:51233"
);
const issueClient = new Client(
  "wss://sidechain-net2.devnet.rippletest.net:51233"
);

const main = async () => {
  await lockClient.connect();
  const res1 = await lockClient.request({
    command: "ledger_entry",
    bridge_account: "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4",
  });
  console.log(res1.result.node);

  await issueClient.connect();
  const res2 = await issueClient.request({
    command: "ledger_entry",
    bridge_account: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
  });
  console.log(res2.result.node);
};

main();
// ロックチェーン
{
  Account: 'rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4',
  Flags: 0,
  LedgerEntryType: 'Bridge',
  MinAccountCreateAmount: '10000000',
  OwnerNode: '0',
  PreviousTxnID: '444E4187E4ABD040D387A0AF2F44047EBC6F226A1E34F86759EDAA6723248A8B',
  PreviousTxnLgrSeq: 193375,
  SignatureReward: '100',
  XChainAccountClaimCount: '21',
  XChainAccountCreateCount: '3e',
  XChainBridge: {
    IssuingChainDoor: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
    IssuingChainIssue: { currency: 'XRP' },
    LockingChainDoor: 'rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4',
    LockingChainIssue: { currency: 'XRP' }
  },
  XChainClaimID: '15d',
  index: '1340B1D2EB3E2A5E4CA16E03F8D5B24D3DFDF6FF71B6BF71AC3C1084274C717D'
}
// 発行チェーン
{
  Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
  Flags: 0,
  LedgerEntryType: 'Bridge',
  MinAccountCreateAmount: '10000000',
  OwnerNode: '0',
  PreviousTxnID: '0AE6777E7EA68B2BB5F4B9662725731D1F72979F67560A43DFE8E5271DD937A6',
  PreviousTxnLgrSeq: 193286,
  SignatureReward: '100',
  XChainAccountClaimCount: '3e',
  XChainAccountCreateCount: '21',
  XChainBridge: {
    IssuingChainDoor: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
    IssuingChainIssue: { currency: 'XRP' },
    LockingChainDoor: 'rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4',
    LockingChainIssue: { currency: 'XRP' }
  },
  XChainClaimID: '196',
  index: '469372BEE8814EC52CA2AECB5374AB57A47B53627E3C0E2ACBE3FDC78DBFEC7B'
}

ロックチェーン→発行チェーン、発行チェーン→ロックチェーンいずれもブリッジ手数料が100drop(=0.000100XRP)であることなどが確認できます。

ロックチェーンから発行チェーンへのアカウント作成を伴うブリッジ送金

最初はロックチェーンにのみトークンを保持しているはずです。ロックチェーンから発行チェーンへ送金を行ってみましょう。

XChainAccountCreateCommitトランザクション

発行チェーンにトークンを保持していないということは有効なアカウントもないということです。XChainAccountCreateCommitトランザクションを利用し、発行チェーンへのブリッジ送金と同時にアカウントを作成します。

import { Client, Wallet, xrpToDrops } from "xrpl";

const client = new Client("wss://sidechain-net1.devnet.rippletest.net:51233");
const lockWallet = Wallet.fromSecret("snXzk7Y78C4Xa3Ktwumb9AmbjqCxH");
const issueWallet = Wallet.fromSecret("shecVzCqMcxLvpAqHjiuGQcYw3eW1");
const main = async () => {
  await client.connect();
  const response = await client.submitAndWait(
    {
      Account: lockWallet.address,
      TransactionType: "XChainAccountCreateCommit",
      Amount: xrpToDrops(100),
      Destination: issueWallet.address,
      // ブリッジ手数料(Amountとは別にアカウントから差し引かれます。)
      SignatureReward: "100",
      // 利用するブリッジを指定
      XChainBridge: {
        IssuingChainDoor: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
        IssuingChainIssue: {
          currency: "XRP",
        },
        LockingChainDoor: "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4",
        LockingChainIssue: {
          currency: "XRP",
        },
      },
    },
    { wallet: lockWallet }
  );
  console.log(response.result)
};

main();

ブリッジ送金とアカウント作成のトランザクションの確認

トランザクションの送信後、ロックチェーンではXChainAccountCreateCommitトランザクションが確認され、発行チェーンでは複数のXChainAddAccountCreateAttestationトランザクションが確認されます。

発行チェーン側のXChainAddAccountCreateAttestationトランザクションは複数のアカウントにより送信されており、トランザクション内に含まれるSignatureはドアアカウントの(マルチシグ)署名者のうちの1人が署名したものです。
このドアアカウントは4 of 5のマルチシグとなっているため、XChainAddAccountCreateAttestationトランザクションにより4つの署名が集まった時点でブリッジ送金が完了し、アカウントが作成されます。

新しいAccountRootが作成されていることが確認できると思います。

ロックチェーンから発行チェーンへのブリッジ送金

今度はアカウントの作成を伴わないブリッジ送金を行ってみましょう。

ClaimIDの取得

まずは発行チェーン側でClaimIDを取得します。

import { Client, Wallet } from "xrpl";

const client = new Client("wss://sidechain-net2.devnet.rippletest.net:51233");
const issueWallet = Wallet.fromSecret("shecVzCqMcxLvpAqHjiuGQcYw3eW1");
const lockWallet = Wallet.fromSecret("snXzk7Y78C4Xa3Ktwumb9AmbjqCxH");
const main = async () => {
  await client.connect();
  const response = await client.submitAndWait(
    {
      Account: issueWallet.address,
      TransactionType: "XChainCreateClaimID",
      OtherChainSource: lockWallet.address,
      SignatureReward: "100",
      XChainBridge: {
        IssuingChainDoor: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
        IssuingChainIssue: {
          currency: "XRP",
        },
        LockingChainDoor: "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4",
        LockingChainIssue: {
          currency: "XRP",
        },
      },
    },
    { wallet: issueWallet }
  );
  console.log(response.result);
};

main();

ClaimIDが取得できました。

ブリッジ送金

次に作成したClaimIDを使ってロックチェーン側から発行チェーンへのブリッジ送金を行います。

import { Client, Wallet, xrpToDrops } from "xrpl";

const client = new Client("wss://sidechain-net1.devnet.rippletest.net:51233");
const lockWallet = Wallet.fromSecret("snXzk7Y78C4Xa3Ktwumb9AmbjqCxH");
const main = async () => {
  await client.connect();
  const response = await client.submitAndWait(
    {
      Account: lockWallet.address,
      TransactionType: "XChainCommit",
      Amount: xrpToDrops(100),
      XChainClaimID:  '1ae'.padStart(16, '0').toUpperCase(),
      XChainBridge: {
        IssuingChainDoor: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
        IssuingChainIssue: {
          currency: "XRP",
        },
        LockingChainDoor: "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4",
        LockingChainIssue: {
          currency: "XRP",
        },
      },
    },
    { wallet: lockWallet }
  );
  console.log(response.result)
};

main();

ロックチェーン側ではClaimID(1ae)を使用したXChainCommitトランザクションが確認できます。
このトランザクションでドアアカウントへ100XRPが送信されます。

発行チェーン側ではXChainAddClaimAttestationトランザクションが複数の署名者により送信されていることが確認できます。

資金の請求

発行チェーン側においてClaimIDを指定してXChainClaimトランザクションを送信することで、ブリッジ送金した資金の請求を行うことができます。

請求後は指定したClaimIDは削除されるため、二重支払いは不可能となります。
一方何らかの原因で失敗した場合はClaimIDへの影響はないため、再度XChainClaimトランザクションを送信することで、請求を再試行することができます。

import { Client, Wallet, xrpToDrops } from "xrpl";

const client = new Client("wss://sidechain-net2.devnet.rippletest.net:51233");
const issueWallet = Wallet.fromSecret("shecVzCqMcxLvpAqHjiuGQcYw3eW1");
const main = async () => {
  await client.connect();
  const response = await client.submitAndWait(
    {
      Account: issueWallet.address,
      TransactionType: "XChainClaim",
      Amount: xrpToDrops(100),
      Destination: issueWallet.address,
      XChainClaimID: "1ae".padStart(16, "0").toUpperCase(),
      XChainBridge: {
        IssuingChainDoor: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
        IssuingChainIssue: {
          currency: "XRP",
        },
        LockingChainDoor: "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4",
        LockingChainIssue: {
          currency: "XRP",
        },
      },
    },
    { wallet: issueWallet }
  );
  console.log(response.result);
};

main();

XChainClaimトランザクションを送信すると、ドアアカウントから指定したアドレス(Destination)への送金が行われます。

発行チェーンのドアアカウントであるrMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4の残高が100,000,000drop(100XRP)減少し、指定アドレスのrfLeBjYnNCkxV8mBwKJxVvRVfcfeE7CPpTの残高が100,000,000drop(100XRP)増加していることが確認できます。

コード

今回使用したコードは以下のリポジトリにあります。
https://github.com/develoQ/xrpl-sample/tree/main/samples/sidechain-bridge

まとめ

今回は、XRP Ledgerのブリッジ機能を使ったブリッジ送金の方法を紹介しました。
XChain機能ではWitnessサーバへのトラストがあれば、ネイティブなトランザクションを利用して簡単にブリッジ送金を行うことが出来ることが分かるかと思います。
今回の内容はXRP LedgerメインネットとXRP Ledgerをベースにしたサイドチェーン間のブリッジであり、両チェーンにおいてXChain機能を利用しました。一方のチェーンがXChain機能をサポートしていない場合(EVMサイドチェーンなど)は、別途XChain機能と同じような機能を実装する必要があります。

興味を持たれた方はXRP Ledger開発者のDiscordチャンネルへ是非お越しください!
日本語チャンネルもありますので、英語ができなくても大丈夫です!
https://xrpldevs.org

Discussion