XRP Ledger(XRPL)のオンチェーン流動性・活用状況・台帳統計(Tx数/手数料/混雑)・クジラ移動を、Windows上のLM StudioからMCPツールとして呼び出します。
本記事では、Node/TypeScript製のXRPL解析MCPを
Dockerイメージ化 → LM Studioで利用するまでを、
ステップバイステップで解説します。
作業の前提
- Node.js がインストールされている。
- 推奨バージョン
Node.js v18以上
推奨:v20 or v22(LTS)
🔹未インストールなら
https://nodejs.org から LTS版をインストールする。
- 推奨バージョン
- Windowsに Docker Desktop が入っていて
dockerコマンドが使える。
(参考:Dockerのインストール) - LM Studioをインストール済み(MCP設定できる)。
(参考:WindowsでLM Studioを使ってローカルでgpt-oss-20bを動かす)
動作イメージ
- LM Studio(DockerでMCP起動)
- XRPL解析MCP(WebSocket接続)
- XRPLノード
- 解析結果をLM Studioに返す
最終ディレクトリ構成(完成形)
Step 1:作業ディレクトリxrpl-analytics-mcp/の作成
mkdir xrpl-analytics-mcp
最終的な作業ディレクトリのディレクトリ構成は、下記の通りになります。
xrpl-analytics-mcp/
│
├─ package.json
├─ package-lock.json
├─ tsconfig.json
├─ Dockerfile
├─ src/
│ └─ index.ts
├─ dist/ ← TypeScriptビルド後に生成
│ └─ index.js
└─ node_modules/ ← npm installで生成
Step 2:XRPL解析MCPを作成
2-1 依存関係インストール
npm init -y
npm i @modelcontextprotocol/sdk zod xrpl
npm i -D typescript @types/node
2-2 tsconfig.json 作成
作業ディレクトリに、tsconfig.jsonを作成する。
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
"rootDir": "src",
"outDir": "dist",
"strict": true
},
"include": ["src"]
}
2-3 package.json にscripts 追加
{
“name”: “xrpl_mcp”,
“version”: “1.0.0”,
“description”: “XRPL Analytics MCP Server”,
“main”: “dist/index.js”,
“scripts”: {
“build”: “tsc -p tsconfig.json”,
“start”: “node dist/index.js”
},
“keywords”: [],
“author”: “”,
“license”: “ISC”,
“type”: “module”,
“dependencies”: {
“@modelcontextprotocol/sdk”: “^1.27.1”,
“xrpl”: “^4.6.0”,
“zod”: “^4.3.6”
},
“devDependencies”: {
“@types/node”: “^25.3.2”,
“typescript”: “^5.9.3”
}
}
2-4 MCP本体(src/index.ts)を作成する
以下の3ツールを実装する。
- server_info(混雑確認)
- fee(手数料確認)
- live TPS(リアルタイム負荷推定)
src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Client as XrplClient } from "xrpl";
const XRPL_WS = process.env.XRPL_WS ?? "wss://s1.ripple.com/";
/**
* XRPLクライアントを安全に開閉する共通ラッパー
*/
async function withClient<T>(fn: (c: XrplClient) => Promise<T>): Promise<T> {
const c = new XrplClient(XRPL_WS);
await c.connect();
try {
return await fn(c);
} finally {
await c.disconnect();
}
}
const server = new McpServer({
name: "xrpl-analytics",
version: "0.3.0",
});
/**
* ① サーバー情報取得(混雑状況など)
*/
server.tool(
"xrpl_get_server_info",
"XRPLノードのserver_infoを取得(混雑・validated ledgerなど)",
{},
async () => {
const data = await withClient((c) =>
c.request({ command: "server_info" as any })
);
return {
content: [{ type: "text", text: JSON.stringify(data.result, null, 2) }],
};
}
);
/**
* ② 手数料情報取得
*/
server.tool(
"xrpl_get_fee_stats",
"現在の手数料状況を取得(fee)",
{},
async () => {
const data = await withClient((c) =>
c.request({ command: "fee" as any })
);
return {
content: [{ type: "text", text: JSON.stringify(data.result, null, 2) }],
};
}
);
/**
* ③ 簡易TPS推定(transactions購読)
*/
server.tool(
"xrpl_live_tps",
"transactionsストリーム購読で直近windowSec秒のTx数(TPS推定)を返す",
{
windowSec: z.number().int().min(10).max(600).default(60),
sampleSec: z.number().int().min(5).max(60).default(10),
},
async ({ windowSec, sampleSec }) => {
const events: number[] = [];
const c = new XrplClient(XRPL_WS);
await c.connect();
await c.request({
command: "subscribe" as any,
streams: ["transactions"],
});
const handler = () => {
const t = Date.now();
events.push(t);
const cutoff = t - windowSec * 1000;
while (events.length && events[0] < cutoff) events.shift();
};
// xrplの型定義が厳しいため any で回避
(c as any).on("message", handler);
await new Promise((r) => setTimeout(r, sampleSec * 1000));
(c as any).off("message", handler);
await c.disconnect();
const now = Date.now();
const cutoff = now - windowSec * 1000;
const count = events.filter((t) => t >= cutoff).length;
const tps = count / windowSec;
return {
content: [
{
type: "text",
text: JSON.stringify(
{
ws: XRPL_WS,
windowSec,
sampleSec,
txCountInWindow: count,
tpsEstimated: Number(tps.toFixed(4)),
},
null,
2
),
},
],
};
}
);
server.tool(
"xrpl_live_tps_advanced",
"高精度TPS分析(瞬間TPS・移動平均・ピーク・異常検知)",
{
sampleSec: z.number().int().min(10).max(600).default(30),
},
async ({ sampleSec }) => {
const events: number[] = [];
const buckets: Record<number, number> = {};
const c = new XrplClient(XRPL_WS);
await c.connect();
await c.request({
command: "subscribe" as any,
streams: ["transactions"],
});
const start = Date.now();
const handler = () => {
const t = Date.now();
events.push(t);
const sec = Math.floor(t / 1000);
buckets[sec] = (buckets[sec] ?? 0) + 1;
};
(c as any).on("message", handler);
await new Promise((r) => setTimeout(r, sampleSec * 1000));
(c as any).off("message", handler);
await c.disconnect();
const end = Date.now();
const durationSec = (end - start) / 1000;
const totalTx = events.length;
const exactTps = totalTx / durationSec;
const perSecond = Object.values(buckets);
const peakTps = perSecond.length > 0 ? Math.max(...perSecond) : 0;
const avgPerSecond =
perSecond.length > 0
? perSecond.reduce((a, b) => a + b, 0) / perSecond.length
: 0;
const variance =
perSecond.length > 0
? perSecond.reduce((a, b) => a + Math.pow(b - avgPerSecond, 2), 0) /
perSecond.length
: 0;
const stdDev = Math.sqrt(variance);
const anomaly =
peakTps > avgPerSecond * 2 && peakTps > 10 ? true : false;
return {
content: [
{
type: "text",
text: JSON.stringify(
{
ws: XRPL_WS,
sampleSec,
durationSec,
totalTx,
exactTps: Number(exactTps.toFixed(4)),
peakTps,
avgPerSecond: Number(avgPerSecond.toFixed(4)),
stdDev: Number(stdDev.toFixed(4)),
anomalyDetected: anomaly,
interpretation:
anomaly
? "Unusual spike detected"
: "Normal activity range",
},
null,
2
),
},
],
};
}
);
// --- AMM Liquidity Monitor (amm_info) ---
const XRPL_ADDR = /^r[1-9A-HJ-NP-Za-km-z]{25,34}$/;
function toAmountSpec(currency: string, issuer?: string) {
const c = currency.toUpperCase();
const iss = typeof issuer === "string" ? issuer.trim() : undefined;
if (c === "XRP") return { currency: "XRP" as const };
if (!iss || !XRPL_ADDR.test(iss)) {
throw new Error(
`issuer is required and must be a valid XRPL address when currency is not XRP (currency=${currency})`
);
}
return { currency, issuer: iss };
}
function dropsToXrp(drops: string | number): number {
const n = typeof drops === "number" ? drops : Number(drops);
return n / 1_000_000;
}
function parseAmountToNumber(
a: any
): { value: number; currency: string; isXrp: boolean } {
if (typeof a === "string" || typeof a === "number") {
return { value: dropsToXrp(a), currency: "XRP", isXrp: true };
}
const v = Number(a?.value ?? "0");
const cur = String(a?.currency ?? "UNKNOWN");
return { value: v, currency: cur, isXrp: false };
}
function buildAmmSnapshot(amm: any, meta: any) {
const a1 = parseAmountToNumber(amm.amount);
const a2 = parseAmountToNumber(amm.amount2);
const price_a2_per_a1 = a1.value > 0 ? a2.value / a1.value : null;
const price_a1_per_a2 = a2.value > 0 ? a1.value / a2.value : null;
return {
ws: XRPL_WS,
pair: meta.pair,
liquidity: {
asset1: { amount: a1.value, unit: a1.isXrp ? "XRP" : a1.currency },
asset2: { amount: a2.value, unit: a2.isXrp ? "XRP" : a2.currency },
},
implied_price_by_reserve_ratio: {
asset2_per_asset1: price_a2_per_a1,
asset1_per_asset2: price_a1_per_a2,
note: "Rough estimate based on reserve ratio (not a swap quote).",
},
amm_meta: {
account: amm.account ?? null,
trading_fee: amm.trading_fee ?? null,
lp_token: amm.lp_token ?? null,
},
};
}
/**
* ツール④:XRP / IOU ペアでAMMを取得(amm_account 引数は “存在しない”)
*/
server.tool(
"xrpl_amm_info_xrp_iou",
"AMMプール情報(amm_info)XRP/IOU専用。asset1はXRP固定。asset2(IOU)はissuer必須。amm_accountは指定不可。",
{
asset2_currency: z.string().min(1),
asset2_issuer: z.preprocess(
(v) => (typeof v === "string" && v.trim() === "" ? undefined : v),
z.string().optional()
),
},
async ({ asset2_currency, asset2_issuer }) => {
const asset = { currency: "XRP" as const };
const asset2 = toAmountSpec(asset2_currency, asset2_issuer);
const data = await withClient((c) =>
c.request({
command: "amm_info" as any,
asset,
asset2,
})
);
const amm = (data as any).result?.amm;
if (!amm) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
error:
"amm_info returned no amm object. The pair may not have an AMM pool, or the node may not support AMM.",
pair: { asset, asset2 },
raw: (data as any).result ?? data,
},
null,
2
),
},
],
};
}
const snapshot = buildAmmSnapshot(amm, {
pair: {
asset1: { currency: "XRP", issuer: null },
asset2: {
currency: asset2_currency.toUpperCase(),
issuer: asset2_issuer ?? null,
},
},
});
return {
content: [{ type: "text", text: JSON.stringify(snapshot, null, 2) }],
};
}
);
/**
* ツール⑤:amm_account でAMMを取得(必要ならこちらを使う)
*/
server.tool(
"xrpl_amm_info_by_account",
"AMMプール情報(amm_info)をamm_account指定で取得。asset/asset2は不要。",
{
amm_account: z
.string()
.min(1)
.refine(
(v) => XRPL_ADDR.test(v.trim()),
"amm_account must be a valid XRPL address"
),
},
async ({ amm_account }) => {
const data = await withClient((c) =>
c.request({
command: "amm_info" as any,
amm_account: amm_account.trim(),
})
);
const amm = (data as any).result?.amm;
if (!amm) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
error: "amm_info returned no amm object.",
amm_account,
raw: (data as any).result ?? data,
},
null,
2
),
},
],
};
}
const snapshot = buildAmmSnapshot(amm, {
pair: { note: "pair fields may be omitted when fetched by amm_account" },
});
return {
content: [{ type: "text", text: JSON.stringify(snapshot, null, 2) }],
};
}
);
/**
* 主要IOU issuer候補(必要に応じて追加可能)
*/
const COMMON_ISSUERS: Record<string, string[]> = {
USDC: [
"rGm7WCVp9gb4jZHWTEtGUr4dd74z2XuWhE", // Circle
"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", // GateHub USD系
],
USD: [
"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq",
],
};
/**
* 自動AMM検索ツール
*/
server.tool(
"xrpl_amm_auto_search",
"XRP/IOU AMMをissuer自動探索。成功/失敗理由も含めて返す(デバッグ用)",
{
asset2_currency: z.string().min(1),
issuers: z.array(z.string()).optional(), // 任意:候補を追加投入できる
},
async ({ asset2_currency, issuers }) => {
const currency = asset2_currency.toUpperCase();
const defaultIssuers = COMMON_ISSUERS[currency] ?? [];
const list = [...new Set([...(issuers ?? []), ...defaultIssuers])];
if (list.length === 0) {
return {
content: [{ type: "text", text: JSON.stringify({ error: "No issuer candidates provided." }, null, 2) }],
};
}
const ok: any[] = [];
const ng: any[] = [];
for (const issuerRaw of list) {
const issuer = issuerRaw.trim();
if (!XRPL_ADDR.test(issuer)) {
ng.push({ issuer, status: "skipped", reason: "invalid issuer format" });
continue;
}
try {
const asset = { currency: "XRP" as const };
const asset2 = { currency, issuer };
const data = await withClient((c) =>
c.request({ command: "amm_info" as any, asset, asset2 })
);
const amm = (data as any).result?.amm;
if (!amm) {
ng.push({ issuer, status: "no_amm", reason: "result.amm is missing", raw: (data as any).result ?? null });
continue;
}
const a1 = parseAmountToNumber(amm.amount);
const a2 = parseAmountToNumber(amm.amount2);
ok.push({
issuer,
xrp_liquidity: a1.value,
token_liquidity: a2.value,
trading_fee: amm.trading_fee ?? null,
account: amm.account ?? null,
});
} catch (e: any) {
ng.push({
issuer,
status: "error",
reason: e?.data?.error_message ?? e?.message ?? String(e),
error: e?.data?.error ?? null,
});
}
}
ok.sort((a, b) => b.xrp_liquidity - a.xrp_liquidity);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
ws: XRPL_WS,
currency,
tried: list.length,
found: ok.length,
best_pool: ok[0] ?? null,
pools: ok,
failures: ng,
note: "If all failures are 'unknownCmd' or similar, the node likely doesn't support amm_info.",
},
null,
2
),
},
],
};
}
);
/**
* Order Book(book_offers)版 流動性モニター
* base/quote の最良気配と板厚(上位N件)を返す
*
* 例:XRP/USD(GateHub USD)
* base = XRP
* quote = USD (issuer必須)
*/
server.tool(
"xrpl_orderbook_liquidity",
"Order Book(book_offers)から流動性を推定。最良BID/ASK、スプレッド、上位N件の板厚を返す。XRPはissuer不要、IOUはissuer必須。",
{
base_currency: z.string().min(1).default("XRP"),
base_issuer: z.string().optional(), // baseがIOUのときのみ必要
quote_currency: z.string().min(1),
quote_issuer: z.string().optional(), // quoteがIOUのとき必要
limit: z.number().int().min(5).max(200).default(50), // book_offers取得件数
depth: z.number().int().min(1).max(50).default(20), // 集計に使う上位件数
},
async ({ base_currency, base_issuer, quote_currency, quote_issuer, limit, depth }) => {
const base = toAmountSpec(base_currency, base_issuer);
const quote = toAmountSpec(quote_currency, quote_issuer);
// ASK側(売り板):takerが base を受け取り、quote を支払う
// => price(quote/base) = taker_pays / taker_gets
const asksResp = await withClient((c) =>
c.request({
command: "book_offers" as any,
taker_gets: base,
taker_pays: quote,
limit,
})
);
// BID側(買い板):takerが quote を受け取り、base を支払う
// => price(quote/base) = taker_gets(quote) / taker_pays(base)
const bidsResp = await withClient((c) =>
c.request({
command: "book_offers" as any,
taker_gets: quote,
taker_pays: base,
limit,
})
);
const asks = ((asksResp as any).result?.offers ?? []) as any[];
const bids = ((bidsResp as any).result?.offers ?? []) as any[];
function normalizeAsk(o: any) {
const getsBase = parseAmountToNumber(o.TakerGets);
const paysQuote = parseAmountToNumber(o.TakerPays);
const baseAmt = getsBase.value; // base量(XRPならXRP)
const quoteAmt = paysQuote.value; // quote量
const price = baseAmt > 0 ? quoteAmt / baseAmt : null; // quote/base
return { price, baseAmt, quoteAmt };
}
function normalizeBid(o: any) {
const getsQuote = parseAmountToNumber(o.TakerGets);
const paysBase = parseAmountToNumber(o.TakerPays);
const baseAmt = paysBase.value; // base量(takerが支払うbase)
const quoteAmt = getsQuote.value; // quote量(takerが受け取るquote)
const price = baseAmt > 0 ? quoteAmt / baseAmt : null; // quote/base
return { price, baseAmt, quoteAmt };
}
const askLevels = asks.map(normalizeAsk).filter((x) => typeof x.price === "number");
const bidLevels = bids.map(normalizeBid).filter((x) => typeof x.price === "number");
// XRPLのbook_offersは通常、良い順(asksは安い順、bidsは高い順)で返る想定だが、安全にソート
askLevels.sort((a, b) => (a.price! - b.price!));
bidLevels.sort((a, b) => (b.price! - a.price!));
const bestAsk = askLevels[0]?.price ?? null;
const bestBid = bidLevels[0]?.price ?? null;
const mid = (bestAsk !== null && bestBid !== null) ? (bestAsk + bestBid) / 2 : null;
const spreadAbs = (bestAsk !== null && bestBid !== null) ? (bestAsk - bestBid) : null;
const spreadPct = (spreadAbs !== null && mid !== null && mid > 0) ? (spreadAbs / mid) * 100 : null;
function sumDepth(levels: { price: number | null; baseAmt: number; quoteAmt: number }[]) {
const top = levels.slice(0, depth);
const baseTotal = top.reduce((s, x) => s + (x.baseAmt || 0), 0);
const quoteTotal = top.reduce((s, x) => s + (x.quoteAmt || 0), 0);
const vwap = (() => {
// quote/base の出来高加重平均(base加重)
const denom = top.reduce((s, x) => s + (x.baseAmt || 0), 0);
if (denom <= 0) return null;
const num = top.reduce((s, x) => s + (x.baseAmt || 0) * (x.price || 0), 0);
return num / denom;
})();
return { baseTotal, quoteTotal, vwapQuotePerBase: vwap, levels: top };
}
const askDepth = sumDepth(askLevels as any);
const bidDepth = sumDepth(bidLevels as any);
const out = {
ws: XRPL_WS,
pair: {
base: { currency: base_currency.toUpperCase(), issuer: base_currency.toUpperCase() === "XRP" ? null : (base_issuer ?? null) },
quote: { currency: quote_currency.toUpperCase(), issuer: quote_currency.toUpperCase() === "XRP" ? null : (quote_issuer ?? null) },
},
best: {
bid_quote_per_base: bestBid,
ask_quote_per_base: bestAsk,
mid_quote_per_base: mid,
spread_abs: spreadAbs,
spread_pct: spreadPct,
},
depth_summary: {
depth_levels_used: depth,
asks: {
base_total: askDepth.baseTotal,
quote_total: askDepth.quoteTotal,
vwap_quote_per_base: askDepth.vwapQuotePerBase,
},
bids: {
base_total: bidDepth.baseTotal,
quote_total: bidDepth.quoteTotal,
vwap_quote_per_base: bidDepth.vwapQuotePerBase,
},
},
note: "If AMM is unavailable, order book depth is a good proxy for on-chain liquidity.",
};
return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
}
);
/**
* MCP stdio 起動
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
Step 3:ローカルビルドの実行
npm run build
package.jsonの”build”: “tsc -p tsconfig.json”が実行され、dist/index.jsが生成される。
Step 4:Docker化
Dockerfileの作成
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
LABEL org.opencontainers.image.source="https://github.com/<OWNER>/xrpl-analytics-mcp"
ENTRYPOINT ["node","dist/index.js"]
※ <OWNER> はGitHubユーザー名に後で置換します。
ローカル動作の確認
- Dockerイメージを作成(build)する。
(イメージ名を:xrpl-analytics-mcp、タグを:dev) - 作成したDockerイメージからコンテナを起動(run)する。
docker build -t xrpl-analytics-mcp:dev .
docker run --rm -i -e XRPL_WS=wss://s1.ripple.com/ xrpl-analytics-mcp:dev
エラーが表示されなければ、コンテナは終了しているのでCtrl + Cで終了する。
以下のコマンドを実行して、0が返却 なら正常終了している。
echo $LASTEXITCODE
作成さしたDockerイメージを、下記で確認する。
docker images | findstr xrpl-analytics-mcp
Dockerイメージ出力例:
xrpl-analytics-mcp:dev
31cad18897e3 316MB 68.1MB
Step 5:LM StudioでMCP登録
LM Studioのmcp.jsonに以下を追加する。
{
"mcpServers": {
"xrpl-analytics": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e", "XRPL_WS=wss://s1.ripple.com/",
"xrpl-analytics-mcp:dev"
]
}
}
}
参考:WindowsでLM Studioを使ってローカルでgpt-oss-20bを動かす
Step 6:動作確認
台帳全体の状況(混雑・手数料・健全性)確認プロンプト例
基本確認プロンプト
現在のXRPLネットワークの混雑状況と手数料レベルを教えて。
server_infoとfee情報を取得して、以下をまとめてほしい:
・現在のledger index
・load_factor
・base fee
・validated ledgerの状態
・ネットワーク混雑度の評価(低・中・高)
TPS(トランザクション活性度)
直近60秒のTPSを推定して。
xrpl_live_tpsを使って、現在のネットワーク利用状況を評価してほしい。
・推定TPS
・直近の増減傾向
・通常時との比較コメント
オンチェーン流動性(AMM・DEX活用)確認プロンプト例
流動性状況分析プロンプト
現在のXRPLの流動性状況を分析して。
・TPS
・手数料水準
・最近のトランザクション増加傾向
から、DEX/AMM利用の活発度を推定してほしい。
DEX利用活性チェック
最近ネットワークが活発かどうか評価して。
TPSとfeeの情報から、投機的取引が増えているかどうか推測してほしい。
クジラ(大口資金移動)分析プロンプト
簡易クジラ分析
最近のネットワーク混雑と手数料変化から、
大口投資家による資金移動が起きている可能性を分析して。
・TPS急増の有無
・feeスパイクの有無
・短期的な異常パターン
明確な閾値指定型
直近10分間のTPSと手数料の変動を分析して。
通常レンジを超えている場合は、
大口資金移動(クジラ)の可能性を指摘してほしい。
総合ダッシュボード型プロンプト
XRPLの現在のネットワーク状態を総合評価して。
以下を取得・分析してまとめてほしい:
1. server_info
2. fee
3. 直近TPS(60秒)
そのうえで、
・混雑度
・流動性活性度
・短期的投機活動の可能性
・クジラ移動の兆候
を総合評価(低・中・高)で出して。
実運用向けテンプレ
XRPLの現在のオンチェーン活動レベルを分析して。
混雑・手数料・TPSから、
市場活発度と大口資金移動の兆候を評価して。
XRPLネットワークの現在の健康状態を診断して。
混雑・手数料・TPSから、
市場の活性度と大口資金移動の兆候を総合評価して。
あなたはXRPLオンチェーンアナリストです。
以下の「動作確認済みツールのみ」を使って、現在のXRPL市場レポートを作成してください。
使用可能ツール:
1. xrpl_get_server_info
2. xrpl_get_fee_stats
3. xrpl_live_tps_advanced(sampleSec=120)
4. xrpl_amm_auto_search(asset2_currency="USD")
5. xrpl_orderbook_liquidity
base_currency="XRP"
quote_currency="USD"
quote_issuer="rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"
出力形式:
# XRPL On-Chain Market Report
## 1. Network Status
- Ledger進行状況
- 混雑度
- ノード安定性
## 2. Transaction Activity
- 推定TPS
- 取引活発度評価
## 3. Fee Environment
- 現在の手数料水準
- 混雑との関係
## 4. AMM Liquidity(XRP/USD)
- プール残高
- trading_fee評価
- 流動性の厚み評価(薄い/中程度/厚い)
## 5. Order Book Liquidity
- 最良BID/ASK
- スプレッド%
- 板厚(上位20件)
- スリッページリスク評価
## 6. 総合マーケット評価
- 強気 / 中立 / 弱気
- リスク要因3つ
- 注目ポイント
JSONは出力せず、日本語で分析レポート形式でまとめてください。
