참고자료 : Offer(xrpl.js)
스크립트 실행 명령어
#1) IOU/XRP 쌍 오퍼 생성 (user)
$npx ts-node xrpl/PermissionedDEX/createPermissionedOffer.ts
#PermissionedDomains/createDomain 스크립트에서 생성된 DomainID(64 hex)를 가져와야 함.
#const DOMAIN_ID = "2A65BCCE9715703A09460B44812BB65D41B9406A42D0CC66979E385C5788####"
#2) 오더북 조회
$npx ts-node xrpl/PermissionedDEX/bookOffers.ts
# 실행 전 1번 스크립트 실행 후 tx_json에서 "Sequence": 123456 가져와야 함.
#3) 오퍼 취소 (user)
$npx ts-node xrpl/PermissionedDEX/cancelOffer.ts
#마찬가지로 PermissionedDomains/createDomain 스크립트에서 생성된 DomainID(64 hex)를 가져와야 함.
#const DOMAIN_ID = "2A65BCCE9715703A09460B44812BB65D41B9406A42D0CC66979E385C5788####"
Bash
복사
1. Permissioned DEX란?
Permissioned DEX는 도메인 규칙이 적용된 탈중앙화 거래소(DEX) 환경이다.
일반 오픈 DEX와 달리, 특정 도메인에 가입된 계정만 해당 오더북의 거래에 참여할 수 있다.
•
Permissioned Offer = DomainID가 붙은 오퍼
•
Open Offer = DomainID 없는 오퍼
거래 규칙은 도메인의 AcceptedCredentials에 따라 결정된다.
즉, 오퍼를 올리거나 체결하려면 해당 도메인에서 허용한 Credential을 보유해야 한다.
2. 왜 필요한가?
•
규제 준수
KYC/AML 요건이 있는 금융기관이 XRPL DEX를 활용할 수 있게 함.
•
접근 통제
허용된 Credential 보유자만 거래 가능 → 무허가 계정 접근 차단.
•
정책 분리
거래 제한 조건은 오퍼에 직접 쓰지 않고, 도메인 정책만 수정해서 일괄 반영 가능.
•
유연한 시장 형성
하나의 토큰이라도 오픈 DEX와 Permissioned DEX에서 다른 유동성 풀을 운영 가능.
3. 시나리오: createPermissionedOffer → bookOffers → cancelOffer
Step 1. Permissioned Offer 생성
•
주체: 도메인 멤버(트레이더)
•
행동: OfferCreate 트랜잭션 전송
•
내용:
◦
DomainID: 참여할 도메인의 ID 지정
◦
TakerGets / TakerPays: 매수·매도 자산 지정
◦
해당 도메인의 AcceptedCredentials 조건을 만족해야 전송 가능
import { Client, Wallet, Transaction } from "xrpl"
import path from "path"
import dotenv from "dotenv"
dotenv.config({ path: path.join(__dirname, "..", ".env") })
// 하이브리드 오퍼를 만들고 싶으면 true로 설정 (tfHybrid 플래그 활성화)
// 하이브리드란 Permissioned DEX 오퍼가 퍼블릭 오더북에도 걸릴 수 있도록 하는 모드
const HYBRID = false
const TF_HYBRID = 0x00100000 // tfHybrid 플래그 비트값
export async function createPermissionedOffer() {
const client = new Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// ADMIN_SEED: 예시에서 IOU를 발행하는 발행자(issuer)
// USER_SEED: 오퍼를 실제 생성하는 계정(트레이더)
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)
const user = Wallet.fromSeed(USER_SEED)
// ⚠️ PermissionedDomains/createDomain 스크립트에서 생성된 DomainID(64 hex)
const DOMAIN_ID = "2A65BCCE9715703A09460B44812BB65D41B9406A42D0CC66979E385C5788####"
/**
* OfferCreate 트랜잭션 구성
* - XRPL 의미:
* * TakerGets = 시장이 가져가는 것(= 내가 파는 것)
* * TakerPays = 시장이 지불하는 것(= 내가 받는 것)
* - 아래 예시는 "USD(IOU, Admin 발행)"을 팔고 "XRP"를 받는 오퍼
*/
const tx: Transaction = {
TransactionType: "OfferCreate",
Account: user.address, // 오퍼 생성자
TakerGets: { // 내가 파는 IOU (USD)
currency: "ABC",
issuer: admin.address,
value: "10"
},
TakerPays: "10000000", // 내가 받는 XRP (10 XRP → drops 단위)
DomainID: DOMAIN_ID, // Permissioned Domain ID 연결
...(HYBRID ? { Flags: TF_HYBRID } : {}) // 하이브리드 모드일 경우 플래그 추가
}
try {
const prepared = await client.autofill(tx) // Fee, Sequence 등 자동 보정
const signed = user.sign(prepared) // 오퍼 생성자(User)가 서명
const result = await client.submitAndWait(signed.tx_blob)
console.log(JSON.stringify(result, null, 2)) // 전체 결과 출력
return result
} finally {
await client.disconnect()
console.log("🔄 연결 종료")
}
}
// 직접 실행
if (require.main === module) {
createPermissionedOffer().catch(e => { console.error(e); process.exit(1) })
}
TypeScript
복사
•
추가로, 생성한 offer 취소를 위해 트랜잭션 전송 후 tx_json의 sequence를 저장,
# OfferCreate 트랜잭션의 sequence
"Sequence": 123456
Bash
복사
Step 2. 오더북 조회
•
주체: 누구나
•
행동: book_offers RPC 호출
•
내용:
◦
domain 파라미터 포함 → 해당 도메인 오더북만 표시
◦
domain 파라미터 생략 → 오픈 오더북만 표시
import { Client, Wallet } from "xrpl"
import path from "path"
import dotenv from "dotenv"
dotenv.config({ path: path.join(__dirname, "..", ".env") })
export async function bookOffers() {
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")
// ADMIN 계정 (USD 발행자)
const admin = Wallet.fromSeed(ADMIN_SEED.trim())
const ADMIN_ADDRESS = admin.address
// ⚠️ PermissionedDomains/createDomain 스크립트에서 생성된 DomainID(64 hex)
// "" (빈 문자열)로 두면 → 일반 오픈 오더북 조회
const DOMAIN_ID = "2A65BCCE9715703A09460B44812BB65D41B9406A42D0CC66979E385C5788####"
try {
/**
* book_offers RPC 요청
* - taker_pays: 내가 지불할 자산(XRP)
* - taker_gets: 내가 받을 자산(USD, ADMIN 발행)
* - domain: 지정 시 해당 도메인 오더북만 반환
*/
const req: any = {
command: "book_offers",
taker_pays: { currency: "XRP" }, // 지불할 자산 (XRP)
taker_gets: { currency: "ABC", issuer: ADMIN_ADDRESS }, // 받을 자산 (ABC, Admin 발행)
limit: 50
}
if (DOMAIN_ID) req.domain = DOMAIN_ID // DomainID 조건 추가 (Permissioned DEX)
const result: any = await client.request(req)
// 전체 응답 출력
console.log(JSON.stringify(result.result || result, null, 2))
return result
} finally {
await client.disconnect()
console.log("🔄 연결 종료")
}
}
// 직접 실행
if (require.main === module) {
bookOffers().catch(e => { console.error(e); process.exit(1) })
}
TypeScript
복사
Step 3. Permissioned Offer 취소
•
주체: 오퍼 생성자
•
행동: OfferCancel 트랜잭션 전송
•
내용: 취소할 오퍼 시퀀스 번호(OfferSequence) 지정
import { Client, Wallet, Transaction } from "xrpl"
import path from "path"
import dotenv from "dotenv"
dotenv.config({ path: path.join(__dirname, "..", ".env") })
export async function cancelOffer() {
const client = new Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
const USER_SEED = process.env.USER_SEED
if (!USER_SEED) throw new Error("Missing env: USER_SEED")
// USER 지갑 로드 (오퍼 생성자)
const user = Wallet.fromSeed(USER_SEED)
/**
* ⚠️ OfferSequence 지정 방법:
* 1) 오퍼 생성 시, 해당 트랜잭션의 Sequence 값 사용
* 2) 또는 account_offers RPC로 내 오퍼 목록 조회 후 원하는 Offer의 Sequence 가져오기
*/
const OFFER_SEQUENCE = 123456 // 실제 취소할 Offer의 시퀀스 번호, OfferCreate 트랜잭션에서 생성됨
try {
// OfferCancel 트랜잭션 구성
const tx: Transaction = {
TransactionType: "OfferCancel",
Account: user.address, // 오퍼를 취소할 계정
OfferSequence: OFFER_SEQUENCE // 취소할 오퍼의 시퀀스
}
// 트랜잭션 준비 → 서명 → 제출
const prepared = await client.autofill(tx)
const signed = user.sign(prepared)
const result = await client.submitAndWait(signed.tx_blob)
// 전체 응답 로그 출력
console.log(JSON.stringify(result, null, 2))
return result
} finally {
await client.disconnect()
console.log("🔄 연결 종료")
}
}
// 직접 실행
if (require.main === module) {
cancelOffer().catch(e => { console.error(e); process.exit(1) })
}
TypeScript
복사

