mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-29 15:37:48 +00:00
359 lines
12 KiB
JavaScript
359 lines
12 KiB
JavaScript
// This example demonstrates how to create escrows that hold fungible tokens.
|
|
// It covers MPTs and Trust Line Tokens, and uses conditional and timed escrows.
|
|
|
|
import xrpl from 'xrpl'
|
|
import { PreimageSha256 } from 'five-bells-condition'
|
|
import { randomBytes } from 'crypto'
|
|
|
|
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
|
|
await client.connect()
|
|
|
|
// Fund an issuer account and an escrow creator account ----------------------
|
|
console.log(`\n=== Funding Accounts ===\n`)
|
|
const [
|
|
{ wallet: issuer },
|
|
{ wallet: creator }
|
|
] = await Promise.all([
|
|
client.fundWallet(),
|
|
client.fundWallet()
|
|
])
|
|
console.log(`Issuer: ${issuer.address}`)
|
|
console.log(`Escrow Creator: ${creator.address}`)
|
|
|
|
// ====== Conditional MPT Escrow ======
|
|
|
|
// Issuer creates an MPT ----------------------
|
|
console.log('\n=== Creating MPT ===\n')
|
|
const mptCreateTx = {
|
|
TransactionType: 'MPTokenIssuanceCreate',
|
|
Account: issuer.address,
|
|
MaximumAmount: '1000000',
|
|
Flags: xrpl.MPTokenIssuanceCreateFlags.tfMPTCanEscrow
|
|
}
|
|
|
|
// Validate the transaction structure before submitting
|
|
xrpl.validate(mptCreateTx)
|
|
console.log(JSON.stringify(mptCreateTx, null, 2))
|
|
|
|
// Submit, sign, and wait for validation
|
|
console.log(`\nSubmitting MPTokenIssuanceCreate transaction...`)
|
|
const mptCreateResponse = await client.submitAndWait(mptCreateTx, {
|
|
wallet: issuer,
|
|
autofill: true
|
|
})
|
|
if (mptCreateResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`MPTokenIssuanceCreate failed: ${mptCreateResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
|
|
// Extract the MPT issuance ID from the transaction result
|
|
const mptIssuanceId = mptCreateResponse.result.meta.mpt_issuance_id
|
|
console.log(`MPT created: ${mptIssuanceId}`)
|
|
|
|
// Escrow Creator authorizes the MPT ----------------------
|
|
console.log('\n=== Escrow Creator Authorizing MPT ===\n')
|
|
const mptAuthTx = {
|
|
TransactionType: 'MPTokenAuthorize',
|
|
Account: creator.address,
|
|
MPTokenIssuanceID: mptIssuanceId
|
|
}
|
|
|
|
xrpl.validate(mptAuthTx)
|
|
console.log(JSON.stringify(mptAuthTx, null, 2))
|
|
|
|
console.log(`\nSubmitting MPTokenAuthorize transaction...`)
|
|
const mptAuthResponse = await client.submitAndWait(mptAuthTx, {
|
|
wallet: creator,
|
|
autofill: true
|
|
})
|
|
if (mptAuthResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`MPTokenAuthorize failed: ${mptAuthResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
console.log('Escrow Creator authorized for MPT.')
|
|
|
|
// Issuer sends MPTs to escrow creator ----------------------
|
|
console.log('\n=== Issuer Sending MPTs to Escrow Creator ===\n')
|
|
const mptPaymentTx = {
|
|
TransactionType: 'Payment',
|
|
Account: issuer.address,
|
|
Destination: creator.address,
|
|
Amount: {
|
|
mpt_issuance_id: mptIssuanceId,
|
|
value: '5000'
|
|
}
|
|
}
|
|
|
|
xrpl.validate(mptPaymentTx)
|
|
console.log(JSON.stringify(mptPaymentTx, null, 2))
|
|
|
|
console.log(`\nSubmitting MPT Payment transaction...`)
|
|
const mptPaymentResponse = await client.submitAndWait(mptPaymentTx, {
|
|
wallet: issuer,
|
|
autofill: true
|
|
})
|
|
if (mptPaymentResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`MPT Payment failed: ${mptPaymentResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
console.log('Successfully sent 5000 MPTs to Escrow Creator.')
|
|
|
|
// Escrow Creator creates a conditional MPT escrow ----------------------
|
|
console.log('\n=== Creating Conditional MPT Escrow ===\n')
|
|
|
|
// Generate crypto-condition
|
|
const preimage = randomBytes(32)
|
|
const fulfillment = new PreimageSha256()
|
|
fulfillment.setPreimage(preimage)
|
|
const fulfillmentHex = fulfillment.serializeBinary().toString('hex').toUpperCase()
|
|
const conditionHex = fulfillment.getConditionBinary().toString('hex').toUpperCase()
|
|
console.log(`Condition: ${conditionHex}`)
|
|
console.log(`Fulfillment: ${fulfillmentHex}\n`)
|
|
|
|
// Set expiration (300 seconds from now)
|
|
const cancelAfter = new Date()
|
|
cancelAfter.setSeconds(cancelAfter.getSeconds() + 300)
|
|
const cancelAfterRippleTime = xrpl.isoTimeToRippleTime(cancelAfter.toISOString())
|
|
|
|
const mptEscrowCreateTx = {
|
|
TransactionType: 'EscrowCreate',
|
|
Account: creator.address,
|
|
Destination: issuer.address,
|
|
Amount: {
|
|
mpt_issuance_id: mptIssuanceId,
|
|
value: '1000'
|
|
},
|
|
Condition: conditionHex,
|
|
CancelAfter: cancelAfterRippleTime // Fungible token escrows require a CancelAfter time
|
|
}
|
|
|
|
xrpl.validate(mptEscrowCreateTx)
|
|
console.log(JSON.stringify(mptEscrowCreateTx, null, 2))
|
|
|
|
console.log(`\nSubmitting MPT EscrowCreate transaction...`)
|
|
const mptEscrowResponse = await client.submitAndWait(mptEscrowCreateTx, {
|
|
wallet: creator,
|
|
autofill: true
|
|
})
|
|
if (mptEscrowResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`MPT EscrowCreate failed: ${mptEscrowResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
|
|
// Save the sequence number to identify the escrow later
|
|
const mptEscrowSeq = mptEscrowResponse.result.tx_json.Sequence
|
|
console.log(`Conditional MPT escrow created. Sequence: ${mptEscrowSeq}`)
|
|
|
|
// Finish the conditional MPT escrow with the fulfillment ----------------------
|
|
console.log('\n=== Finishing Conditional MPT Escrow ===\n')
|
|
const mptEscrowFinishTx = {
|
|
TransactionType: 'EscrowFinish',
|
|
Account: creator.address,
|
|
Owner: creator.address,
|
|
OfferSequence: mptEscrowSeq,
|
|
Condition: conditionHex,
|
|
Fulfillment: fulfillmentHex
|
|
}
|
|
|
|
xrpl.validate(mptEscrowFinishTx)
|
|
console.log(JSON.stringify(mptEscrowFinishTx, null, 2))
|
|
|
|
console.log(`\nSubmitting EscrowFinish transaction...`)
|
|
const mptFinishResponse = await client.submitAndWait(mptEscrowFinishTx, {
|
|
wallet: creator,
|
|
autofill: true
|
|
})
|
|
if (mptFinishResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`MPT EscrowFinish failed: ${mptFinishResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
console.log(`Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/${mptFinishResponse.result.hash}`)
|
|
|
|
// ====== Timed Trust Line Token Escrow ======
|
|
|
|
// Enable trust line token escrows on the issuer ----------------------
|
|
console.log('\n=== Enabling Trust Line Token Escrows on Issuer ===\n')
|
|
const accountSetTx = {
|
|
TransactionType: 'AccountSet',
|
|
Account: issuer.address,
|
|
SetFlag: xrpl.AccountSetAsfFlags.asfAllowTrustLineLocking
|
|
}
|
|
|
|
xrpl.validate(accountSetTx)
|
|
console.log(JSON.stringify(accountSetTx, null, 2))
|
|
|
|
console.log(`\nSubmitting AccountSet transaction...`)
|
|
const accountSetResponse = await client.submitAndWait(accountSetTx, {
|
|
wallet: issuer,
|
|
autofill: true
|
|
})
|
|
if (accountSetResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`AccountSet failed: ${accountSetResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
console.log('Trust line token escrows enabled by issuer.')
|
|
|
|
// Escrow Creator sets up a trust line to the issuer ----------------------
|
|
console.log('\n=== Setting Up Trust Line ===\n')
|
|
const currencyCode = 'IOU'
|
|
|
|
const trustSetTx = {
|
|
TransactionType: 'TrustSet',
|
|
Account: creator.address,
|
|
LimitAmount: {
|
|
currency: currencyCode,
|
|
issuer: issuer.address,
|
|
value: '10000000'
|
|
}
|
|
}
|
|
|
|
xrpl.validate(trustSetTx)
|
|
console.log(JSON.stringify(trustSetTx, null, 2))
|
|
|
|
console.log(`\nSubmitting TrustSet transaction...`)
|
|
const trustResponse = await client.submitAndWait(trustSetTx, {
|
|
wallet: creator,
|
|
autofill: true
|
|
})
|
|
if (trustResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`TrustSet failed: ${trustResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
console.log(`Trust line successfully created for "${currencyCode}" tokens.`)
|
|
|
|
// Issuer sends IOU tokens to creator ----------------------
|
|
console.log('\n=== Issuer Sending IOU Tokens to Escrow Creator ===\n')
|
|
const iouPaymentTx = {
|
|
TransactionType: 'Payment',
|
|
Account: issuer.address,
|
|
Destination: creator.address,
|
|
Amount: {
|
|
currency: currencyCode,
|
|
value: '5000',
|
|
issuer: issuer.address
|
|
}
|
|
}
|
|
|
|
xrpl.validate(iouPaymentTx)
|
|
console.log(JSON.stringify(iouPaymentTx, null, 2))
|
|
|
|
console.log(`\nSubmitting Trust Line Token payment transaction...`)
|
|
const iouPayResponse = await client.submitAndWait(iouPaymentTx, {
|
|
wallet: issuer,
|
|
autofill: true
|
|
})
|
|
if (iouPayResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`Trust Line Token payment failed: ${iouPayResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
console.log(`Successfully sent 5000 ${currencyCode} tokens.`)
|
|
|
|
// Escrow Creator creates a timed trust line token escrow ----------------------
|
|
console.log('\n=== Creating Timed Trust Line Token Escrow ===\n')
|
|
const delay = 10 // seconds
|
|
const now = new Date()
|
|
const finishAfter = new Date(now.getTime() + delay * 1000)
|
|
const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString())
|
|
console.log(`Escrow will mature after: ${finishAfter.toLocaleString()}\n`)
|
|
|
|
const iouCancelAfter = new Date(now.getTime() + 300 * 1000)
|
|
const iouCancelAfterRippleTime = xrpl.isoTimeToRippleTime(iouCancelAfter.toISOString())
|
|
|
|
const iouEscrowCreateTx = {
|
|
TransactionType: 'EscrowCreate',
|
|
Account: creator.address,
|
|
Destination: issuer.address,
|
|
Amount: {
|
|
currency: currencyCode,
|
|
value: '1000',
|
|
issuer: issuer.address
|
|
},
|
|
FinishAfter: finishAfterRippleTime,
|
|
CancelAfter: iouCancelAfterRippleTime
|
|
}
|
|
|
|
xrpl.validate(iouEscrowCreateTx)
|
|
console.log(JSON.stringify(iouEscrowCreateTx, null, 2))
|
|
|
|
console.log(`\nSubmitting Trust Line Token EscrowCreate transaction...`)
|
|
const iouEscrowResponse = await client.submitAndWait(iouEscrowCreateTx, {
|
|
wallet: creator,
|
|
autofill: true
|
|
})
|
|
if (iouEscrowResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`Trust Line Token EscrowCreate failed: ${iouEscrowResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
|
|
// Save the sequence number to identify the escrow later
|
|
const iouEscrowSeq = iouEscrowResponse.result.tx_json.Sequence
|
|
console.log(`Trust Line Token escrow created. Sequence: ${iouEscrowSeq}`)
|
|
|
|
// Wait for the escrow to mature, then finish it --------------------
|
|
console.log(`\n=== Waiting For Timed Trust Line Token Escrow to Mature ===\n`)
|
|
|
|
// Sleep function to countdown delay until escrow matures
|
|
function sleep (delayInSeconds) {
|
|
return new Promise((resolve) => setTimeout(resolve, delayInSeconds * 1000))
|
|
}
|
|
for (let i = delay; i >= 0; i--) {
|
|
process.stdout.write(`\rWaiting for escrow to mature... ${i}s remaining...`)
|
|
await sleep(1)
|
|
}
|
|
console.log('\rWaiting for escrow to mature... done. ')
|
|
|
|
// Confirm latest validated ledger close time is after the FinishAfter time
|
|
let escrowReady = false
|
|
while (!escrowReady) {
|
|
const validatedLedger = await client.request({
|
|
command: 'ledger',
|
|
ledger_index: 'validated'
|
|
})
|
|
const ledgerCloseTime = validatedLedger.result.ledger.close_time
|
|
console.log(`Latest validated ledger closed at: ${new Date(xrpl.rippleTimeToISOTime(ledgerCloseTime)).toLocaleString()}`)
|
|
if (ledgerCloseTime > finishAfterRippleTime) {
|
|
escrowReady = true
|
|
console.log('Escrow confirmed ready to finish.')
|
|
} else {
|
|
let timeDifference = finishAfterRippleTime - ledgerCloseTime
|
|
if (timeDifference === 0) { timeDifference = 1 }
|
|
console.log(`Escrow needs to wait another ${timeDifference}s.`)
|
|
await sleep(timeDifference)
|
|
}
|
|
}
|
|
|
|
// Finish the timed trust line token escrow --------------------
|
|
console.log('\n=== Finishing Timed Trust Line Token Escrow ===\n')
|
|
const iouEscrowFinishTx = {
|
|
TransactionType: 'EscrowFinish',
|
|
Account: creator.address,
|
|
Owner: creator.address,
|
|
OfferSequence: iouEscrowSeq
|
|
}
|
|
|
|
xrpl.validate(iouEscrowFinishTx)
|
|
console.log(JSON.stringify(iouEscrowFinishTx, null, 2))
|
|
|
|
console.log(`\nSubmitting EscrowFinish transaction...`)
|
|
const iouFinishResponse = await client.submitAndWait(iouEscrowFinishTx, {
|
|
wallet: creator,
|
|
autofill: true
|
|
})
|
|
if (iouFinishResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
|
console.error(`Trust Line Token EscrowFinish failed: ${iouFinishResponse.result.meta.TransactionResult}`)
|
|
await client.disconnect()
|
|
process.exit(1)
|
|
}
|
|
console.log(`Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/${iouFinishResponse.result.hash}`)
|
|
|
|
await client.disconnect()
|