About XRPL
home

PermissionedDex

주요 내용
도메인 규칙 적용된 DEX 거래
폴더명
PermissionedDex
참고자료 : 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. 시나리오: createPermissionedOfferbookOfferscancelOffer

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
복사