About XRPL
home

TokenEscrow

์ฃผ์š” ๋‚ด์šฉ
ํ† ํฐ ์˜ˆ์น˜ยทํ•ด์ œ
ํด๋”๋ช…
TokenEscrow

์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ๋ช…๋ น์–ด

#1) MPT๋กœ ์—์Šคํฌ๋กœ ์ƒ์„ฑํ•˜๊ธฐ $npx ts-node xrpl/TokenEscrow/escrowCreateMPT.ts #createIssuance ์‹คํ–‰ ๊ฒฐ๊ณผ์—์„œ ๋ณต์‚ฌํ•œ MPT IssuanceID(48hex)๋ฅผ ๋„ฃ์–ด์ค˜์•ผ ํ•จ. #const ISSUANCE_ID = "0049CE349E4215DD8AC6196A0A5027DF489AEC3B17BD6211" #2) IOU๋กœ ์—์Šคํฌ๋กœ ์ƒ์„ฑํ•˜๊ธฐ (Admin ๊ณ„์ •์— 17๋ฒˆ ํ”Œ๋ž˜๊ทธ๋กœ AccountSet ํ•„์ˆ˜) $npx ts-node xrpl/TokenEscrow/escrowCreateIOU.ts ์ƒ์„ฑ์ด ๋˜๋ฉด, EscrowCreate(MPT Userโ†’User2) -> Owner=<User์ฃผ์†Œ>, OfferSequence=123456 ๋ผ๊ณ  ๋œจ๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ OfferSequence๋ฅผ ๊ธฐ๋กํ•ด 3,4๋ฒˆ ์Šคํฌ๋ฆฝํŠธ์— ์‚ฌ์šฉ #3) ์—์Šคํฌ๋กœ ๋๋‚ด๊ธฐ (์กฐ๊ฑด ๋๋‚˜๊ธฐ ์ „์—๋Š” ์•ˆ๋จ) $npx ts-node xrpl/TokenEscrow/escrowFinish.ts #4) ์—์Šคํฌ๋กœ ์ทจ์†Œํ•˜๊ธฐ (์กฐ๊ฑด ๋๋‚˜๊ธฐ ์ „์—๋Š” ์•ˆ๋จ) $npx ts-node xrpl/TokenEscrow/escrowCancel.ts
Bash
๋ณต์‚ฌ

1. TokenEscrow๋ž€?

TokenEscrow๋Š” XRPL์˜ ๊ธฐ์กด XRP ์ „์šฉ Escrow ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•ด,
IOU ํ† ํฐ๊ณผ MPT(Multi-Purpose Token)๋„ ์—์Šคํฌ๋กœ์— ๊ฑธ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.
ํ† ํฐ ํƒ€์ž…๋ณ„ ํŠน์ง•:
โ€ข
IOU
โ—ฆ
๋ฐœํ–‰์ž์™€ ๋ณด์œ ์ž ๊ฐ„ Trustline ํ•„์š”
โ—ฆ
๋ฐœํ–‰์ž ์ •์ฑ…: RequireAuth, Freeze, TransferRate
โ—ฆ
Escrow ํ—ˆ์šฉ ํ”Œ๋ž˜๊ทธ: lsfAllowTrustLineLocking : 17
โ€ข
MPT
โ—ฆ
Trustline ์—†์Œ
โ—ฆ
๋ฐœํ–‰์ž ์ •์ฑ…: RequireAuth, Lock, TransferFee
โ—ฆ
Escrow ํ—ˆ์šฉ ํ”Œ๋ž˜๊ทธ: tfMPTCanEscrow (MPTokenIssuance ํŠธ๋žœ์žญ์…˜์— ๋ช…์‹œํ•ด์•ผ Escrow ๊ฐ€๋Šฅ)
โ—ฆ
์ „์†ก ๊ฐ€๋Šฅ ํ”Œ๋ž˜๊ทธ: tfMPTCanTransfer (MPTokenIssuance ํŠธ๋žœ์žญ์…˜์— ๋ช…์‹œ, ์—†์œผ๋ฉด ๋ฐœํ–‰์ž์—๊ฒŒ๋งŒ ์ „์†ก ๊ฐ€๋Šฅ)

2. ์™œ ํ•„์š”ํ•œ๊ฐ€?

