🏦

XRPLでのAMM機能実装ガイド②:AMMへの流動性追加 (AMMDeposit)

に公開

はじめに

前回の記事「XRPLでのAMM機能実装ガイド①:セットアップからプール作成まで」では、XRP Ledger (XRPL) 上でAMM(Automated Market Maker)プールを作成する基本的な流れを解説しました。今回はその続きとして、作成したAMMプールに流動性を追加するための AMMDeposit トランザクションについて詳しく見ていきます。

流動性の提供はAMMの根幹をなす活動であり、これによりユーザーはスムーズなトークン交換が可能になります。流動性提供者(LP)は、その見返りとしてLPトークンを受け取り、取引手数料の一部を収益として得ることができます。

この記事では、AMMDeposit の基本的な使い方から、シングルアセット入金とダブルアセット入金の違い、そして重要なフラグやエラーケースまでを網羅的に解説します。

AMMDepositとは?

AMMDeposit は、既存のAMMプールに資産(XRPまたはIOUトークン)を追加するためのトランザクションタイプです。流動性を提供することで、LPはその貢献度に応じたLPトークンを受け取ります。このLPトークンは、預けた資産のシェアを表し、将来流動性を引き出す際や、AMMの運営(手数料率の投票など)に関与する際に使用されます。

入金方法の種類:シングルアセット vs ダブルアセット

AMMへの流動性提供には、大きく分けて2つの方法があります。

1. ダブルアセット入金 (Dual Asset Deposit)

AMMプールを構成する両方の資産を、プール内の現在の資産比率になるべく近い形で入金する方法です。

  • メリット: プールの資産比率を大きく変動させずに流動性を追加でき、一般的に受け取れるLPトークンの量を最大化しやすいです。スリッページ(価格変動による損失)の影響を抑えやすい傾向があります。
  • 指定フィールド: AmountAmount2 の両方を指定します。
  • フラグ: tfTwoAsset または tfLPToken フラグを使用します。※tfLPTokenの場合は、Amount フィールドではなく、LPTokenOut フィールドで指定する必要があります。

2. シングルアセット入金 (Single Asset Deposit)

AMMプールを構成する資産のうち、片方の資産のみを入金する方法です。

  • メリット: 片方の資産しか保有していない場合や、意図的にプールの資産比率を調整したい場合に利用できます。
  • デメリット: 入金する資産と反対側の資産がプールから実質的に引き出される(内部的な交換が行われる)ため、ダブルアセット入金と比較してスリッページが大きくなる可能性があります。受け取れるLPトークンの量も、プールの状態によっては少なくなることがあります。
  • 指定フィールド:
    • 入金する資産量を指定する場合: Amount を指定します。
    • 受け取りたいLPトークン量を指定する場合: LPTokenOut を指定します。
    • スリッページを考慮する場合: EPrice (実効価格) や tfLimitLPToken フラグと LPTokenOut (最小受取量) を指定します。
  • フラグ: tfSingleAsset フラグ、または tfOneAssetLPToken フラグを使用します。スリッページ制御のために tfLimitLPToken フラグを組み合わせることも可能です。

AMMDepositトランザクションの構造

AMMDeposit トランザクションを構成する主要なフィールドを見ていきましょう。

{
  "TransactionType": "AMMDeposit",
  "Account": "r...", // 流動性を提供するアカウントアドレス
  "Asset": {         // プールの一方の資産情報 (IOUトークンの場合)
    "currency": "XYZ", // 3文字の通貨コード
    "issuer": "r..."   // 発行者のアドレス
  },
  "Asset2": {        // プールのもう一方の資産情報
    "currency": "XRP"  // XRPの場合、issuerは不要
                     // IOUトークンの場合は Asset と同様に currency と issuer が必要
  },
  // --- 以下は入金タイプに応じて指定 ---
  // IOUトークンの量を指定する場合 (AmountObject)
  "Amount": {
    "currency": "XYZ",
    "issuer": "r...",
    "value": "1000"    // 文字列で量を指定
  },
  // XRPの量を指定する場合 (string - Drops単位)
  // "Amount": "1000000", // 1 XRP = 1,000,000 drops

  // Amount2 も同様に、IOUトークンなら AmountObject、XRPなら string (Drops) で指定
  "Amount2": "50000000", // 例: 50 XRP

  // 受け取りたいLPトークンの量を指定する場合 (AmountObject)
  "LPTokenOut": {
    "currency": "03...", // LPトークンの160-bit通貨コード (HEX文字列)
    "issuer": "r...",   // AMMアカウントのアドレス
    "value": "100"     // 文字列で量を指定
  },
  // "EPrice": "...", // 実効価格の上限
  "Flags": 0           // オプションフラグ (後述)
  // ... その他の共通フィールド (Fee, Sequenceなど)
}

