About XRPL
home

AMM

μ£Όμš” λ‚΄μš©
DEX κ΅¬ν˜„ μ‹œ ν•„μš”ν•œ μ „λ°˜μ μΈ AMM κΈ°λŠ₯
폴더λͺ…
AMM

슀크립트 μ‹€ν–‰ λͺ…λ Ήμ–΄ (AMM)

$npx ts-node xrpl/AMM/AMMCreate.ts $npx ts-node xrpl/AMM/AMMDeposit.ts $npx ts-node xrpl/AMM/AMMWithdraw.ts $npx ts-node xrpl/AMM/AMMSwap.ts $npx ts-node xrpl/AMM/AMMVote.ts $npx ts-node xrpl/AMM/AMMBid.ts $npx ts-node xrpl/AMM/AMMDelete.ts $npx ts-node xrpl/AMM/AMMClawback.ts # (μ˜΅μ…˜) XLS-73 Clawback κΈ°λŠ₯ $npx ts-node xrpl/AMM/getAMMInfo.ts # (μ˜΅μ…˜) μƒμ„±λœ ν’€ 쑰회 κΈ°λŠ₯
Bash
볡사

1. AMM(Automated Market Maker)λž€?

AMM은 XRPL 원μž₯에 직접 ν†΅ν•©λœ μžλ™ν™”λœ μœ λ™μ„± 풀이닀.
XLS-30d Amendment둜 λ„μž…λ˜μ—ˆμœΌλ©°, κΈ°μ‘΄ DEX μ˜€λ”λΆκ³Ό λ³‘ν–‰λ˜μ–΄ 졜적의 가격 경둜λ₯Ό 찾을 수 μžˆλ‹€.
β€’
μ˜€λ”λΆ 기반 거래: κΈ°μ‘΄ XRPL의 DEX λ©”μ»€λ‹ˆμ¦˜, κ°œλ³„ 맀도/맀수 μ£Όλ¬Έ λ§€μΉ­
β€’
AMM 기반 거래: μžμ‚° μŒλ³„λ‘œ ν•˜λ‚˜μ˜ μœ λ™μ„± 풀을 운영, 곑선에 따라 μžλ™ 가격 κ²°μ •
AMM 풀은 νŠΉλ³„ 계정(Account)에 μ˜ν•΄ μžμ‚°μ„ λ³΄κ΄€ν•˜κ³ , μ˜ˆμΉ˜μžμ—κ²Œ LP 토큰을 λ°œν–‰ν•œλ‹€.
LP 토큰 λ³΄μœ μžλŠ” 지뢄에 따라 μˆ˜μ΅μ„ κ³΅μœ ν•˜λ©°, 수수료율 νˆ¬ν‘œκΆŒμ„ κ°–λŠ”λ‹€.

2. μ™œ ν•„μš”ν•œκ°€?

β€’
DEX ν™•μž₯μ„±
Order Bookλ§ŒμœΌλ‘œλŠ” 얇은 μ‹œμž₯μ—μ„œ μŠ¬λ¦¬ν”Όμ§€κ°€ 컀질 수 있음 β†’ AMM을 톡해 보완
β€’
μœ λ™μ„± 제곡자 μΈμ„Όν‹°λΈŒ
λ‹¨μˆœ 보유 λŒ€μ‹  풀에 μœ λ™μ„±μ„ 곡급해 수수료 수읡 νšλ“ κ°€λŠ₯
β€’
λ‹€μ–‘ν•œ μ°Έμ—¬ 방식
β—¦
νŠΈλ ˆμ΄λ”: μŠ€μ™‘(κ΅ν™˜)
β—¦
LP: μœ λ™μ„± μž…κΈˆ/좜금
β—¦
LP 토큰 보유자: 수수료율 νˆ¬ν‘œ, κ²½λ§€ μž…μ°° μ°Έμ—¬
β€’
κ±°λ²„λ„ŒμŠ€ λ‚΄μž₯
수수료율 νˆ¬ν‘œ, 슬둯 κ²½λ§€ λ“± ν’€ λ‚΄λΆ€ κ·œμΉ™μ΄ ν”„λ‘œν† μ½œ μ°¨μ›μ—μ„œ 지원됨