โ€ข
์ž์‚ฐ ์ž ๊ธˆ ์ง€์› ํ™•๋Œ€
โ—ฆ
XRP๋ฟ ์•„๋‹ˆ๋ผ, ๋ฐœํ–‰ ํ† ํฐ(์Šคํ…Œ์ด๋ธ”์ฝ”์ธยทํฌ์ธํŠธยท๊ถŒ๋ฆฌ์ฆ์„œ ๋“ฑ)๋„ ์—์Šคํฌ๋กœ ๊ฐ€๋Šฅ.
โ€ข
์ •์‚ฐ ์˜ˆ์ธก์„ฑ ๋ณด์žฅ
โ—ฆ
Escrow ์ƒ์„ฑ ์‹œ์ ์˜ TransferRate/TransferFee๋ฅผ ๊ณ ์ •, ์ดํ›„ ๋ณ€๊ฒฝ์—๋„ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์Œ.
โ€ข
๋ฐœํ–‰์ž ์ •์ฑ… ์ค€์ˆ˜
โ—ฆ
RequireAuthยทFreezeยทLock ๋“ฑ์˜ ์ œ์–ด๊ฐ€ Escrow์—๋„ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ.
โ€ข
๋‹ค์–‘ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค ์ง€์›
โ—ฆ
๊ณ„์•ฝ๊ธˆ, ์ง€๊ธ‰ ๋ณด๋ฅ˜, ์กฐ๊ฑด๋ถ€ ์ง€๊ธ‰, ๋ฒ ์ŠคํŒ…(vesting) ๋“ฑ์— IOU/MPT ์‚ฌ์šฉ ๊ฐ€๋Šฅ.

3. ์‹œ๋‚˜๋ฆฌ์˜ค: escrowCreate โ†’ escrowFinish โ†’ (์˜ต์…˜) escrowCancel

Step 1. ์—์Šคํฌ๋กœ ์ƒ์„ฑ (escrowCreate.ts)