重要な点:

  • Asset / Asset2 フィールド:
    • IOUトークンを指定する場合、必ず currency (3文字のコード) と issuer (発行者のアドレス) の両方を含むオブジェクトで指定します。
    • XRPを指定する場合、currency: "XRP" のみを含むオブジェクトで指定します (issuer は不要です)。
  • Amount / Amount2 フィールド:
    • IOUトークンの量を指定する場合、currency, issuer, value を含むオブジェクト(AmountObject)で指定します。value は文字列で量を表します。
    • XRPの量を指定する場合、Drops単位の量を文字列で指定します (例: "1000000" は 1 XRP)。
  • LPTokenOut フィールド: 受け取るLPトークン量を指定する場合、LPトークンの currency (160-bit HEX)、issuer (AMMアカウントアドレス)、value (受け取りたい量) を含むオブジェクトで指定します。

重要なフラグ解説

AMMDeposit トランザクションでは、Flags フィールドを使って入金の詳細な挙動を制御できます。フラグはビット値であり、複数のフラグを組み合わせることが可能です(ただし、矛盾する組み合わせはできません)。組み合わせる場合は、各フラグの値をビット単位でOR演算した結果(合計値)を Flags フィールドに設定します。

フラグ名 値 (Decimal) 値 (Hex) 説明
tfLPToken 1048576 0x00010000 ダブルアセット入金 を行います。LPTokenOut のみで指定する必要があります。
tfTwoAsset 1048576 0x00100000 ダブルアセット入金 を行います。AmountAmount2 の両方を指定する必要があります。
tfSingleAsset 131072 0x00080000 シングルアセット入金 を行います。どちらか一方を Amount で指定します。
tfOneAssetLPToken 262144 0x00200000 シングルアセット入金 で、受け取る LPトークンの量を指定 します。LPTokenOut フィールドが必須です。
tfLimitLPToken 524288 0x00400000 1つの資産につき指定された金額まで入金できる。LPトークンごとに指定された実効価格(手数料後)を超えて支払うことはできません。

注意点:

  • tfTwoAssettfSingleAsset は互いに排他的です。
  • tfTwoAssettfOneAssetLPToken は互いに排他的です。
  • tfSingleAssettfOneAssetLPToken は互いに排他的です。

コード例 (xrpl.js)

前回の記事でセットアップした環境とアカウント (aliceWallet, issuerWallet, AMM情報) を利用する想定で、いくつかの入金パターンのコード例を示します。(※エラーハンドリング等は省略しています。型定義は xrpl.js のバージョンによって異なる可能性があるため、適宜調整してください。)

import { Client, Wallet, xrpToDrops, AMMDeposit, Amount } from "xrpl"; // AMMDeposit と Amount をインポート

// 前回の記事で作成したクライアント、ウォレット、AMM情報を想定
// const client = new Client("wss://s.altnet.rippletest.net:51233");
// await client.connect();
// const aliceWallet = Wallet.fromSeed("s..."); // Aliceのシード
// const issuerWallet = Wallet.fromSeed("s..."); // Issuerのシード
// const ammAsset = { currency: "XYZ", issuer: issuerWallet.address }; // 例: IOU Asset
// const ammAsset2 = { currency: "XRP" }; // 例: XRP Asset