3. μ‹œλ‚˜λ¦¬μ˜€: AMMCreate β†’ AMMDeposit β†’ AMMWithdraw β†’ AMMSwap β†’ AMMVote β†’ AMMBid β†’ AMMDelete (+ μ˜΅μ…˜: AMMClawback)

μ•„λž˜ μ‹œλ‚˜λ¦¬μ˜€λŠ” AMM의 전체 라이프사이클을 닀룬닀.
ν’€ 생성뢀터 μœ λ™μ„± 곡급/제거, 거래, κ±°λ²„λ„ŒμŠ€, κ²½λ§€, μ‚­μ œκΉŒμ§€ 순차적으둜 μ§„ν–‰λœλ‹€.

Step 1. AMM ν’€ 생성 (AMMCreate.ts)

β€’
주체: 토큰 λ°œν–‰μž(ADMIN)
β€’
행동: AMMCreate νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: 두 μžμ‚°(XRP + IOU)으둜 풀을 μƒμ„±ν•˜κ³  초기 μœ λ™μ„±μ„ 곡급
β€’
κ²°κ³Ό: μƒˆλ‘œμš΄ AMM μΈμŠ€ν„΄μŠ€κ°€ 원μž₯에 λ“±λ‘λ˜κ³ , LP 토큰이 λ°œκΈ‰λ¨
const tx: Transaction= { TransactionType: "AccountSet", Account: admin.address, // AMM ν’€ μƒμ„±μž(Admin) SetFlag: 8 // asfDefaultRipple : Enable rippling on this account's trust lines by default. }
TypeScript
볡사
β€’
μ£Όμ˜μ‚¬ν•­ : 사전에 Admin κ³„μ •μ—μ„œ Ripping ν—ˆμš© ν”Œλž˜κ·Έ μ„ΈνŒ…ν•΄ μ€˜μ•Ό 함. (AccountSetμ—μ„œ ν”Œλž˜κ·Έ 8둜 μ„€μ •)
β€’
κΈˆμœ΅κΈ°κ΄€μ΄λ‚˜ λ§ˆμΌ“λ©”μ΄μ»€μ²˜λŸΌ IOUλ₯Ό 적극적으둜 μœ ν†΅μ‹œν‚€κ³  싢은 μ£Όμ²΄λŠ” 이 ν”Œλž˜κ·Έλ₯Ό μΌœλŠ” 게 μΌλ°˜μ μž„
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * AMMCreate νŠΈλžœμž­μ…˜: μƒˆλ‘œμš΄ AMM 풀을 생성 * - 두 μžμ‚°μ„ μ˜ˆμΉ˜ν•΄ 풀을 λ§Œλ“€κ³  초기 TradingFee μ„€μ • * - TradingFee λ²”μœ„: 0 ~ 1000 (0.001% ~ 1%) */ export async function AMMCreate() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const ADMIN_SEED = process.env.ADMIN_SEED if (!ADMIN_SEED) throw new Error("Missing env: ADMIN_SEED") const admin = Wallet.fromSeed(ADMIN_SEED.trim()) /** * ⚠️ μ˜ˆμ‹œ μžμ‚° μ„€μ • * - Asset: XRP * - Asset2: USD (ADMIN λ°œν–‰ IOU) * - TradingFee: 30 (0.3%) */ const tx: Transaction = { TransactionType: "AMMCreate", Account: admin.address, // ν’€ μƒμ„±μž Amount: "10000000", // 10 XRP (drops λ‹¨μœ„) Amount2: { currency: "ABC, issuer: admin.address, value: "100" }, TradingFee: 30 // 0.3% } try { const prepared = await client.autofill(tx) const signed = admin.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMCreate Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } // 직접 μ‹€ν–‰ if (require.main === module) { AMMCreate().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사

Step 2. μœ λ™μ„± μΆ”κ°€ (AMMDeposit.ts)

β€’
주체: 일반 μœ μ €(USER)
β€’
행동: AMMDeposit νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: 풀에 두 μžμ‚°μ„ μ˜ˆμΉ˜ν•˜κ³ , λΉ„μœ¨λ§ŒνΌ LP 토큰 수령
β€’
κ²°κ³Ό: μœ μ €κ°€ LP 토큰을 νšλ“, 지뢄에 따라 μˆ˜μ΅Β·νˆ¬ν‘œκΆŒ 보유
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * AMMDeposit νŠΈλžœμž­μ…˜: κΈ°μ‘΄ AMM 풀에 μœ λ™μ„± μΆ”κ°€ * - 두 μžμ‚° λΉ„μœ¨μ— 맞좰 μΆ”κ°€ν•˜κ±°λ‚˜, 단일 μžμ‚°λ§Œ μΆ”κ°€ν•  μˆ˜λ„ 있음 * - μ˜ˆμ‹œμ—μ„œλŠ” XRP + USD(IOU) λΉ„μœ¨λ‘œ 예치 */ export async function AMMDeposit() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const ADMIN_SEED = process.env.ADMIN_SEED const USER_SEED = process.env.USER_SEED if (!ADMIN_SEED || !USER_SEED) throw new Error("Missing env: ADMIN_SEED, USER_SEED") const admin = Wallet.fromSeed(ADMIN_SEED.trim()) const user = Wallet.fromSeed(USER_SEED.trim()) const tx: Transaction = { TransactionType: "AMMDeposit", Account: user.address, // μœ λ™μ„± μΆ”κ°€ 주체 (νŠΈλ ˆμ΄λ”) Asset: { currency: "XRP" }, // 첫 번째 ν’€ μžμ‚° Asset2: { currency: "USD", issuer: admin.address }, Amount: "5000000", // 5 XRP (drops λ‹¨μœ„) Amount2: { currency: "USD", issuer: admin.address, value: "50" }, Flags: 0x00100000 // tfTwoAsset: 두 μžμ‚° λΉ„μœ¨ 맞좰 예치 } try { const prepared = await client.autofill(tx) const signed = user.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMDeposit Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } // 직접 μ‹€ν–‰ if (require.main === module) { AMMDeposit().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사

Step 3. μœ λ™μ„± 제거 (AMMWithdraw.ts)

β€’
주체: μœ λ™μ„± κ³΅κΈ‰μž(USER)
β€’
행동: AMMWithdraw νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: LP 토큰을 λ°˜ν™˜ν•΄ μ›λž˜ μžμ‚°μ„ 회수
β€’
κ²°κ³Ό: ν’€μ˜ μœ λ™μ„±μ΄ 쀄고, USER 지갑에 μžμ‚° λ°˜ν™˜
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * AMMWithdraw νŠΈλžœμž­μ…˜: κΈ°μ‘΄ AMM ν’€μ—μ„œ μœ λ™μ„± 제거 * - LPToken을 μ œμΆœν•˜λ©΄, κ·Έ λΉ„μœ¨λ§ŒνΌ μžμ‚°(A/B)을 λ°˜ν™˜λ°›μŒ * - 전체 인좜(tfWithdrawAll) λ˜λŠ” λΆ€λΆ„ 인좜 κ°€λŠ₯ */ export async function AMMWithdraw() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const USER_SEED = process.env.USER_SEED const ADMIN_SEED = process.env.ADMIN_SEED if (!USER_SEED || !ADMIN_SEED) throw new Error("Missing env: USER_SEED, ADMIN_SEED") const user = Wallet.fromSeed(USER_SEED.trim()) const admin = Wallet.fromSeed(ADMIN_SEED.trim()) const tx: Transaction = { TransactionType: "AMMWithdraw", Account: user.address, // μœ λ™μ„± 제거 주체 Asset: { currency: "XRP" }, Asset2: { currency: "USD", issuer: admin.address }, LPTokenIn: { currency: "LP", // getAMMInfo κ²°κ³Όμ—μ„œ λ°˜ν™˜λœ lp_token.currency(16μ§„ λ¬Έμžμ—΄)κ°’ issuer: "rAMMInstance...", // AMM 계정 μ£Όμ†Œ (ν’€ 생성 μ‹œ μžλ™ 생성됨) value: "10" // μ œκ±°ν•  LPToken μˆ˜λŸ‰ }, Flags: 0x00020000 // tfWithdrawAll: λͺ¨λ“  μžμ‚° λΉ„μœ¨λ‘œ 좜금 } try { const prepared = await client.autofill(tx) const signed = user.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMWithdraw Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } // 직접 μ‹€ν–‰ if (require.main === module) { AMMWithdraw().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사

Step 4. μžμ‚° μŠ€μ™‘ (AMMSwap.ts)

β€’
주체: νŠΈλ ˆμ΄λ”(USER)
β€’
행동: Payment νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: SendMax, Amount ν•„λ“œλ₯Ό μ§€μ •ν•΄ κ΅ν™˜ 경둜 탐색 β†’ μ˜€λ”λΆ+AMM을 μ‘°ν•©ν•΄ 졜적 가격에 체결
β€’
κ²°κ³Ό: μœ μ € μžμ‚° κ΅ν™˜ μ™„λ£Œ, μˆ˜μˆ˜λ£ŒλŠ” 풀에 λΆ„λ°°
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * AMMSwap (μ‹€μ œλ‘œλŠ” Payment νŠΈλžœμž­μ…˜ ν™œμš©) * - AssetA β†’ AssetB κ΅ν™˜ * - AMM ν’€κ³Ό μ˜€λ”λΆμ„ μžλ™μœΌλ‘œ κ²½μœ ν•˜μ—¬ 졜적 가격 경둜λ₯Ό 선택 */ export async function AMMSwap() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const USER_SEED = process.env.USER_SEED const ADMIN_SEED = process.env.ADMIN_SEED if (!USER_SEED || !ADMIN_SEED) throw new Error("Missing env: USER_SEED, ADMIN_SEED") const user = Wallet.fromSeed(USER_SEED.trim()) const admin = Wallet.fromSeed(ADMIN_SEED.trim()) /** * ⚠️ μ˜ˆμ‹œ: USERκ°€ 5 XRPλ₯Ό λ‚΄κ³ , μ΅œμ†Œ 40 USD(IOU)λ₯Ό λ°›κ³  싢을 λ•Œ * - Amount: λ‚΄κ°€ λ°›κ³  싢은 μžμ‚° * - SendMax: λ‚΄κ°€ μ΅œλŒ€ νˆ¬μž…ν•  μžμ‚° */ const tx: Transaction = { TransactionType: "Payment", Account: user.address, Destination: user.address, // 자기 μžμ‹ μ„ λŒ€μƒμœΌλ‘œ μ„€μ • (μŠ€μ™‘ κ²°κ³Όλ₯Ό 본인 지갑에 λ°›μŒ) Amount: { currency: "USD", issuer: admin.address, value: "40" // λ°›κ³  싢은 USD μ΅œμ†Œ μˆ˜λŸ‰ }, SendMax: "5000000", // μ΅œλŒ€ 5 XRP μ§€λΆˆ (drops λ‹¨μœ„) Flags: 0x00020000 // tfPartialPayment (μΌλΆ€λ§Œ 좩쑱해도 μ‹€ν–‰ κ°€λŠ₯) } try { const prepared = await client.autofill(tx) const signed = user.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMSwap (Payment) Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } // 직접 μ‹€ν–‰ if (require.main === module) { AMMSwap().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사

Step 5. 수수료 νˆ¬ν‘œ (AMMVote.ts)

β€’
주체: LP 토큰 보유자(USER)
β€’
행동: AMMVote νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: ν’€μ˜ TradingFee(0~1000, 0~1%)에 νˆ¬ν‘œ β†’ κ°€μ€‘μΉ˜λŠ” 보유 LP 토큰 μˆ˜λŸ‰
β€’
κ²°κ³Ό: λͺ¨λ“  νˆ¬ν‘œμ˜ 가쀑 ν‰κ· μœΌλ‘œ ν’€ 수수료율 κ²°μ •
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * AMMVote νŠΈλžœμž­μ…˜ * - LPToken λ³΄μœ μžκ°€ AMM μΈμŠ€ν„΄μŠ€μ˜ TradingFee에 νˆ¬ν‘œ * - TradingFee λ²”μœ„: 0 ~ 1000 (0.001% ~ 1%) */ export async function AMMVote() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const USER_SEED = process.env.USER_SEED const ADMIN_SEED = process.env.ADMIN_SEED if (!USER_SEED || !ADMIN_SEED) throw new Error("Missing env: USER_SEED, ADMIN_SEED") const user = Wallet.fromSeed(USER_SEED.trim()) const admin = Wallet.fromSeed(ADMIN_SEED.trim()) /** * ⚠️ μ˜ˆμ‹œ: USERκ°€ XRP/USD AMM 풀에 λŒ€ν•΄ 0.25% (TradingFee = 25) 둜 νˆ¬ν‘œ */ const tx: Transaction = { TransactionType: "AMMVote", Account: user.address, Asset: { currency: "XRP" }, // ν’€μ˜ 첫 번째 μžμ‚° Asset2: { currency: "USD", issuer: admin.address }, // ν’€μ˜ 두 번째 μžμ‚° TradingFee: 25 // 0.25% } try { const prepared = await client.autofill(tx) const signed = user.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMVote Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } // 직접 μ‹€ν–‰ if (require.main === module) { AMMVote().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사

Step 6. κ²½λ§€ 슬둯 μž…μ°° (AMMBid.ts)

β€’
주체: Arbitrageur(USER)
β€’
행동: AMMBid νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: LP ν† ν°μœΌλ‘œ μž…μ°° β†’ 슬둯 νšλ“ μ‹œ 24μ‹œκ°„ λ™μ•ˆ 수수료 할인 ν˜œνƒ
β€’
κ²°κ³Ό: κΈ°μ‘΄ 슬둯 λ³΄μœ μžκ°€ μžˆλ‹€λ©΄ 일뢀 λ¦¬νŽ€λ“œ ν›„ μ†Œκ°, USERκ°€ μƒˆ 슬둯 보유자 됨
import { Client, Transaction, Wallet } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * AMMBid: AMM ν’€μ˜ κ²½λ§€ 슬둯 μž…μ°° * - μž…μ°°μžλŠ” LPToken λ‹¨μœ„λ‘œ BidMax/BidMin μ§€μ • κ°€λŠ₯ * - 승리 μ‹œ 24μ‹œκ°„ λ™μ•ˆ 수수료 할인 (TradingFee/10) */ export async function AMMBid() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const USER_SEED = process.env.USER_SEED const ADMIN_SEED = process.env.ADMIN_SEED if (!USER_SEED || !ADMIN_SEED) throw new Error("Missing env: USER_SEED, ADMIN_SEED") const user = Wallet.fromSeed(USER_SEED.trim()) const admin = Wallet.fromSeed(ADMIN_SEED.trim()) // currency에 λ“€μ–΄κ°€λŠ” "LP"λŠ” getAMMInfo κ²°κ³Όμ—μ„œ λ°˜ν™˜λœ lp_token.currency(16μ§„ λ¬Έμžμ—΄)값을 λ„£μ–΄μ•Ό 함 const tx: Transaction = { TransactionType: "AMMBid", Account: user.address, Asset: { currency: "XRP" }, Asset2: { currency: "USD", issuer: admin.address }, BidMax: { currency: "LP", // ν’€ 생성 μ‹œ λ°œκΈ‰λœ LP 토큰 μ½”λ“œ (16μ§„ λ¬Έμžμ—΄) issuer: "rAMMInstance...", // AMM 계정 μ£Όμ†Œ value: "100" // μž…μ°°ν•  LPToken μˆ˜λŸ‰ }, AuthAccounts: [ { AuthAccount: { Account: "rOtherAccount..." } } ] } try { const prepared = await client.autofill(tx) const signed = user.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMBid Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } if (require.main === module) { AMMBid().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사

Step 7. ν’€ μ‚­μ œ (AMMDelete.ts)

β€’
주체: ADMIN
β€’
행동: AMMDelete νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: ν’€ μœ λ™μ„±μ΄ λͺ¨λ‘ 제거된 μƒνƒœμ—μ„œ μΈμŠ€ν„΄μŠ€ μ‚­μ œ
β€’
κ²°κ³Ό: 원μž₯μ—μ„œ ν•΄λ‹Ή AMM μ—”νŠΈλ¦¬ 제거
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * AMMDelete νŠΈλžœμž­μ…˜ * - 풀에 남은 μœ λ™μ„±μ΄ μ „ν˜€ 없을 λ•Œλ§Œ μ‹€ν–‰ κ°€λŠ₯ * - AMM μΈμŠ€ν„΄μŠ€λ₯Ό 원μž₯μ—μ„œ 제거 */ export async function AMMDelete() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const ADMIN_SEED = process.env.ADMIN_SEED if (!ADMIN_SEED) throw new Error("Missing env: ADMIN_SEED") const admin = Wallet.fromSeed(ADMIN_SEED.trim()) /** * ⚠️ μ˜ˆμ‹œ: XRP / USD(IOU) ν’€ μ‚­μ œ * - Asset, Asset2: ν’€ 생성 μ‹œ μ •μ˜ν–ˆλ˜ 동일 μžμ‚°μŒ */ const tx: Transaction = { TransactionType: "AMMDelete", Account: admin.address, // ν’€ μƒμ„±μž(ν˜Ήμ€ 관리 계정) Asset: { currency: "XRP" }, Asset2: { currency: "USD", issuer: admin.address } } try { const prepared = await client.autofill(tx) const signed = admin.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMDelete Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } // 직접 μ‹€ν–‰ if (require.main === module) { AMMDelete().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사

μ‹œλ‚˜λ¦¬μ˜€ μ˜ˆμƒ κ²°κ³Ό

β€’
성곡 μ‹œ
β—¦
AMM 생성 β†’ μž…κΈˆ/좜금 β†’ μŠ€μ™‘ β†’ νˆ¬ν‘œ/κ²½λ§€ β†’ μ‚­μ œκΉŒμ§€ 정상 λ™μž‘
β—¦
Clawback 토큰은 λ°œν–‰μžκ°€ 회수 κ°€λŠ₯
β€’
μ‹€νŒ¨ μ‹œ
β—¦
μœ λ™μ„±μ΄ λ‚¨μ•„μžˆλŠ” μƒνƒœμ—μ„œ μ‚­μ œ μ‹œλ„ β†’ tecINCOMPLETE
β—¦
Clawback λΉ„ν™œμ„± 토큰 β†’ tecNO_PERMISSION
β—¦
잘λͺ»λœ μžμ‚° μ§€μ • β†’ temMALFORMED

(μ˜΅μ…˜ 1) Clawback (AMMClawback.ts)

β€’
주체: λ°œν–‰μž(ADMIN, Clawback κΆŒν•œ 보유)
β€’
행동: AMMClawback νŠΈλžœμž­μ…˜ 전솑
β€’
λ‚΄μš©: AMM에 예치된 Clawback ν™œμ„± 토큰을 νŠΉμ • Holder κ³„μ •μœΌλ‘œλΆ€ν„° 회수
β€’
κ²°κ³Ό: λ°œν–‰μžκ°€ μ§€μ •ν•œ μˆ˜λŸ‰λ§ŒνΌ 토큰을 μ§€κ°‘μœΌλ‘œ 되찾음
import { Client, Transaction, Wallet } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) export async function AMMClawback() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const ADMIN_SEED = process.env.ADMIN_SEED if (!ADMIN_SEED) throw new Error("Missing env: ADMIN_SEED") const admin = Wallet.fromSeed(ADMIN_SEED.trim()) const tx: Transaction = { TransactionType: "AMMClawback", Account: admin.address, Asset: { currency: "XRP", issuer: admin.address}, // XRPλŠ” issuer ν•„μš” μ—†μŒ (any둜 우회) Asset2: { currency: "USD", issuer: admin.address }, Amount: { currency: "USD", issuer: admin.address, value: "10" }, Holder: "rSomeLiquidityProvider..." // βœ… clawback λŒ€μƒ 계정 } try { const prepared = await client.autofill(tx) const signed = admin.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log("βœ… AMMClawback Result:") console.log(JSON.stringify(result, null, 2)) return result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } if (require.main === module) { AMMClawback().catch(e => { console.error(e); process.exit(1) }) }
TypeScript
볡사

(μ˜΅μ…˜ 2) getAMMInfo (getAMMInfo.ts)

β€’
주체: μ•„λ¬΄λ‚˜
β€’
행동: amm_info RPC ν˜ΈμΆœμ„ 톡해 νŠΉμ • AMM ν’€ μƒνƒœλ₯Ό 쑰회
β€’
λ‚΄μš©:
β—¦
μžμ‚°μŒ(Asset, Asset2)을 μ§€μ •ν•˜λ©΄ ν•΄λ‹Ή ν’€μ˜ μ„ΈλΆ€ 정보λ₯Ό λ°˜ν™˜
β—¦
ν’€ μž”μ•‘(amount, amount2), ν˜„μž¬ 수수료율(trading_fee), AMM 계정 μ£Όμ†Œ(account) 확인 κ°€λŠ₯
β—¦
특히 lp_token ν•„λ“œμ—μ„œ LP Token의 currency(16λ°”μ΄νŠΈ hex μ½”λ“œ)와 issuer(ν’€ μ „μš© 계정 μ£Όμ†Œ)λ₯Ό 쑰회 κ°€λŠ₯ β†’ Withdraw/Clawback μ‹œ λ°˜λ“œμ‹œ ν•„μš”
import { Client, Wallet } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) /** * νŠΉμ • AMM ν’€μ˜ 정보 쑰회 * - ν’€ μƒνƒœ(μž”μ•‘, TradingFee, LP Token λ“±) 확인 κ°€λŠ₯ * - LP Token currency/issuer 확인 ν›„ Withdraw 등에 ν™œμš© */ export async function getAMMInfo() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() const ADMIN_SEED = process.env.ADMIN_SEED if (!ADMIN_SEED) throw new Error("Missing env: ADMIN_SEED") const admin = Wallet.fromSeed(ADMIN_SEED.trim()) // ⚠️ μ‘°νšŒν•  ν’€ μžμ‚°μŒ (예: XRP + USD) const req: any = { command: "amm_info", asset: { currency: "XRP" }, asset2: { currency: "USD", issuer: admin.address } } try { const result: any = await client.request(req as any) console.log("βœ… AMM Info Result:") console.log(JSON.stringify(result.result, null, 2)) if (result.result?.amm?.lp_token) { console.log("πŸ“Œ LPToken Info:") console.log(result.result.amm.lp_token) } return result.result } finally { await client.disconnect() console.log("πŸ”„ μ—°κ²° μ’…λ£Œ") } } // 직접 μ‹€ν–‰ if (require.main === module) { getAMMInfo().catch(e => { console.error(e) process.exit(1) }) }
TypeScript
볡사
β€’
κ²°κ³Ό:
β—¦
성곡 μ‹œ β†’ ν’€μ˜ 전체 μƒνƒœ 및 LP Token 정보 λ°˜ν™˜
β—¦
"account": "rUd5wEYNLtC4NRoMEXDAmd8L9ASof8Hn18" β†’ XRPL 원μž₯이 풀을 λ§Œλ“€λ©΄μ„œ μžλ™ μƒμ„±ν•œ μ „μš© AMM 계정
β—¦
μ‹€νŒ¨ μ‹œ β†’ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μžμ‚°μŒμ΄λ©΄ temBAD_AMM_ASSET 였λ₯˜ λ°˜ν™˜, 잘λͺ»λœ issuer μ§€μ • μ‹œ temMALFORMED λ°œμƒ
βœ… AMM Info Result: { "amm": { "account": "rUd5wEYNLtC4NRoMEXDAmd8L9ASof8Hn18", "amount": "1000000", "amount2": { "currency": "USD", "issuer": "rER8ArNFu1bTgoAbNzCxUt9iMvTHWoPJus", "value": "10" }, "asset2_frozen": false, "auction_slot": { "account": "rER8ArNFu1bTgoAbNzCxUt9iMvTHWoPJus", "discounted_fee": 3, "expiration": "2025-09-01T13:51:32+0000", "price": { "currency": "03930D02208264E2E40EC1B0C09E4DB96EE197B1", "issuer": "rUd5wEYNLtC4NRoMEXDAmd8L9ASof8Hn18", "value": "0" }, "time_interval": 10 }, "lp_token": { "currency": "03930D02208264E2E40EC1B0C09E4DB96EE197B1", "issuer": "rUd5wEYNLtC4NRoMEXDAmd8L9ASof8Hn18", "value": "3162.277660168379" }, "trading_fee": 30, "vote_slots": [ { "account": "rER8ArNFu1bTgoAbNzCxUt9iMvTHWoPJus", "trading_fee": 30, "vote_weight": 100000 } ] }, "ledger_current_index": 5806953, "validated": false }
TypeScript
볡사
좜λ ₯ 둜그 ν•˜λ‹¨(μ€‘μš”) :
πŸ“Œ LPToken Info: { currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', issuer: 'rUd5wEYNLtC4NRoMEXDAmd8L9ASof8Hn18', value: '3162.277660168379' }
TypeScript
볡사