โ€ข
์ฃผ์ฒด: ์†ก๊ธˆ์ž(Source) : User
โ€ข
ํ–‰๋™: EscrowCreate ํŠธ๋žœ์žญ์…˜ ์ „์†ก
โ€ข
Amount ํ˜•์‹:
โ—ฆ
XRP: drops ๋ฌธ์ž์—ด
โ—ฆ
IOU/MPT: CurrencyAmount ๊ฐ์ฒด
โ€ข
ํ•„์ˆ˜ ์กฐ๊ฑด:
โ—ฆ
IOU/MPT โ†’ CancelAfter ํ•„์ˆ˜
โ—ฆ
๋ฐœํ–‰์ž๊ฐ€ Source์ด๋ฉด ๋ถˆ๊ฐ€
โ—ฆ
๋ฐœํ–‰์ž Escrow ํ—ˆ์šฉ ํ”Œ๋ž˜๊ทธ ์„ค์ • ํ•„์š”
โ—ฆ
Source๊ฐ€ RequireAuth ํ† ํฐ์ด๋ฉด ์‚ฌ์ „ ์Šน์ธ ํ•„์š”
โ—ฆ
Source ์ž”์•กยทTrustlineยทAuthorize ์ƒํƒœ ์ •์ƒ
+์ฃผ์˜ํ•  ์  : IOU์˜ ๊ฒฝ์šฐ Escrow ์ƒ์„ฑํ•˜๊ธฐ ์ „์— Admin ๊ณ„์ •์— 17๋ฒˆ ํ”Œ๋ž˜๊ทธ(asfAllowTrustLineLocking)๋กœ accountSet์„ ํ•ด ์ค˜์•ผ ํ•จ!
์ž˜ ์•ˆ๋˜๋ฉด : https://xspence.co.uk/devnet ์—์„œ AccountSet ํ•˜๋ฉด ๋จ.
import { Client, Wallet, Transaction } from "xrpl" import { encodeForSigning, encode } from "ripple-binary-codec" import { sign as kpSign, deriveKeypair } from "ripple-keypairs" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) // XRPL ๋ฆฌํ”Œ ์—ํญ ์‹œ๊ฐ„ ๋ณ€ํ™˜ function Now() { return Math.floor(Date.now() / 1000) - 946_684_800 } export async function escrowCreateMPT() { 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! const USER2_SEED = process.env.USER2_SEED! const admin = Wallet.fromSeed(ADMIN_SEED) // MPT ๋ฐœํ–‰์ž(์ฐธ์กฐ์šฉ) const user = Wallet.fromSeed(USER_SEED) // Escrow ์†Œ์Šค const user2 = Wallet.fromSeed(USER2_SEED) // Escrow ๋ชฉ์ ์ง€ try { // โš ๏ธ createIssuance ์‹คํ–‰ ๊ฒฐ๊ณผ์—์„œ ๋ณต์‚ฌํ•œ MPT IssuanceID(48hex) const ISSUANCE_ID = "0049CE349E4215DD8AC6196A0A5027DF489AEC3B17BD6211" // ๐Ÿ“ EscrowCreate ํŠธ๋žœ์žญ์…˜ (MPTAmount ์‚ฌ์šฉ) const tx: Transaction = { TransactionType: "EscrowCreate", Account: user.address, // ์†Œ์Šค(User) Destination: user2.address, // ๋ชฉ์ ์ง€(User2) Amount: { mpt_issuance_id: ISSUANCE_ID, value: "50" // ์ „์†ก ์ˆ˜๋Ÿ‰ } as any, // XRPL SDK์—์„œ MPTAmount ๋ฏธ์ง€์› โ†’ any ์ฒ˜๋ฆฌ FinishAfter: Now() + 30, // ์ตœ์†Œ ์™„๋ฃŒ ์‹œ๊ฐ (30์ดˆ ํ›„) CancelAfter: Now() + 120 // ์ทจ์†Œ ๊ฐ€๋Šฅ ์‹œ๊ฐ (120์ดˆ ํ›„) } // 1) autofill โ†’ ๊ธฐ๋ณธ ํ•„๋“œ ์ฑ„์›€ (Sequence, Fee ๋“ฑ) const prepared = await client.autofill(tx) // 2) ์„œ๋ช… ๋Œ€์ƒ ๊ฐ์ฒด์— SigningPubKey๋ฅผ ๋„ฃ์Œ const toSign = { ...prepared, SigningPubKey: user.publicKey, } // 3) seed ๊ธฐ๋ฐ˜ keypair ์ƒ์„ฑ const { privateKey } = deriveKeypair(USER_SEED) // 4) ํ”„๋ฆฌํ”ฝ์Šค ํฌํ•จ ๋ฐ์ดํ„ฐ ์„œ๋ช… const signingData = encodeForSigning(toSign as any) const signature = kpSign(signingData, privateKey) // 5) ์ตœ์ข… ํŠธ๋žœ์žญ์…˜์— TxnSignature ์ถ”๊ฐ€ ํ›„ ์ธ์ฝ”๋”ฉ const signedTx = { ...toSign, TxnSignature: signature } const tx_blob = encode(signedTx) // 6) ์ œ์ถœ & ๊ฒฐ๊ณผ ํ™•์ธ const result = await client.submitAndWait(tx_blob) console.log(JSON.stringify(result, null, 2)) console.log(`โœ… EscrowCreate(MPT) ์„ฑ๊ณต: Owner=${user.address}, OfferSequence=${prepared.Sequence}`) return result } finally { await client.disconnect() console.log("๐Ÿ”„ ์—ฐ๊ฒฐ ์ข…๋ฃŒ") } } // ์ง์ ‘ ์‹คํ–‰ if (require.main === module) { escrowCreateMPT().catch(e => { console.error(e); process.exit(1) }) }
JSON
๋ณต์‚ฌ
โ€ข
์„œ๋ช… ๋ฐฉ์‹ : xrpl.js๊ฐ€ ์•„์ง EscrowCreate์˜ Amount๋ฅผ โ€œXRP ๋ฌธ์ž์—ดโ€๋กœ๋งŒ ๊ฒ€์ฆ/์ง๋ ฌํ™”ํ•˜๋ ค๊ณ  ํ•ด์„œ IOU/MPT ๊ฐ์ฒด ๋„ฃ์œผ๋ฉด ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ง‰ํ˜€ Invalid signature๊ฐ€ ๋‚จ.
โ€ข
๊ทธ๋ž˜์„œ raw ์„œ๋ช…(encodeForSigning + ripple-keypairs.sign) ์œผ๋กœ ๊ฒ€์ฆ/์ง๋ ฌํ™”๋ฅผ ์šฐํšŒํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ๊ทธ๋Œ€๋กœ ๋ฐ›์•„์„œ ์ •์ƒ ์ฒ˜๋ฆฌ.
//๊ธฐ์กด ๋ฐฉ์‹ const prepared = await client.autofill(tx) const signed = user.sign(prepared) const result = await client.submitAndWait(signed.tx_blob)
TypeScript
๋ณต์‚ฌ
//TokenEscrow์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์„œ๋ช… ๋ฐฉ์‹ import { encodeForSigning, encode } from "ripple-binary-codec" import { sign as kpSign, deriveKeypair } from "ripple-keypairs" const prepared = await client.autofill(tx) // 1) ์„œ๋ช… ๋Œ€์ƒ ๊ฐ์ฒด์— SigningPubKey๋ฅผ "๋ฏธ๋ฆฌ" ๋„ฃ๋Š”๋‹ค const toSign = { ...prepared, SigningPubKey: user.publicKey, // ๋ณดํ†ต 'ED...' 33๋ฐ”์ดํŠธ(hex) } // 2) seed๋กœ keypair ํŒŒ์ƒ (โ˜… Wallet.privateKey ๋Œ€์‹ , seedโ†’derive ์‚ฌ์šฉ) const { privateKey, publicKey } = deriveKeypair(USER_SEED) // 3) ์„œ๋ช… (ํ”„๋ฆฌํ”ฝ์Šค ์ž๋ฅด์ง€ ๋ง๊ณ  ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ) const signingData = encodeForSigning(toSign as any) const signature = kpSign(signingData, privateKey) // 4) ์ตœ์ข… ์ธ์ฝ”๋”ฉ & ์ œ์ถœ const signedTx = { ...toSign, TxnSignature: signature } const tx_blob = encode(signedTx) const result = await client.submitAndWait(tx_blob)
TypeScript
๋ณต์‚ฌ
โ€ข
์ฝ˜์†” ์ถœ๋ ฅ ์˜ˆ์‹œ
EscrowCreate(MPT Userโ†’User2) -> Owner=<User์ฃผ์†Œ>, OfferSequence=123456
Bash
๋ณต์‚ฌ