// --- 共通関数 ---
// (submitTx 関数は変更なし)
async function submitTx(client, tx, wallet) {
  console.log("トランザクション準備:", JSON.stringify(tx, null, 2));
  const prepared = await client.autofill(tx);
  console.log("Autofill完了:", JSON.stringify(prepared, null, 2));
  const signed = wallet.sign(prepared);
  console.log("署名完了:", signed.tx_blob);
  const result = await client.submitAndWait(signed.tx_blob);
  console.log("送信結果:", JSON.stringify(result, null, 2));
  if (result.result.meta.TransactionResult === "tesSUCCESS") {
    console.log("入金成功!");
  } else {
    console.error("入金失敗:", result.result.meta.TransactionResult);
  }
  return result;
}

// --- 1. ダブルアセット入金(Flags: tfTwoAsset) ---
async function depositTwoAsset(client, wallet, iouAsset: Amount, xrpAmountDrops: string) {
  // asset (IOU) と asset2 (XRP) の Amount を正しく設定
  const tx: AMMDeposit = {
    TransactionType: "AMMDeposit",
    Account: wallet.address,
    Asset: iouAsset, // { currency: 'XYZ', issuer: 'r...' }
    Asset2: { currency: "XRP" },
    Amount: iouAsset, // IOUトークンのAmountObject (例: { currency: 'XYZ', issuer: 'r...', value: '500' })
    Amount2: xrpAmountDrops, // XRP量 (Drops単位の文字列, 例: "25000000")
    Flags: 1048576, // tfTwoAsset
  };
  await submitTx(client, tx, wallet);
}

// --- 2. ダブルアセット入金(Flags: tfLPToken) ---
async function depositTwoAssetLPToken(client, wallet, iouAsset: Amount, xrpAmountDrops: string) {
  // asset (IOU) と asset2 (XRP) の Amount を正しく設定
  const tx: AMMDeposit = {
    TransactionType: "AMMDeposit",
    Account: wallet.address,
    Asset: iouAsset, // { currency: 'XYZ', issuer: 'r...', value: '100' }
    Asset2: { currency: "XRP" },
    LPTokenOut: lpAmountObj, //  { currency: lpTokenInfo.currency, issuer: 'r...' }
    Flags: 65536,   // tfLPToken
  };
  await submitTx(client, tx, wallet);
}

// LPトークンの情報は以下のように取得できます。
const ammResponse = await client.request({
    command: "amm_info",
    asset: {
      currency: currencyCode,
      issuer: issuer
    },
    asset2: {
      currency: "XRP"
    }
});
const lpTokenInfo = ammResponse.result.amm.lp_token;

const lpAmountObj = {
    currency: lpTokenInfo.currency,
    issuer: lpTokenInfo.issuer,
    value: "100"
};

// --- 3. シングルアセット入金 (IOUトークン量指定) ---
async function depositSingleAssetIOU(client, wallet, iouAsset: Amount) {
  const tx: AMMDeposit = {
    TransactionType: "AMMDeposit",
    Account: wallet.address,
    Asset: iouAsset, // 入金するIOU資産
    Asset2: { currency: "XRP" }, // プールのもう片方の資産 (XRP)
    Amount: iouAsset, // 入金するIOUのAmountObject (例: { currency: 'XYZ', issuer: 'r...', value: '100' })
    Flags: 524288,  // tfSingleAsset
    // Amount2 は指定しない
  };
  await submitTx(client, tx, wallet);
}

// --- 2b. シングルアセット入金 (XRP量指定) ---
async function depositSingleAssetXRP(client, wallet, iouAssetInfo: Amount, xrpAmountDrops) {
  const tx: AMMDeposit = {
    TransactionType: "AMMDeposit",
    Account: wallet.address,
    Asset: iouAssetInfo, // プールのIOU資産情報 { currency: 'XYZ', issuer: 'r...' }
    Asset2: { currency: "XRP" }, // 入金するXRP資産
    Amount: xrpAmountDrops, // 入金するXRP量 (Drops単位の文字列, 例: "10000000")
    Flags: 524288,  // tfSingleAsset
    // Amount は指定しない
  };
  await submitTx(client, tx, wallet);
}

