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トークンの量を最大化しやすいです。スリッページ(価格変動による損失)の影響を抑えやすい傾向があります。
-
指定フィールド:
Amount
とAmount2
の両方を指定します。 -
フラグ:
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
は不要です)。
-
IOUトークンを指定する場合、必ず
-
Amount / Amount2 フィールド:
-
IOUトークンの量を指定する場合、
currency
,issuer
,value
を含むオブジェクト(AmountObject
)で指定します。value
は文字列で量を表します。 -
XRPの量を指定する場合、Drops単位の量を文字列で指定します (例:
"1000000"
は 1 XRP)。
-
IOUトークンの量を指定する場合、
-
LPTokenOut フィールド: 受け取るLPトークン量を指定する場合、LPトークンの
currency
(160-bit HEX)、issuer
(AMMアカウントアドレス)、value
(受け取りたい量) を含むオブジェクトで指定します。
重要なフラグ解説
AMMDeposit
トランザクションでは、Flags
フィールドを使って入金の詳細な挙動を制御できます。フラグはビット値であり、複数のフラグを組み合わせることが可能です(ただし、矛盾する組み合わせはできません)。組み合わせる場合は、各フラグの値をビット単位でOR演算した結果(合計値)を Flags
フィールドに設定します。
フラグ名 | 値 (Decimal) | 値 (Hex) | 説明 |
---|---|---|---|
tfLPToken |
1048576 | 0x00010000 |
ダブルアセット入金 を行います。LPTokenOut のみで指定する必要があります。 |
tfTwoAsset |
1048576 | 0x00100000 |
ダブルアセット入金 を行います。Amount と Amount2 の両方を指定する必要があります。 |
tfSingleAsset |
131072 | 0x00080000 |
シングルアセット入金 を行います。どちらか一方を Amount で指定します。 |
tfOneAssetLPToken |
262144 | 0x00200000 |
シングルアセット入金 で、受け取る LPトークンの量を指定 します。LPTokenOut フィールドが必須です。 |
tfLimitLPToken |
524288 | 0x00400000 |
1つの資産につき指定された金額まで入金できる。LPトークンごとに指定された実効価格(手数料後)を超えて支払うことはできません。 |
注意点:
-
tfTwoAsset
とtfSingleAsset
は互いに排他的です。 -
tfTwoAsset
とtfOneAssetLPToken
は互いに排他的です。 -
tfSingleAsset
とtfOneAssetLPToken
は互いに排他的です。
コード例 (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を使用する場合は、AMMDeposit
や Amount
などの型を 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
の値が無効、または矛盾するフラグが指定されています(例:tfTwoAsset
とtfSingleAsset
の同時指定)。 -
temBAD_ISSUER
:Asset
,Asset2
, またはLPTokenOut
で指定された発行者アドレスが無効です。 -
temBAD_LIMIT
:tfLimitLPToken
を使用する際に、LPTokenOut
が指定されていないか、値が不正です。 -
temBAD_SRC_ACCOUNT
:Account
フィールドで指定されたアカウントアドレスが無効です。 -
temDISABLED
: AMM機能が現在ネットワークで有効化されていません(Amendmentが通っていない、または無効化された場合)。 -
terNO_ACCOUNT
:Account
フィールドで指定されたアカウントがLedger上に存在しません。 -
terNO_AMM
: 指定されたAsset
とAsset2
のペアに対応するAMMインスタンスが存在しません。 -
terNO_AUTH
: IOUトークンを入金しようとしているが、必要なトラストラインが設定されていない、または発行者によってアカウントが承認されていません(lsfRequireAuth
フラグが発行者アカウントで有効な場合)。 -
terNO_LINE
: IOUトークンを入金しようとしているが、必要なトラストラインが存在しません。
これらのエラーが発生した場合、トランザクションの内容(指定した値、フラグ、アカウントの状態、AMMの状態など)を確認し、原因を特定して修正する必要があります。XRPLのエクスプローラーなどでトランザクションの詳細やアカウント情報、AMMの状態を確認することも有効です。
まとめ
今回は、XRPLのAMMに流動性を追加するための AMMDeposit
トランザクションについて解説しました。
-
AMMDeposit
は既存のAMMプールに資産を追加するトランザクションです。 - ダブルアセット入金とシングルアセット入金の2種類があり、それぞれメリット・デメリット、ユースケースが異なります。
-
IOUトークンは
currency
とissuer
を持つオブジェクトで、XRPはcurrency: "XRP"
のみで指定します。 -
IOUトークンの量は
AmountObject
で、XRPの量は Drops単位の文字列で指定します。 -
Flags
フィールドは、ビット演算のORで組み合わせ可能ですが、tfTwoAsset
とtfSingleAsset
/tfOneAssetLPToken
のような矛盾する組み合わせはできません。 - スリッページ制御には
tfLimitLPToken
フラグとLPTokenOut
フィールドの組み合わせが推奨されます。 - エラーケースを理解し、適切なエラーハンドリングを行うことが重要です。
流動性の提供はDeFiエコシステムの基盤であり、AMMDeposit
はその中心的な操作の一つです。この記事が、XRPLでのAMM開発や利用の一助となれば幸いです。
次回は、AMMから流動性を引き出す AMMWithdraw
トランザクションについて解説します。
関連情報
- AMMDeposit Transaction (公式ドキュメント): https://xrpl.org/docs/references/protocol/transactions/types/ammdeposit/
- AMM オブジェクト (公式ドキュメント): https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/amm/ (AMMの状態を確認する際に参照)
- XRPL.org ドキュメント: https://xrpl.org/docs/
- Amount フォーマット (公式ドキュメント): https://xrpl.org/docs/concepts/tokens/currency-formats/
イベント告知
Discussion