Step 2. ์—์Šคํฌ๋กœ ํ•ด์ œ (escrowFinish.ts)

โ€ข
์ฃผ์ฒด: ๋ˆ„๊ตฌ๋‚˜(์กฐ๊ฑด ์ถฉ์กฑ ์‹œ)
โ€ข
ํ–‰๋™: EscrowFinish ํŠธ๋žœ์žญ์…˜ ์ „์†ก
โ€ข
ํ•„์ˆ˜ ์กฐ๊ฑด:
โ—ฆ
FinishAfter ์‹œ๊ฐ„ ๊ฒฝ๊ณผ
โ—ฆ
Destination์ด RequireAuth์ด๋ฉด ์‚ฌ์ „ ์Šน์ธ ํ•„์š”
โ—ฆ
IOU โ†’ Trustline ์กด์žฌ / MPT โ†’ ํ† ํฐ ๋ณด์œ  ๋˜๋Š” ์ž๋™ ์ƒ์„ฑ ๊ฐ€๋Šฅ
โ—ฆ
Freeze/Lock ์ƒํƒœ ์ฒดํฌ
โ€ข
์ƒํƒœ ๋ณ€ํ™”:
โ—ฆ
์ž”์•ก ์ด๋™: ๋ฐœํ–‰์ž/์ˆ˜์‹ ์ž ์กฐํ•ฉ์— ๋”ฐ๋ผ EscrowedAmount, OutstandingAmount ์ฒ˜๋ฆฌ ๊ทœ์น™ ๋‹ค๋ฆ„
โ—ฆ
Escrow ๊ฐ์ฒด ์‚ญ์ œ
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) export async function escrowFinish() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() // ๐Ÿ”‘ ์ง€๊ฐ‘ ๋กœ๋“œ const USER_SEED = process.env.USER_SEED! // Escrow ์†Œ์Šค(User) const USER2_SEED = process.env.USER2_SEED! // ๋ชฉ์ ์ง€(User2, Finisher) const ownerWallet = Wallet.fromSeed(USER_SEED) // Escrow ์†Œ์Šค const finisherWallet = Wallet.fromSeed(USER2_SEED) // Finish ์‹คํ–‰์ž try { // โš ๏ธ EscrowCreate ์‹คํ–‰ ํ›„ ์ฝ˜์†”์— ์ถœ๋ ฅ๋œ Sequence ๊ฐ’์œผ๋กœ ๊ต์ฒด const OFFER_SEQUENCE = 4836805 // ๐Ÿ“ EscrowFinish ํŠธ๋žœ์žญ์…˜ const tx: Transaction = { TransactionType: "EscrowFinish", Account: finisherWallet.address, // ์‹คํ–‰์ž(๋ณดํ†ต ์ˆ˜ํ˜œ์ž User2) Owner: ownerWallet.address, // Escrow ์› ์†Œ์Šค ์ฃผ์†Œ OfferSequence: OFFER_SEQUENCE // EscrowCreate ์‹œํ€€์Šค ๋ฒˆํ˜ธ } // ์ž๋™ ํ•„๋“œ ์ฑ„์›€ โ†’ ์„œ๋ช… โ†’ ์ œ์ถœ const prepared = await client.autofill(tx) const signed = finisherWallet.sign(prepared) const result = await client.submitAndWait(signed.tx_blob) console.log(JSON.stringify(result, null, 2)) console.log(`โœ… EscrowFinish ์„ฑ๊ณต: Owner=${ownerWallet.address}, OfferSequence=${OFFER_SEQUENCE}`) return result } finally { await client.disconnect() console.log("๐Ÿ”„ ์—ฐ๊ฒฐ ์ข…๋ฃŒ") } } // ์ง์ ‘ ์‹คํ–‰ if (require.main === module) { escrowFinish().catch(e => { console.error(e); process.exit(1) }) }
TypeScript
๋ณต์‚ฌ