// --- 3. シングルアセット入金 (受取LPトークン量指定) ---
async function depositSingleAssetLPToken(client, wallet, poolAsset: Amount, poolAsset2: Amount, lpTokenAmount: Amount) {
  const tx: AMMDeposit = {
    TransactionType: "AMMDeposit",
    Account: wallet.address,
    Asset: poolAsset,
    Asset2: poolAsset2,
    LPTokenOut: lpTokenAmount, // 受け取りたいLPトークンのAmountObject
    Flags: 2097152, // tfOneAssetLPToken
    // Amount, Amount2 はオプションで上限として指定可能
  };
  await submitTx(client, tx, wallet);
}

// --- 実行例 ---
/*
(async () => {
  const client = new Client("wss://s.altnet.rippletest.net:51233");
  await client.connect();
  
  const aliceWallet = Wallet.fromSeed("s..."); // 取得したAliceのシードに置き換える
  const issuerWallet = Wallet.fromSeed("s..."); // 取得したIssuerのシードに置き換える

  // --- 資産定義の例 ---
  const xyzCurrency = "XYZ";
  const issuerAddress = issuerWallet.address;
  const lpTokenIssuer = "r..."; // AMMアカウントアドレス (事前に amm_info で取得)
  const lpTokenCurrency = "03..."; // LPトークン通貨コード (事前に amm_info で取得)

  const iouAssetInfo: Amount = { currency: xyzCurrency, issuer: issuerAddress }; // IOU情報のみ
  const xrpAssetInfo: Amount = { currency: "XRP" }; // XRP情報

  const depositIouAmount: Amount = { ...iouAssetInfo, value: "500" }; // 預けるIOU量
  const depositXrpDrops: string = xrpToDrops("25"); // 預けるXRP量 (Drops)

  const receiveLpAmount: Amount = { currency: lpTokenCurrency, issuer: lpTokenIssuer, value: "50" }; // 受け取りたいLPトークン量
  const minReceiveLpAmount: Amount = { currency: lpTokenCurrency, issuer: lpTokenIssuer, value: "45" }; // 最低受け取りたいLPトークン量

  // --- 実行呼び出し例 ---
  // ダブルアセット入金
  // await depositTwoAsset(client, aliceWallet, depositIouAmount, depositXrpDrops);

  // シングルアセット入金 (IOUトークン指定)
  // await depositSingleAssetIOU(client, aliceWallet, depositIouAmount);

  // シングルアセット入金 (XRP指定)
  // await depositSingleAssetXRP(client, aliceWallet, iouAssetInfo, depositXrpDrops);

  await client.disconnect();
})();
*/

注意: 上記コードは基本的な例です。実際の実装では、エラーハンドリング、AMM情報の動的な取得、Amount オブジェクトの正確な作成、ユーザーインターフェースとの連携などが必要になります。Wallet.fromSeed("s...") や AMM/LPトークンの情報は、ご自身の環境に合わせて置き換えてください。TypeScriptを使用する場合は、AMMDepositAmount などの型を xrpl ライブラリからインポートして使用してください。

エラーケース

AMMDeposit トランザクションが失敗する可能性のある一般的なエラーコード (tec) とその原因、そして最終的なトランザクション結果 (ter) について説明します。

(エラーケースの説明は変更ありません)

