์คํฌ๋ฆฝํธ ์คํ ๋ช
๋ น์ด
#1) ์ฌ์ ๋ฐํ ์ ์ ์์ฑ(Admin)
$npx ts-node xrpl/MPTokensV1/createIssuance.ts
# ์คํ ํ IssuanceID(created): 006419063CEBEB49FC20032206CE0F203138BFC59F1A#### ๊ธฐ๋กํด๋๊ธฐ, ์ดํ 2~5๋ฒ ์คํฌ๋ฆฝํธ์ ์ฐ์
#2) ํ๋ ๊ถํ ๋ถ์ฌ : 1๋ฒ์์ ํธ๋์ญ์
์ (tfMPTRequireAuth: true)๋ก ๋ณด๋์ ๋๋ง!
-์๋ ๋์๋ ์ตํธ์ธ ํด์ผ ํจ.
$npx ts-node xrpl/MPTokensV1/authorizeHolder.tsi
#3) MPT ์ ์ก
$npx ts-node xrpl/MPTokensV1/sendMPT.ts
#4) ๋ฐํ์ ์ ์ญ์ (๋ชจ๋ ํ๋๋ค์ ํด๋น ํ ํฐ ์์ก์ด 0์ด์ด์ผ ๊ฐ๋ฅ)
$npx ts-node xrpl/MPTokensV1/destroyIssuance.ts
#5) ๋ฐํ๋ ํ ํฐ ๋ฝ/์ธ๋ฝ (Optional)
$npx ts-node xrpl/MPTokensV1/setIssuance.ts
Bash
๋ณต์ฌ
1. MPT(Multi-Purpose Token)์ด๋?
MPT๋ XRPL์ ์๋ก์ด ํ์ ๋ธ ํ ํฐ ํ์
์ผ๋ก, ๊ธฐ์กด IOU(์๋ฐฉํฅ ํธ๋ฌ์คํธ๋ผ์ธ)๋ณด๋ค ๋จ์ํ ๋ฐํยท๋ณด์ ๋ชจ๋ธ์ ์ ๊ณตํ๋ค.
ํ ํฐ์ ๋ ๊ฐ์ง ์์ฅ ๊ฐ์ฒด๋ก ๋ค๋ค์ง๋ค:
โข
MPTokenIssuance: ๋ฐํ ์ ์(์ค์ผ์ผ, ์ต๋๋ฐํ๋, ์ ์ก/๋ฝ/ํด๋ก๋ฐฑ/๊ถํํ์ ๋ฑ์ ์ ์ฑ
)
โข
MPToken: ๊ฐ๋ณ ๊ณ์ ์ด ํน์ ๋ฐํ๋ณธ์ ์ผ๋ง๋ ๋ณด์ ํ๋์ง
v1์ ํน์ง: ์ง์ ๊ฒฐ์ ๋ง ์ง์(๊ณ์ โ๊ณ์ ). DEX ๊ฑฐ๋๋ ๋ถ๊ฐ.
2. ์ ํ์ํ๊ฐ?
โข
๊ฐ๊ฒฐํ ์ด์ ๋ชจ๋ธ
Trustline ์์ด ๋ฐํยท์ ์ก ์ค์ฌ์ ๋จ๋ฐฉํฅ ๋ชจ๋ธ.
โข
์ ์ฑ
์ผ์ํ
์ ์ก ๊ฐ๋ฅ ์ฌ๋ถ, ๊ถํ ํ์(RequireAuth), ๋ฝ/์ธ๋ฝ, ํด๋ก๋ฐฑ ๋ฑ์ ๋ฐํ๋ณธ ๋จ์ ์ ์ฑ
์ ํ ๊ณณ์์ ๊ด๋ฆฌ.
โข
๋ช
ํํ ์๋ณ์
๊ฒฐ์ ์ MPTokenIssuanceID(๊ณ ์ ID)๋ฅผ ์ฌ์ฉ โ ํ ํฐ ์ ์๋ฅผ ๋ช
ํํ ๊ตฌ๋ถ.
โข
์ค์ผ์ผ(์์์ ) ๋ด์ฅ
AssetScale๋ก ์ต์๋จ์๋ฅผ ์ ์ โ ๊ธ์ก ์ฒ๋ฆฌ๊ฐ ์์ธก ๊ฐ๋ฅ.
3. ์๋๋ฆฌ์ค: createIssuance โ authorizeHolder โ sendMPT (+ setIssuance, destroyIssuance )
Step 1. ๋ฐํ ์ ์ ์์ฑ (createIssuance.ts)
โข
์ฃผ์ฒด: ADMIN(๋ฐํ์)
โข
ํ๋: MPTokenIssuanceCreate ํธ๋์ญ์
์ ์ก
โข
๋ด์ฉ:
โฆ
AssetScale(์์ ์๋ฆฌ์), MaximumAmount(์ ํ), Flags( tfMPTCanTransfer, tfMPTRequireAuth ๋ฑ) ์ง์
โฆ
โข
tfMPTRequireAuth: true โ ๋ฐํ์๊ฐ ์ฌ์ ์น์ธ(MPTokenAuthorize)ํ ๊ณ์ ๋ง ํด๋น MPT๋ฅผ ๋ณด์ ยท์๋ น ๊ฐ๋ฅํ๊ฒ ํ๋ ํ์ดํธ๋ฆฌ์คํธ ๋ชจ๋. KYC/AML ๋ฑ ๊ท์ ์ค์ํ ํ ํฐ์ ํ์ฉ.
import { Client, Wallet, Transaction } from "xrpl"
import path from "path"
import dotenv from "dotenv"
dotenv.config({ path: path.join(__dirname, "..", ".env") })
export async function createIssuance() {
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)
/**
* MPTokenIssuanceCreate ํธ๋์ญ์
* - AssetScale: ์์ ์๋ฆฟ์ (0 โ ์ ์๋ง)
* - MaximumAmount: ์ด ๋ฐํ ์ํ (์ต์
)
* - Flags: ์ ์ฑ
์ง์ (์ ์ก ๊ฐ๋ฅ, ์์คํฌ๋ก ๊ฐ๋ฅ, ๊ถํ ํ์ ์ฌ๋ถ ๋ฑ)
*/
const tx: Transaction = {
TransactionType: "MPTokenIssuanceCreate",
Account: admin.address,
AssetScale: 0, // ์์์ ์์
MaximumAmount: "1000000000", // ์ต๋ ๋ฐํ๋ (์ต์
)
Flags: { // ์ ์ฑ
์์
tfMPTCanTransfer: true, // ์ ์ก ๊ฐ๋ฅ
tfMPTCanEscrow: true, // ์์คํฌ๋ก ๊ฐ๋ฅ
tfMPTRequireAuth: false // ๊ถํ ํ์ X
},
// MPTokenMetadata: "<hex-encoded string>" // ํ์์ hex ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ
}
try {
// ํธ๋์ญ์
์ค๋น โ ์๋ช
โ ์ ์ถ
const prepared = await client.autofill(tx)
const signed = admin.sign(prepared)
const result = await client.submitAndWait(signed.tx_blob)
// ์ ์ฒด ์๋ต ๋ก๊ทธ ์ถ๋ ฅ
console.log(JSON.stringify(result, null, 2))
// ๊ฒฐ๊ณผ ๋ก๊ทธ์์ ๋ฐํ๋ณธ์ IssuanceID ์ถ์ถ (์ผ๋ถ ๋
ธ๋ ๊ตฌํ์ฒด๋ meta์ ์ ์ฅ๋จ)
const issuanceId48 = (result.result.meta as any)?.mpt_issuance_id
if (issuanceId48) {
console.log(`IssuanceID(created): ${issuanceId48}`)
} else {
console.log("โ ๏ธ IssuanceID๋ฅผ meta์์ ์ฐพ์ง ๋ชปํ์ต๋๋ค. ์๋ต ๋ก๊ทธ ํ์ธ ํ์.")
}
return result
} finally {
await client.disconnect()
console.log("๐ ์ฐ๊ฒฐ ์ข
๋ฃ")
}
}
// ์ง์ ์คํ
if (require.main === module) {
createIssuance().catch(e => { console.error(e); process.exit(1) })
}
TypeScript
๋ณต์ฌ
โข
์ถ๋ ฅ ์์
IssuanceID(created): 006419063CEBEB49FC20032206CE0F203138BFC59F1A####
PowerShell
๋ณต์ฌ
โข
๊ฒฐ๊ณผ ๋ก๊ทธ์์ IssuanceID๋ฅผ ๋ณต์ฌ โ ์ดํ ๋ชจ๋ ๋จ๊ณ์ ์ฌ์ฉ
Step 2. ํ๋ ๊ถํ ๋ถ์ฌ (authorizeHolder.ts)
โข
์ฃผ์ฒด: ADMIN
โข
ํ๋: MPTokenAuthorize ํธ๋์ญ์
์ ์ก
โข
๋ด์ฉ:
โฆ
๋ฐํ๋ณธ์ด tfMPTRequireAuth=true๋ฉด ์ ์ก ์ ์ ํ๋(USER)๋ฅผ ํ๊ฐํด์ผ ํจ
โฆ
MPTokenIssuanceID: <๋ณต์ฌํ ID>, Holder: USER.address
โฆ
(ํด์ ๋ Flags.tfMPTUnauthorize)
1๋จ๊ณ : User๊ฐ Issuance์ ์ตํธ์ธ
โข
Account : User ์ฃผ์
โข
ํธ๋์ญ์
์๋ช
์ฃผ์ฒด : User
import { Client, Wallet, Transaction } from "xrpl"
import path from "path"
import dotenv from "dotenv"
dotenv.config({ path: path.join(__dirname, "..", ".env") })
// โ ๏ธ createIssuance ์คํ ๋ก๊ทธ์์ ๋ณต์ฌํ IssuanceID ์ฌ์ฉ
const ISSUANCE_ID = "0049CE349E4215DD8AC6196A0A5027DF489AEC3B17BD6211"
export async function authorizeHolder() {
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.USER2_SEED
if (!ADMIN_SEED || !USER_SEED) throw new Error("Missing env: ADMIN_SEED, USER2_SEED")
// ๋ฐํ์(Admin)์ ํผ๋ฐ๊ธ์(User) ์ง๊ฐ ๋ก๋
const admin = Wallet.fromSeed(ADMIN_SEED)
const user = Wallet.fromSeed(USER_SEED)
/**
* Step 1. User๊ฐ Issuance์ ์ตํธ์ธ
* - Account: user.address
* - ์๋ช
์: User (์ค์ค๋ก "์ฐธ์ฌ ์์ฌ" ์ ์ถ)
*/
const tx: Transaction = {
TransactionType: "MPTokenAuthorize",
Account: user.address, // User๊ฐ ์๋ช
/์ ์ก
MPTokenIssuanceID: ISSUANCE_ID, // MPT ๋ฐํ ์ ์ ID
// Holder: user.address // User ๋จ๊ณ์์๋ ๋ถํ์
// Flags: { tfMPTUnauthorize: true } // ํด์ ํ ๋๋ง ์ฌ์ฉ
}
try {
// ํธ๋์ญ์
์ค๋น โ ์๋ช
โ ์ ์ถ
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) {
authorizeHolder().catch(e => { console.error(e); process.exit(1) })
}
TypeScript
๋ณต์ฌ
2๋จ๊ณ : Admin(๋ฐํ์)๊ฐ ํ์ฉ
โข
Account : Admin ์ฃผ์
โข
Holder : User ์ฃผ์
โข
ํธ๋์ญ์
์๋ช
์ฃผ์ฒด : Admin
// createIssuance ์คํ ๋ก๊ทธ์์ ๋ณต์ฌํ IssuanceID
const ISSUANCE_ID = "006419063CEBEB49FC20032206CE0F203138BFC59F1A####"
const tx: Transaction = {
TransactionType: "MPTokenAuthorize",
Account: admin.address,
MPTokenIssuanceID: ISSUANCE_ID,
Holder: user.address
// Flags: { tfMPTUnauthorize: true } // ํด์ ํ๊ณ ์ถ์ ๋๋ง ์ฌ์ฉ
}
TypeScript
๋ณต์ฌ
Step 3. MPT ์ ์ก (sendMPT.ts)
โข
์ฃผ์ฒด: ADMIN(๋ณด๋ด๋ ์ชฝ)
โข
ํ๋: Payment ํธ๋์ญ์
์ ์ก
โข
๋ด์ฉ:
โฆ
Amount๋ MPTAmount ํ์:
{ mpt_issuance_id: "<๋ณต์ฌํ ID>", value: "<์ ์ ๋ฌธ์์ด>" }
โฆ
v1์ ์ง์ ๊ฒฐ์ ๋ง ์ง์(DEX/๊ฒฝ๋กํ์ ์์)
import { Client, Wallet, Transaction } from "xrpl"
import path from "path"
import dotenv from "dotenv"
dotenv.config({ path: path.join(__dirname, "..", ".env") })
// โ ๏ธ createIssuance ์คํ ๋ก๊ทธ์์ ๋ณต์ฌํ IssuanceID ์
๋ ฅ
const ISSUANCE_ID = "0049CE349E4215DD8AC6196A0A5027DF489AEC3B17BD6211"
export async function sendMPT() {
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) // ๋ฐํ์ (์ก์ ์)
const user = Wallet.fromSeed(USER_SEED) // ์์ ์
/**
* MPT ์ ์ก ํธ๋์ญ์
* - TransactionType: Payment
* - Amount: { mpt_issuance_id, value }
* * mpt_issuance_id: ๋ฐํ ์ ์ ๊ณ ์ ID
* * value: ์ ์ก ์๋ (์ ์ ๋ฌธ์์ด)
* - DeliverMax / SendMax ๋ ๊ฐ์ ๊ตฌ์กฐ๋ก ์ง์ ๊ฐ๋ฅ
*/
const tx: Transaction = {
TransactionType: "Payment",
Account: admin.address,
Destination: user.address,
Amount: {
mpt_issuance_id: ISSUANCE_ID, // ๋ฐํ๋ณธ ID
value: "100" // ์ ์ก ์๋
}
}
try {
// ํธ๋์ญ์
์ค๋น โ ์๋ช
โ ์ ์ถ
const prepared = await client.autofill(tx)
const signed = admin.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) {
sendMPT().catch(e => { console.error(e); process.exit(1) })
}
TypeScript
๋ณต์ฌ
(์์ ํ์) Step 4. ๋ฐํ๋ณธ ๋ฝ/์ธ๋ฝ (setIssuance.ts) + Clawback
โข
์ฃผ์ฒด: ADMIN
โข
ํ๋: MPTokenIssuanceSet ํธ๋์ญ์
์ ์ก
โข
๋ด์ฉ:
โฆ
๊ธ๋ก๋ฒ ์ ๊ธ/ํด์ : Flags.tfMPTLock / Flags.tfMPTUnlock
โฆ
ํน์ ํ๋๋ง ์ ๊ธ/ํด์ : Holder์ ๋์ ์ฃผ์ ์ง์ + ๊ฐ์ ํ๋๊ทธ ์ฌ์ฉ
โฆ
ํจ๊ณผ: ์ ๊ธ ์ค์๋ ํด๋น ๋ฒ์ ์ ์ก/์ฌ์ฉ ์ ํ(์ ์ฑ
์ ๋ฐ๋ฆ)
// createIssuance ์คํ ๋ก๊ทธ์์ ๋ณต์ฌํ IssuanceID
const ISSUANCE_ID = "006419063CEBEB49FC20032206CE0F203138BFC59F1A####"
// ===== CLI ์ธ์ ํ์ฑ =====
// ์ฌ์ฉ๋ฒ: ts-node setIssuance.ts <lock|unlock> [holderAddress]
// - ์ฒซ ๋ฒ์งธ ์ธ์(mode): "lock" ์ด๋ฉด ์ ๊ธ, "unlock" ์ด๋ฉด ์ ๊ธ ํด์
// - ๋ ๋ฒ์งธ ์ธ์(holder): ์ ํ๊ฐ. ํน์ ํ๋๋ง ์ ๊ทธ๊ฑฐ๋ ํ ๋ ๊ทธ XRPL ์ฃผ์๋ฅผ ๋ฃ์.
// * holder๋ฅผ ์๋ตํ๋ฉด "๊ธ๋ก๋ฒ" ์ ์ฉ(๋ฐํ๋ณธ ์ ์ฒด๋ฅผ ์ ๊ทธ๊ฑฐ๋ ํ).
const mode = (process.argv[2] || "").toLowerCase()
const holder = process.argv[3]
// ์ธ์ ๊ฒ์ฆ: lock/unlock ์ธ ๊ฐ์ด๋ฉด ์ฌ์ฉ๋ฒ ์๋ด ํ ์ข
๋ฃ(๋น์ ์ ์ข
๋ฃ ์ฝ๋ 1)
if (mode !== "lock" && mode !== "unlock") {
console.error('Usage: ts-node setIssuance.ts <lock|unlock> [holderAddress]')
process.exit(1)
}
// XRPL ํธ๋์ญ์
Flags ์ค์
// - MPTokenIssuanceSet๋ boolean ํ๋๊ทธ๋ก ์ค์ ๊ฐ๋ฅ
// * { tfMPTLock: true } -> ์ ๊ธ
// * { tfMPTUnlock: true } -> ์ ๊ธ ํด์
const flags = mode === "lock" ? { tfMPTLock: true } : { tfMPTUnlock: true }
// ===== ํธ๋์ญ์
๊ตฌ์ฑ =====
const tx: Transaction = {
TransactionType: "MPTokenIssuanceSet",
Account: admin.address, // ๋ฐํ์(ADMIN) ์๋ช
/์ ์ก
MPTokenIssuanceID: ISSUANCE_ID, // createIssuance ๊ฒฐ๊ณผ๋ก ์ป์ ๋ฐํ ์ ์ ID
// Holder ํ๋๋ ์ ํ๊ฐ:
// - ํฌํจํ๋ฉด "ํด๋น ํ๋ ์ฃผ์๋ง" ๋์(๊ฐ๋ณ ์ ๊ธ/ํด์ )
// - ์๋ตํ๋ฉด ๋ฐํ๋ณธ ์ ์ฒด ๋์(๊ธ๋ก๋ฒ ์ ๊ธ/ํด์ )
...(holder ? { Holder: holder } : {}),
Flags: flags // ์์์ mode์ ๋ฐ๋ผ lock/unlock ํ๋๊ทธ ์ง์
}
TypeScript
๋ณต์ฌ
โข
์ฌ์ฉ ๋ฐฉ๋ฒ : ํฐ๋ฏธ๋ ๋ช
๋ น์ด ์์
# ๊ธ๋ก๋ฒ ์ ๊ธ
$npx ts-node setIssuance.ts lock
# ๊ธ๋ก๋ฒ ์ ๊ธ ํด์
$npx ts-node setIssuance.ts unlock
# ํน์ ํ๋(User)๋ง ์ ๊ธ
$npx ts-node setIssuance.ts lock <user ์ฃผ์>
# ํน์ ํ๋(User)๋ง ์ ๊ธ ํด์
$npx ts-node setIssuance.ts unlock <user์ฃผ์>
Bash
๋ณต์ฌ
(์ต์ ) Step 5. ๋ฐํ ์ ์ ์ญ์ (destroyIssuance.ts)
โข
์ฃผ์ฒด: ADMIN
โข
ํ๋: MPTokenIssuanceDestroy ํธ๋์ญ์
์ ์ก
โข
์ฑ๊ณต ์กฐ๊ฑด: ๋ชจ๋ ํ๋์ ์์ก์ด 0์ด์ด์ผ ํจ(ํ ๋ช
์ด๋ผ๋ ๋จ์์์ผ๋ฉด ์คํจ)
โข
ํจ๊ณผ: ๋ฐํ ์ ์ ์ ๊ฑฐ, ๋ฆฌ์ ๋ธ 1 ๊ฐ์(์์ ์ค๋ธ์ ํธ ์ ๊ฐ์)
import { Client, Wallet, Transaction } from "xrpl"
import path from "path"
import dotenv from "dotenv"
dotenv.config({ path: path.join(__dirname, "..", ".env") })
// โ ๏ธ createIssuance ์คํ ๋ก๊ทธ์์ ๋ฐ๊ธ๋ IssuanceID ์
๋ ฅ
const ISSUANCE_ID = "0049CE349E4215DD8AC6196A0A5027DF489AEC3B17BD6211"
export async function destroyIssuance() {
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)
/**
* MPTokenIssuanceDestroy ํธ๋์ญ์
* - ๋ชจ๋ ํ๋๊ฐ ํด๋น Issuance ์์ก์ 0์ผ๋ก ๋ง๋ค์ด์ผ ์คํ ๊ฐ๋ฅ
* - ์ฑ๊ณต ์ ๋ฐํ ์ ์ ์ ๊ฑฐ + ๋ฆฌ์ ๋ธ 1 ๊ฐ์
*/
const tx: Transaction = {
TransactionType: "MPTokenIssuanceDestroy",
Account: admin.address, // ๋ฐํ์(Admin)
MPTokenIssuanceID: ISSUANCE_ID // ์ญ์ ํ ๋ฐํ๋ณธ ID
}
try {
// ํธ๋์ญ์
์ค๋น โ ์๋ช
โ ์ ์ถ
const prepared = await client.autofill(tx)
const signed = admin.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) {
destroyIssuance().catch(e => { console.error(e); process.exit(1) })
}
TypeScript
๋ณต์ฌ