Step 3. ์—์Šคํฌ๋กœ ์ทจ์†Œ (escrowCancel.ts)

โ€ข
์ฃผ์ฒด: ๋ˆ„๊ตฌ๋‚˜(์กฐ๊ฑด ์ถฉ์กฑ ์‹œ)
โ€ข
ํ–‰๋™: EscrowCancel ํŠธ๋žœ์žญ์…˜ ์ „์†ก
โ€ข
ํ•„์ˆ˜ ์กฐ๊ฑด:
โ—ฆ
CancelAfter ๊ฒฝ๊ณผ
โ—ฆ
Source AuthorizeยทTrustline/MPT ๋ณด์œ  ์ƒํƒœ ์ •์ƒ
โ—ฆ
Freeze/Lock ์ƒํƒœ๋Š” ์ทจ์†Œ ๊ฐ€๋Šฅ
โ€ข
์ƒํƒœ ๋ณ€ํ™”:
โ—ฆ
์ž”์•ก ๋ฐ˜ํ™˜
โ—ฆ
Escrow ๊ฐ์ฒด ์‚ญ์ œ
import { Client, Wallet, Transaction } from "xrpl" import path from "path" import dotenv from "dotenv" dotenv.config({ path: path.join(__dirname, "..", ".env") }) export async function escrowCancel() { const client = new Client("wss://s.devnet.rippletest.net:51233") await client.connect() // ๐Ÿ”‘ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ์‹œ๋“œ ๋กœ๋“œ const USER_SEED = process.env.USER_SEED! // Escrow ์†Œ์Šค(User) const USER2_SEED = process.env.USER2_SEED! // Cancel ์‹คํ–‰์ž(User2) // ๐Ÿชช Escrow ์†Œ์Šค(Owner) / Cancel ์‹คํ–‰์ž ๊ณ„์ • ์ƒ์„ฑ const ownerWallet = Wallet.fromSeed(USER_SEED) const cancellerWallet = Wallet.fromSeed(USER2_SEED) try { /** * โš ๏ธ EscrowCreate ์‹คํ–‰ ํ›„ ๋ฐ˜ํ™˜๋œ "Sequence"๋ฅผ OFFER_SEQUENCE์— ๋„ฃ์–ด์•ผ ํ•จ * - EscrowCancel์€ Escrow๋ฅผ ์ƒ์„ฑํ•œ ์›๋ž˜ ํŠธ๋žœ์žญ์…˜(Sequence)์™€ Owner ์ฃผ์†Œ๋ฅผ ์ง€์ •ํ•ด์•ผ ์œ ํšจ */ const OFFER_SEQUENCE = 4836805 const tx: Transaction = { TransactionType: "EscrowCancel", Account: cancellerWallet.address, // Cancel ์‹คํ–‰์ž(= ํŠธ๋žœ์žญ์…˜ ์„œ๋ช… ์ฃผ์ฒด) Owner: ownerWallet.address, // Escrow ์†Œ์Šค(์›๋ž˜ Escrow ๋งŒ๋“  ๊ณ„์ •) OfferSequence: OFFER_SEQUENCE // EscrowCreate์˜ Sequence } // ํŠธ๋žœ์žญ์…˜ ์ค€๋น„ โ†’ ์„œ๋ช… โ†’ ์ œ์ถœ const prepared = await client.autofill(tx) const signed = cancellerWallet.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) { escrowCancel().catch(e => { console.error(e); process.exit(1) }) }
TypeScript
๋ณต์‚ฌ