一般的なエラー (tec): トランザクションは手数料を消費しますが、Ledgerには記録されません。

  • tecAMM_EMPTY: プール内の片方または両方の資産残高がゼロ、またはそれに近い状態のため、有効な交換レートを計算できず、入金処理を実行できません。特にシングルアセット入金時に発生しやすいです。
  • tecAMM_INVALID_TOKENS: トランザクションで指定された Asset, Asset2, または LPTokenOut の通貨コードや発行者が、AMMインスタンスの情報と一致しません。
  • tecAMM_BALANCE: 流動性を提供しようとしているアカウントに、指定された Amount または Amount2 の資産が不足しています。
  • tecAMM_FAILED: tfLimitLPToken フラグを使用し、LPTokenOut で最小受取量を指定した場合に、計算された実際の受取LPトークン量が指定した最小量を下回った場合に発生します。これは、トランザクション実行時のスリッページ(価格変動)が許容範囲を超えたことを意味します。
  • tecINSUFFICIENT_FUNDS: アカウントのXRP残高が、トランザクション手数料を支払うのに不足しています。
  • tecINTERNAL: 不明な内部エラー。リトライするか、詳細を調査する必要があります。

最終エラー (tem, ter): トランザクションはLedgerに記録されず、手数料も消費されません。アカウントのシーケンス番号も増加しません。

  • temBAD_AMOUNT: Amount, Amount2, LPTokenOut の値が不正です(例: 負の値、大きすぎる値、数値でないなど)。AmountObjectまたはstring(Drops)の形式が間違っている場合も含みます。
  • temBAD_ASSET: Asset または Asset2 の指定(通貨コードや発行者)が不正です。IOUなのにissuerがない、XRPなのにissuerがあるなども含みます。
  • temBAD_FEE: 手数料 (Fee) の値が不正です。
  • temBAD_FLAGS: 指定された Flags の値が無効、または矛盾するフラグが指定されています(例: tfTwoAssettfSingleAsset の同時指定)。
  • temBAD_ISSUER: Asset, Asset2, または LPTokenOut で指定された発行者アドレスが無効です。
  • temBAD_LIMIT: tfLimitLPToken を使用する際に、LPTokenOut が指定されていないか、値が不正です。
  • temBAD_SRC_ACCOUNT: Account フィールドで指定されたアカウントアドレスが無効です。
  • temDISABLED: AMM機能が現在ネットワークで有効化されていません(Amendmentが通っていない、または無効化された場合)。
  • terNO_ACCOUNT: Account フィールドで指定されたアカウントがLedger上に存在しません。
  • terNO_AMM: 指定された AssetAsset2 のペアに対応するAMMインスタンスが存在しません。
  • terNO_AUTH: IOUトークンを入金しようとしているが、必要なトラストラインが設定されていない、または発行者によってアカウントが承認されていません(lsfRequireAuth フラグが発行者アカウントで有効な場合)。
  • terNO_LINE: IOUトークンを入金しようとしているが、必要なトラストラインが存在しません。

これらのエラーが発生した場合、トランザクションの内容(指定した値、フラグ、アカウントの状態、AMMの状態など)を確認し、原因を特定して修正する必要があります。XRPLのエクスプローラーなどでトランザクションの詳細やアカウント情報、AMMの状態を確認することも有効です。

まとめ

今回は、XRPLのAMMに流動性を追加するための AMMDeposit トランザクションについて解説しました。

  • AMMDeposit は既存のAMMプールに資産を追加するトランザクションです。
  • ダブルアセット入金シングルアセット入金の2種類があり、それぞれメリット・デメリット、ユースケースが異なります。
  • IOUトークンcurrencyissuer を持つオブジェクトで、XRPcurrency: "XRP" のみで指定します。
  • IOUトークンの量AmountObject で、XRPの量は Drops単位の文字列で指定します。
  • Flags フィールドは、ビット演算のORで組み合わせ可能ですが、tfTwoAssettfSingleAsset/tfOneAssetLPToken のような矛盾する組み合わせはできません。
  • スリッページ制御には tfLimitLPToken フラグと LPTokenOut フィールドの組み合わせが推奨されます。
  • エラーケースを理解し、適切なエラーハンドリングを行うことが重要です。

流動性の提供はDeFiエコシステムの基盤であり、AMMDeposit はその中心的な操作の一つです。この記事が、XRPLでのAMM開発や利用の一助となれば幸いです。

次回は、AMMから流動性を引き出す AMMWithdraw トランザクションについて解説します。

関連情報

イベント告知

https://x.com/XRPLJapan/status/1918506329607389464

Discussion