From 242a6cc31b5fd8b937bb094375f7faddd403b5fb Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Fri, 15 Aug 2025 16:30:42 -0700 Subject: [PATCH 1/6] Show two possible formats of Escrow sample code --- _code-samples/escrow/js/create-escrow.js | 69 --------- _code-samples/escrow/js/package.json | 7 +- .../escrow/js/send-timed-escrow-functions.js | 146 ++++++++++++++++++ .../escrow/js/send-timed-escrow-linear.js | 92 +++++++++++ 4 files changed, 242 insertions(+), 72 deletions(-) delete mode 100644 _code-samples/escrow/js/create-escrow.js create mode 100644 _code-samples/escrow/js/send-timed-escrow-functions.js create mode 100644 _code-samples/escrow/js/send-timed-escrow-linear.js diff --git a/_code-samples/escrow/js/create-escrow.js b/_code-samples/escrow/js/create-escrow.js deleted file mode 100644 index 6d557475b69..00000000000 --- a/_code-samples/escrow/js/create-escrow.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict' -const xrpl = require('xrpl'); -const cc = require('five-bells-condition'); -const crypto = require('crypto'); - -// Useful Documentation:- -// 1. five-bells-condition: https://www.npmjs.com/package/five-bells-condition -// 2. Crypto module: https://nodejs.org/api/crypto.html - -// Your seed value, for testing purposes you can make one with the faucet: -// https://xrpl.org/resources/dev-tools/xrp-faucets -const seed = "sEd7jfWyNG6J71dEojB3W9YdHp2KCjy"; - -async function main() { - try { - - // Connect ---------------------------------------------------------------- - const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233'); - await client.connect(); - - // Prepare wallet to sign the transaction --------------------------------- - const wallet = await xrpl.Wallet.fromSeed(seed); - console.log("Wallet Address: ", wallet.address); - console.log("Seed: ", seed); - - // Set the escrow finish time --------------------------------------------- - let finishAfter = new Date((new Date().getTime() / 1000) + 120); // 2 minutes from now - finishAfter = new Date(finishAfter * 1000); - console.log("This escrow will finish after: ", finishAfter); - - // Construct condition and fulfillment ------------------------------------ - const preimageData = crypto.randomBytes(32); - const myFulfillment = new cc.PreimageSha256(); - myFulfillment.setPreimage(preimageData); - const conditionHex = myFulfillment.getConditionBinary().toString('hex').toUpperCase(); - - console.log('Condition:', conditionHex); - console.log('Fulfillment:', myFulfillment.serializeBinary().toString('hex').toUpperCase()); - - // Prepare EscrowCreate transaction ------------------------------------ - const escrowCreateTransaction = { - "TransactionType": "EscrowCreate", - "Account": wallet.address, - "Destination": wallet.address, - "Amount": "6000000", //drops XRP - "DestinationTag": 2023, - "Condition": conditionHex, // Omit this for time-held escrows - "Fee": "12", - "FinishAfter": xrpl.isoTimeToRippleTime(finishAfter.toISOString()), - }; - - xrpl.validate(escrowCreateTransaction); - - // Sign and submit the transaction ---------------------------------------- - console.log('Signing and submitting the transaction:', - JSON.stringify(escrowCreateTransaction, null, "\t"), "\n" - ); - const response = await client.submitAndWait(escrowCreateTransaction, { wallet }); - console.log(`Sequence number: ${response.result.tx_json.Sequence}`); - console.log(`Finished submitting! ${JSON.stringify(response.result, null, "\t")}`); - - await client.disconnect(); - - } catch (error) { - console.log(error); - } -} - -main() diff --git a/_code-samples/escrow/js/package.json b/_code-samples/escrow/js/package.json index f10d9adf373..7b7cf3a9472 100644 --- a/_code-samples/escrow/js/package.json +++ b/_code-samples/escrow/js/package.json @@ -1,9 +1,10 @@ { "name": "escrow-examples", - "version": "0.0.3", + "version": "2.0.0", "license": "MIT", "dependencies": { "five-bells-condition": "*", - "xrpl": "^4.0.0" - } + "xrpl": "^4.4.0" + }, + "type": "module" } diff --git a/_code-samples/escrow/js/send-timed-escrow-functions.js b/_code-samples/escrow/js/send-timed-escrow-functions.js new file mode 100644 index 00000000000..980c53f2005 --- /dev/null +++ b/_code-samples/escrow/js/send-timed-escrow-functions.js @@ -0,0 +1,146 @@ +import xrpl from 'xrpl' + +/* Sleep function that can be used with await */ +function sleep (delayInSeconds) { + const delayInMs = delayInSeconds * 1000 + return new Promise((resolve) => setTimeout(resolve, delayInMs)) +} + +/* Main function when called as a commandline script */ +async function main () { + const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233') + await client.connect() + + console.log('Funding new wallet from faucet...') + const { wallet } = await client.fundWallet() + const dest_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet + const delay = 30 + + const { escrowSeq, finishAfterRippleTime } = await send_timed_escrow( + client, + wallet, + dest_address, + delay + ) + + await wait_for_escrow(client, finishAfterRippleTime) + + await finish_escrow(client, wallet, escrowSeq) + + client.disconnect() +} + +/* + * Create a time-based escrow. + * Parameters: + * client (xrpl.Client): network-connected client + * wallet (xrpl.Wallet): sender wallet + * dest_address (string): receiver address in base58 + * delay (int): number of seconds until the escrow is mature + * Returns: object with the following keys + * response (xrpl.TxResponse): transaction result from submitAndWait + * escrowSeq (int): sequence number of the created escrow (int) + * finishAfterRippleTime (int): the FinishAfter time of the created escrow, + * in seconds since the Ripple Epoch + */ +async function send_timed_escrow (client, wallet, dest_address, delay) { + // Set the escrow finish time ----------------------------------------------- + const finishAfter = new Date() + finishAfter.setSeconds(finishAfter.getSeconds() + delay) + console.log('This escrow will finish after:', finishAfter) + // Convert finishAfter to seconds since the Ripple Epoch: + const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString()) + + // Send EscrowCreate transaction -------------------------------------------- + const escrowCreate = { + TransactionType: 'EscrowCreate', + Account: wallet.address, + Destination: dest_address, + Amount: '12345', // drops of XRP + FinishAfter: finishAfterRippleTime + } + xrpl.validate(escrowCreate) + + console.log('Signing and submitting the transaction:', + JSON.stringify(escrowCreate, null, 2)) + const response = await client.submitAndWait(escrowCreate, { + wallet, + autofill: true + }) + console.log(JSON.stringify(response.result, null, 2)) + const escrowSeq = response.result.tx_json.Sequence + console.log(`Escrow sequence is ${escrowSeq}.`) + return { + response, + escrowSeq, + finishAfterRippleTime + } +} + +/* + * Check the ledger close time to see if an escrow can be finished. + * If it's not ready yet, wait a number of seconds equal to the difference + * from the latest ledger close time to the escrow's FinishAfter time. + * Parameters: + * client (xrpl.Client): network-connected client + * finishAfterRippleTime (int): the FinishAfter time of the escrow, + * in seconds since the Ripple Epoch + * Returns: null + */ +async function wait_for_escrow (client, finishAfterRippleTime) { + // Check if escrow can be finished ------------------------------------------- + let escrowReady = false + while (!escrowReady) { + // Check the close time of the latest validated ledger. + // Close times are rounded by about 10 seconds, so the exact time the escrow + // is ready to finish may vary by +/- 10 seconds. + const validatedLedger = await client.request({ + command: 'ledger', + ledger_index: 'validated' + }) + const ledgerCloseTime = validatedLedger.result.ledger.close_time + console.log('Latest validated ledger closed at', + xrpl.rippleTimeToISOTime(ledgerCloseTime)) + if (ledgerCloseTime > finishAfterRippleTime) { + escrowReady = true + console.log('Escrow is ready to be finished.') + } else { + let timeDifference = finishAfterRippleTime - ledgerCloseTime + if (timeDifference === 0) { timeDifference = 1 } + console.log(`Waiting another ${timeDifference} second(s).`) + await sleep(timeDifference) + } + } +} + +/* + * Finish an escrow that your account owns. + * Parameters: + * client (xrpl.Client): network-connected client + * wallet (xrpl.Wallet): escrow owner and transaction sender's wallet + * escrowSeq (int): the Sequence number of the escrow to finish + * Returns: null + */ +async function finish_escrow (client, wallet, escrowSeq) { + // Send EscrowFinish transaction -------------------------------------------- + const escrowFinish = { + TransactionType: 'EscrowFinish', + Account: wallet.address, + Owner: wallet.address, + OfferSequence: escrowSeq + } + xrpl.validate(escrowFinish) + + console.log('Signing and submitting the transaction:', + JSON.stringify(escrowFinish, null, 2)) + const response2 = await client.submitAndWait(escrowFinish, { + wallet, + autofill: true + }) + console.log(JSON.stringify(response2.result, null, 2)) + if (response2.result.meta.TransactionResult === 'tesSUCCESS') { + console.log('Escrow finished successfully.') + } +} + +main() diff --git a/_code-samples/escrow/js/send-timed-escrow-linear.js b/_code-samples/escrow/js/send-timed-escrow-linear.js new file mode 100644 index 00000000000..c8079fd37ce --- /dev/null +++ b/_code-samples/escrow/js/send-timed-escrow-linear.js @@ -0,0 +1,92 @@ +import xrpl from 'xrpl' + +/* Sleep function that can be used with await */ +function sleep (delayInSeconds) { + const delayInMs = delayInSeconds * 1000 + return new Promise((resolve) => setTimeout(resolve, delayInMs)) +} + +const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233') +await client.connect() + +console.log('Funding new wallet from faucet...') +const { wallet } = await client.fundWallet() +const dest_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet +const delay = 30 + +// Set the escrow finish time ----------------------------------------------- +const finishAfter = new Date() +finishAfter.setSeconds(finishAfter.getSeconds() + delay) +console.log('This escrow will finish after:', finishAfter) +// Convert finishAfter to seconds since the Ripple Epoch: +const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString()) + +// Send EscrowCreate transaction -------------------------------------------- +const escrowCreate = { + TransactionType: 'EscrowCreate', + Account: wallet.address, + Destination: dest_address, + Amount: '12345', // drops of XRP + FinishAfter: finishAfterRippleTime +} +xrpl.validate(escrowCreate) + +console.log('Signing and submitting the transaction:', + JSON.stringify(escrowCreate, null, 2)) +const response = await client.submitAndWait(escrowCreate, { + wallet, + autofill: true +}) +console.log(JSON.stringify(response.result, null, 2)) +const escrowSeq = response.result.tx_json.Sequence +console.log(`Escrow sequence is ${escrowSeq}.`) + +// Wait for the escrow to be finishable ------------------------------------- +console.log(`Waiting ${delay} seconds for the escrow to mature...`) +await sleep(delay) + +// Check if escrow can be finished ------------------------------------------- +let escrowReady = false +while (!escrowReady) { + // Check the close time of the latest validated ledger. + // Close times are rounded by about 10 seconds, so the exact time the escrow + // is ready to finish may vary by +/- 10 seconds. + const validatedLedger = await client.request({ + command: 'ledger', + ledger_index: 'validated' + }) + const ledgerCloseTime = validatedLedger.result.ledger.close_time + console.log('Latest validated ledger closed at', + xrpl.rippleTimeToISOTime(ledgerCloseTime)) + if (ledgerCloseTime > finishAfterRippleTime) { + escrowReady = true + console.log('Escrow is ready to be finished.') + } else { + let timeDifference = finishAfterRippleTime - ledgerCloseTime + if (timeDifference === 0) { timeDifference = 1 } + console.log(`Waiting another ${timeDifference} second(s).`) + await sleep(timeDifference) + } +} + +// Send EscrowFinish transaction -------------------------------------------- +const escrowFinish = { + TransactionType: 'EscrowFinish', + Account: wallet.address, + Owner: wallet.address, + OfferSequence: escrowSeq +} +xrpl.validate(escrowFinish) + +console.log('Signing and submitting the transaction:', + JSON.stringify(escrowFinish, null, 2)) +const response2 = await client.submitAndWait(escrowFinish, { + wallet, + autofill: true +}) +console.log(JSON.stringify(response2.result, null, 2)) +if (response2.result.meta.TransactionResult === 'tesSUCCESS') { + console.log('Escrow finished successfully.') +} + +client.disconnect() From 8c25a5ea3397acb985226ad32b45a51511aeb79b Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 17 Sep 2025 18:19:53 -0700 Subject: [PATCH 2/6] Start implementing tutorial for updated timed escrow sample --- .../escrow/js/send-timed-escrow-linear.js | 92 ------------------- ...crow-functions.js => send-timed-escrow.js} | 30 +++--- ...-held-escrow.md => send-a-timed-escrow.md} | 56 +++++++++-- 3 files changed, 68 insertions(+), 110 deletions(-) delete mode 100644 _code-samples/escrow/js/send-timed-escrow-linear.js rename _code-samples/escrow/js/{send-timed-escrow-functions.js => send-timed-escrow.js} (93%) rename docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/{send-a-time-held-escrow.md => send-a-timed-escrow.md} (81%) diff --git a/_code-samples/escrow/js/send-timed-escrow-linear.js b/_code-samples/escrow/js/send-timed-escrow-linear.js deleted file mode 100644 index c8079fd37ce..00000000000 --- a/_code-samples/escrow/js/send-timed-escrow-linear.js +++ /dev/null @@ -1,92 +0,0 @@ -import xrpl from 'xrpl' - -/* Sleep function that can be used with await */ -function sleep (delayInSeconds) { - const delayInMs = delayInSeconds * 1000 - return new Promise((resolve) => setTimeout(resolve, delayInMs)) -} - -const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233') -await client.connect() - -console.log('Funding new wallet from faucet...') -const { wallet } = await client.fundWallet() -const dest_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet -const delay = 30 - -// Set the escrow finish time ----------------------------------------------- -const finishAfter = new Date() -finishAfter.setSeconds(finishAfter.getSeconds() + delay) -console.log('This escrow will finish after:', finishAfter) -// Convert finishAfter to seconds since the Ripple Epoch: -const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString()) - -// Send EscrowCreate transaction -------------------------------------------- -const escrowCreate = { - TransactionType: 'EscrowCreate', - Account: wallet.address, - Destination: dest_address, - Amount: '12345', // drops of XRP - FinishAfter: finishAfterRippleTime -} -xrpl.validate(escrowCreate) - -console.log('Signing and submitting the transaction:', - JSON.stringify(escrowCreate, null, 2)) -const response = await client.submitAndWait(escrowCreate, { - wallet, - autofill: true -}) -console.log(JSON.stringify(response.result, null, 2)) -const escrowSeq = response.result.tx_json.Sequence -console.log(`Escrow sequence is ${escrowSeq}.`) - -// Wait for the escrow to be finishable ------------------------------------- -console.log(`Waiting ${delay} seconds for the escrow to mature...`) -await sleep(delay) - -// Check if escrow can be finished ------------------------------------------- -let escrowReady = false -while (!escrowReady) { - // Check the close time of the latest validated ledger. - // Close times are rounded by about 10 seconds, so the exact time the escrow - // is ready to finish may vary by +/- 10 seconds. - const validatedLedger = await client.request({ - command: 'ledger', - ledger_index: 'validated' - }) - const ledgerCloseTime = validatedLedger.result.ledger.close_time - console.log('Latest validated ledger closed at', - xrpl.rippleTimeToISOTime(ledgerCloseTime)) - if (ledgerCloseTime > finishAfterRippleTime) { - escrowReady = true - console.log('Escrow is ready to be finished.') - } else { - let timeDifference = finishAfterRippleTime - ledgerCloseTime - if (timeDifference === 0) { timeDifference = 1 } - console.log(`Waiting another ${timeDifference} second(s).`) - await sleep(timeDifference) - } -} - -// Send EscrowFinish transaction -------------------------------------------- -const escrowFinish = { - TransactionType: 'EscrowFinish', - Account: wallet.address, - Owner: wallet.address, - OfferSequence: escrowSeq -} -xrpl.validate(escrowFinish) - -console.log('Signing and submitting the transaction:', - JSON.stringify(escrowFinish, null, 2)) -const response2 = await client.submitAndWait(escrowFinish, { - wallet, - autofill: true -}) -console.log(JSON.stringify(response2.result, null, 2)) -if (response2.result.meta.TransactionResult === 'tesSUCCESS') { - console.log('Escrow finished successfully.') -} - -client.disconnect() diff --git a/_code-samples/escrow/js/send-timed-escrow-functions.js b/_code-samples/escrow/js/send-timed-escrow.js similarity index 93% rename from _code-samples/escrow/js/send-timed-escrow-functions.js rename to _code-samples/escrow/js/send-timed-escrow.js index 980c53f2005..dc302d9d002 100644 --- a/_code-samples/escrow/js/send-timed-escrow-functions.js +++ b/_code-samples/escrow/js/send-timed-escrow.js @@ -1,11 +1,5 @@ import xrpl from 'xrpl' -/* Sleep function that can be used with await */ -function sleep (delayInSeconds) { - const delayInMs = delayInSeconds * 1000 - return new Promise((resolve) => setTimeout(resolve, delayInMs)) -} - /* Main function when called as a commandline script */ async function main () { const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233') @@ -13,13 +7,17 @@ async function main () { console.log('Funding new wallet from faucet...') const { wallet } = await client.fundWallet() + + // Define properties of the escrow const dest_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet - const delay = 30 + const delay = 30 // how long to escrow the funds, in seconds + const amount = '12345' // drops of XRP to send in the escrow const { escrowSeq, finishAfterRippleTime } = await send_timed_escrow( client, wallet, dest_address, + amount, delay ) @@ -30,12 +28,13 @@ async function main () { client.disconnect() } -/* +/* send_timed_escrow * Create a time-based escrow. * Parameters: * client (xrpl.Client): network-connected client * wallet (xrpl.Wallet): sender wallet * dest_address (string): receiver address in base58 + * amount (string): how many drops of XRP to send in escrow * delay (int): number of seconds until the escrow is mature * Returns: object with the following keys * response (xrpl.TxResponse): transaction result from submitAndWait @@ -43,7 +42,7 @@ async function main () { * finishAfterRippleTime (int): the FinishAfter time of the created escrow, * in seconds since the Ripple Epoch */ -async function send_timed_escrow (client, wallet, dest_address, delay) { +async function send_timed_escrow (client, wallet, dest_address, amount, delay) { // Set the escrow finish time ----------------------------------------------- const finishAfter = new Date() finishAfter.setSeconds(finishAfter.getSeconds() + delay) @@ -56,7 +55,7 @@ async function send_timed_escrow (client, wallet, dest_address, delay) { TransactionType: 'EscrowCreate', Account: wallet.address, Destination: dest_address, - Amount: '12345', // drops of XRP + Amount: amount, FinishAfter: finishAfterRippleTime } xrpl.validate(escrowCreate) @@ -77,7 +76,7 @@ async function send_timed_escrow (client, wallet, dest_address, delay) { } } -/* +/* wait_for_escrow * Check the ledger close time to see if an escrow can be finished. * If it's not ready yet, wait a number of seconds equal to the difference * from the latest ledger close time to the escrow's FinishAfter time. @@ -113,7 +112,13 @@ async function wait_for_escrow (client, finishAfterRippleTime) { } } -/* +/* Sleep function that can be used with await */ +function sleep (delayInSeconds) { + const delayInMs = delayInSeconds * 1000 + return new Promise((resolve) => setTimeout(resolve, delayInMs)) +} + +/* finish_escrow * Finish an escrow that your account owns. * Parameters: * client (xrpl.Client): network-connected client @@ -143,4 +148,5 @@ async function finish_escrow (client, wallet, escrowSeq) { } } +// Call main function so it runs as a script main() diff --git a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-time-held-escrow.md b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md similarity index 81% rename from docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-time-held-escrow.md rename to docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md index 3f82e9c66e9..f72d7bf4560 100644 --- a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-time-held-escrow.md +++ b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md @@ -1,15 +1,59 @@ --- -html: send-a-time-held-escrow.html -parent: use-escrows.html seo: - description: Create an escrow whose only condition for release is that a specific time has passed. + description: Send an escrow whose only condition for release is that a specific time has passed. labels: - Escrow - - Smart Contracts --- -# Send a Time-Held Escrow +# Send a Timed Escrow + +This tutorial demonstrates how to send an [escrow](../../../../concepts/payment-types/escrow.md) whose only condition for release is that a specific time has passed. You can use this to set aside money for yourself or others so that it absolutely cannot be used until the specified time. + +This tutorial shows how to escrow XRP. If the [TokenEscrow amendment][] is enabled, you can also escrow tokens. + +## Goals + +By following this tutorial, you should learn how to: + +- Convert a timestamp into the XRP Ledger's native format. +- Create and finish an escrow. + +## Prerequisites + +To complete this tutorial, you should: + +- Have a basic understanding of the XRP Ledger +- Have an XRP Ledger client library, such as **xrpl.js**, installed. + +## Source Code + +You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/escrow/send-timed-escrow.js" %}code samples section of this website's repository{% /repo-link %}. + +## Steps + +### 1. Install dependencies + +{% tabs %} +{% tab label="JavaScript" %} +From the code sample folder, use npm to install dependencies: + +```sh +npm i +``` +{% /tab %} +{% /tabs %} + +### 2. Import dependencies and get accounts + +After importing the XRPL client library, the tutorial code gets a new wallet from the testnet faucet and defines the properties of the escrow as hard-coded constants. You can use an existing wallet or modify the constants as desired. + + +{% tabs %} +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" before="/* send_timed_escrow" /%} +{% /tab %} +{% /tabs %} + -The [EscrowCreate transaction][] type can create an escrow whose only condition for release is that a specific time has passed. To do this, use the `FinishAfter` field and omit the `Condition` field. ## 1. Calculate release time From 892f9202c5a9cb53e064ca8c381020d253a568a8 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Fri, 10 Oct 2025 16:38:04 -0700 Subject: [PATCH 3/6] Finish updating timed escrow tutorial --- _code-samples/escrow/js/send-timed-escrow.js | 24 ++- .../use-escrows/send-a-timed-escrow.md | 185 +++--------------- sidebars.yaml | 2 +- 3 files changed, 47 insertions(+), 164 deletions(-) diff --git a/_code-samples/escrow/js/send-timed-escrow.js b/_code-samples/escrow/js/send-timed-escrow.js index dc302d9d002..640d5cdc20a 100644 --- a/_code-samples/escrow/js/send-timed-escrow.js +++ b/_code-samples/escrow/js/send-timed-escrow.js @@ -1,6 +1,7 @@ import xrpl from 'xrpl' -/* Main function when called as a commandline script */ +/* Main function when called as a commandline script ------------------------*/ +main() async function main () { const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233') await client.connect() @@ -43,14 +44,14 @@ async function main () { * in seconds since the Ripple Epoch */ async function send_timed_escrow (client, wallet, dest_address, amount, delay) { - // Set the escrow finish time ----------------------------------------------- + // Set the escrow finish time const finishAfter = new Date() finishAfter.setSeconds(finishAfter.getSeconds() + delay) console.log('This escrow will finish after:', finishAfter) // Convert finishAfter to seconds since the Ripple Epoch: const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString()) - // Send EscrowCreate transaction -------------------------------------------- + // Construct the EscrowCreate transaction const escrowCreate = { TransactionType: 'EscrowCreate', Account: wallet.address, @@ -60,12 +61,15 @@ async function send_timed_escrow (client, wallet, dest_address, amount, delay) { } xrpl.validate(escrowCreate) + // Send the transaction console.log('Signing and submitting the transaction:', JSON.stringify(escrowCreate, null, 2)) const response = await client.submitAndWait(escrowCreate, { wallet, autofill: true }) + + // Display the transaction results & return them console.log(JSON.stringify(response.result, null, 2)) const escrowSeq = response.result.tx_json.Sequence console.log(`Escrow sequence is ${escrowSeq}.`) @@ -76,7 +80,7 @@ async function send_timed_escrow (client, wallet, dest_address, amount, delay) { } } -/* wait_for_escrow +/* wait_for_escrow ------------------------------------------------------------ * Check the ledger close time to see if an escrow can be finished. * If it's not ready yet, wait a number of seconds equal to the difference * from the latest ledger close time to the escrow's FinishAfter time. @@ -87,7 +91,7 @@ async function send_timed_escrow (client, wallet, dest_address, amount, delay) { * Returns: null */ async function wait_for_escrow (client, finishAfterRippleTime) { - // Check if escrow can be finished ------------------------------------------- + // Check if escrow can be finished let escrowReady = false while (!escrowReady) { // Check the close time of the latest validated ledger. @@ -118,7 +122,7 @@ function sleep (delayInSeconds) { return new Promise((resolve) => setTimeout(resolve, delayInMs)) } -/* finish_escrow +/* finish_escrow -------------------------------------------------------------- * Finish an escrow that your account owns. * Parameters: * client (xrpl.Client): network-connected client @@ -127,7 +131,7 @@ function sleep (delayInSeconds) { * Returns: null */ async function finish_escrow (client, wallet, escrowSeq) { - // Send EscrowFinish transaction -------------------------------------------- + // Construct the EscrowFinish transaction const escrowFinish = { TransactionType: 'EscrowFinish', Account: wallet.address, @@ -136,17 +140,17 @@ async function finish_escrow (client, wallet, escrowSeq) { } xrpl.validate(escrowFinish) + // Send the transaction console.log('Signing and submitting the transaction:', JSON.stringify(escrowFinish, null, 2)) const response2 = await client.submitAndWait(escrowFinish, { wallet, autofill: true }) + + // Display the transaction results console.log(JSON.stringify(response2.result, null, 2)) if (response2.result.meta.TransactionResult === 'tesSUCCESS') { console.log('Escrow finished successfully.') } } - -// Call main function so it runs as a script -main() diff --git a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md index f72d7bf4560..c2071ce25c2 100644 --- a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md +++ b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md @@ -42,10 +42,14 @@ npm i {% /tab %} {% /tabs %} -### 2. Import dependencies and get accounts +### 2. Import dependencies and define main function -After importing the XRPL client library, the tutorial code gets a new wallet from the testnet faucet and defines the properties of the escrow as hard-coded constants. You can use an existing wallet or modify the constants as desired. +After importing the XRPL client library, the tutorial code defines the main function which controls the flow of the script. This function does several things: +1. Connect to the network and get a new wallet from the testnet faucet. +2. Define the properties of the escrow as hard-coded constants. +3. Use helper functions to create the escrow, wait for it to be ready, and then finish it. These functions are defined later in the file. +4. Disconnect from the network when done. {% tabs %} {% tab label="JavaScript" %} @@ -53,181 +57,56 @@ After importing the XRPL client library, the tutorial code gets a new wallet fro {% /tab %} {% /tabs %} +### 3. Create the escrow +Next, the `send_timed_escrow(...)` function implements the following: -## 1. Calculate release time - -You must specify the time as whole **[seconds since the Ripple Epoch][]**, which is 946684800 seconds after the UNIX epoch. For example, to release funds at midnight UTC on November 13, 2017: +1. Calculate the maturity time of the escrow (when it should be possible to finish it), and convert it to the correct format ([seconds since the Ripple Epoch][]). + {% admonition type="danger" name="Warning" %}If you use a UNIX time in the `FinishAfter` field without converting to the equivalent Ripple time first, that sets the unlock time to an extra **30 years** in the future!{% /admonition %} +2. Construct an [EscrowCreate transaction][]. + {% admonition type="info" name="Note" %}If you are sending a token escrow, you must also add an expiration time in the `CancelAfter` field, in the same time format. This time must be after the maturity time.{% /admonition %} +3. Submit the transaction to the network and wait for it to be validated by consensus. +4. Return the details of the escrow, particularly the autofilled sequence number. You need this sequence number to identify the escrow in later transactions. {% tabs %} - {% tab label="JavaScript" %} -```js -// JavaScript Date() is natively expressed in milliseconds; convert to seconds -const release_date_unix = Math.floor( new Date("2017-11-13T00:00:00Z") / 1000 ); -const release_date_ripple = release_date_unix - 946684800; -console.log(release_date_ripple); -// 563846400 -``` -{% /tab %} - -{% tab label="Python 3" %} -```python -import datetime -release_date_utc = datetime.datetime(2017,11,13,0,0,0,tzinfo=datetime.timezone.utc) -release_date_ripple = int(release_date_utc.timestamp()) - 946684800 -print(release_date_ripple) -# 563846400 -``` -{% /tab %} - -{% /tabs %} - -{% admonition type="danger" name="Warning" %}If you use a UNIX time in the `FinishAfter` field without converting to the equivalent Ripple time first, that sets the unlock time to an extra **30 years** in the future!{% /admonition %} - -## 2. Submit EscrowCreate transaction - -[Sign and submit](../../../../concepts/transactions/index.md#signing-and-submitting-transactions) an [EscrowCreate transaction][]. Set the `FinishAfter` field of the transaction to the time when the held payment should be released. Omit the `Condition` field to make time the only condition for releasing the held payment. Set the `Destination` to the recipient, which may be the same address as the sender. Set the `Amount` to the total amount of [XRP, in drops][], to escrow. - -{% partial file="/docs/_snippets/secret-key-warning.md" /%} - - -{% tabs %} - -{% tab label="Websocket" %} -Request: -{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowcreate-time.json" language="json" /%} -Response: -{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowcreate-time.json" language="json" /%} -{% /tab %} - -{% tab label="Javascript" %} -{% code-snippet file="/_code-samples/escrow/js/create-escrow.js" language="js" from="// Prepare EscrowCreate" before="await client.disconnect" /%} -{% /tab %} - -{% tab label="Python" %} -{% code-snippet file="/_code-samples/escrow/py/create_escrow.py" language="py" from="# Build escrow create" /%} -{% /tab %} - -{% /tabs %} - -Take note of the transaction's identifying `hash` value so you can check its final status when it is included in a validated ledger version. - -## 3. Wait for validation - -{% raw-partial file="/docs/_snippets/wait-for-validation.md" /%} - -## 4. Confirm that the escrow was created - -Use the [tx method][] with the transaction's identifying hash to check its final status. Look for a `CreatedNode` in the transaction metadata to indicate that it created an [Escrow ledger object](../../../../concepts/payment-types/escrow.md). - -Request: - -{% tabs %} - -{% tab label="Websocket" %} -{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowcreate-time.json" language="json" /%} -{% /tab %} - -{% /tabs %} - -Response: - -{% tabs %} - -{% tab label="Websocket" %} -{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowcreate-time.json" language="json" /%} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* send_timed_escrow" before="/* wait_for_escrow" /%} {% /tab %} - {% /tabs %} -## 5. Wait for the release time - -Held payments with a `FinishAfter` time cannot be finished until a ledger has already closed with a [`close_time` header field](../../../../references/protocol/ledger-data/ledger-header.md) that is later than the Escrow node's `FinishAfter` time. - -You can check the close time of the most recently-validated ledger with the [ledger method][]: - -Request: -{% tabs %} +### 4. Wait for the escrow -{% tab label="Websocket" %} -{% code-snippet file="/_api-examples/escrow/websocket/ledger-request.json" language="json" /%} -{% /tab %} +The `wait_for_escrow(...)` function implements the following: -{% /tabs %} - -Response: - -{% tabs %} - -{% tab label="Websocket" %} -{% code-snippet file="/_api-examples/escrow/websocket/ledger-response.json" language="json" /%} -{% /tab %} - -{% /tabs %} - - -## 6. Submit EscrowFinish transaction - -[Sign and submit](../../../../concepts/transactions/index.md#signing-and-submitting-transactions) an [EscrowFinish transaction][] to execute the release of the funds after the `FinishAfter` time has passed. Set the `Owner` field of the transaction to the `Account` address from the EscrowCreate transaction, and the `OfferSequence` to the `Sequence` number from the EscrowCreate transaction. For an escrow held only by time, omit the `Condition` and `Fulfillment` fields. - -{% admonition type="success" name="Tip" %} -The EscrowFinish transaction is necessary because the XRP Ledger's state can only be modified by transactions. The sender of this transaction may be the recipient of the escrow, the original sender of the escrow, or any other XRP Ledger address. -{% /admonition %} - -If the escrow has expired, you can only [cancel the escrow](cancel-an-expired-escrow.md) instead. - -{% partial file="/docs/_snippets/secret-key-warning.md" /%} +1. Check the official close time of the most recent validated ledger. +2. Wait a number of seconds based on the difference between that close time and the time when the escrow is ready to be finished. +3. Repeat until the escrow is ready. The actual, official close time of ledgers [is rounded](../../../../concepts/ledgers/ledger-close-times.md) by up to 10 seconds, so there is some variance in how long it actually takes for an escrow to be ready. {% tabs %} +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* wait_for_escrow" before="/* Sleep function" /%} -{% tab label="Websocket" %} -Request: -{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowfinish-time.json" language="json" /%} +Additionally, since JavaScript doesn't have a native `sleep(...)` function, the sample code implements one to be used with `await`, as a convenience: -Response: -{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowfinish-time.json" language="json" /%} -{% /tab %} - -{% tab label="Javascript" %} -{% code-snippet file="/_code-samples/escrow/js/finish-escrow.js" language="js" from="// Prepare EscrowFinish" before="await client.disconnect" /%} -{% /tab %} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* Sleep function" before="/* finish_escrow" /%} -{% tab label="Python" %} -{% code-snippet file="/_code-samples/escrow/py/finish_escrow.py" language="py" from="# Build escrow finish" /%} {% /tab %} - {% /tabs %} -Take note of the transaction's identifying `hash` value so you can check its final status when it is included in a validated ledger version. - -## 7. Wait for validation - -{% raw-partial file="/docs/_snippets/wait-for-validation.md" /%} +### 5. Finish the escrow -## 8. Confirm final result +The `finish_escrow(...)` function implements the following: -Use the [tx method][] with the EscrowFinish transaction's identifying hash to check its final status. In particular, look in the transaction metadata for a `ModifiedNode` of type `AccountRoot` for the destination of the escrowed payment. The `FinalFields` of the object should show the increase in XRP in the `Balance` field. - -Request: +1. Construct an [EscrowFinish transaction][], using the sequence number recorded when the escrow was created. + {% admonition type="success" name="Tip" %}Anyone can finish a timed escrow when it is ready. Regardless of who does so—the sender, receiver, or even a third party—the escrow delivers the funds to its intended recipient.{% /admonition %} +2. Submit the transaction to the network and wait for it to be validated by consensus. +3. Display the details of the validated transaction. {% tabs %} - -{% tab label="Websocket" %} -{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowfinish-time.json" language="json" /%} -{% /tab %} - -{% /tabs %} - -Response: - -{% tabs %} - -{% tab label="Websocket" %} -{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowfinish-time.json" language="json" /%} +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* finish_escrow" /%} {% /tab %} - {% /tabs %} diff --git a/sidebars.yaml b/sidebars.yaml index 7443fb81a06..bb492584222 100644 --- a/sidebars.yaml +++ b/sidebars.yaml @@ -299,7 +299,7 @@ - page: docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/index.md expanded: false items: - - page: docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-time-held-escrow.md + - page: docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md - page: docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditionally-held-escrow.md - page: docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/cancel-an-expired-escrow.md - page: docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/look-up-escrows.md From 8f301ed391a8131cf111017e4294086976105ec6 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 13 Oct 2025 14:23:07 -0700 Subject: [PATCH 4/6] rm reference to pre-refactor escrow code sample --- .../use-escrows/send-a-conditionally-held-escrow.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditionally-held-escrow.md b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditionally-held-escrow.md index f2dad19faa1..61cf19c16f0 100644 --- a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditionally-held-escrow.md +++ b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditionally-held-escrow.md @@ -106,16 +106,14 @@ Response: {% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowcreate-condition.json" language="json" /%} {% /tab %} -{% tab label="Javascript" %} -{% code-snippet file="/_code-samples/escrow/js/create-escrow.js" language="js" from="// Prepare EscrowCreate" before="await client.disconnect" /%} -{% /tab %} - {% tab label="Python" %} {% code-snippet file="/_code-samples/escrow/py/create_escrow.py" language="py" from="# Build escrow create" /%} {% /tab %} {% /tabs %} + + ## 4. Wait for validation {% raw-partial file="/docs/_snippets/wait-for-validation.md" /%} From 95196d3195cab7d18dd1e3857a57e2ea39e3f2b5 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Thu, 16 Oct 2025 13:38:13 -0700 Subject: [PATCH 5/6] Make tutorial for linear version of the timed escrow code --- _code-samples/escrow/js/send-timed-escrow.js | 211 ++++++------------ .../use-escrows/send-a-timed-escrow.md | 76 ++++--- 2 files changed, 118 insertions(+), 169 deletions(-) diff --git a/_code-samples/escrow/js/send-timed-escrow.js b/_code-samples/escrow/js/send-timed-escrow.js index 640d5cdc20a..5b0b98c8924 100644 --- a/_code-samples/escrow/js/send-timed-escrow.js +++ b/_code-samples/escrow/js/send-timed-escrow.js @@ -1,120 +1,44 @@ import xrpl from 'xrpl' -/* Main function when called as a commandline script ------------------------*/ -main() -async function main () { - const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233') - await client.connect() +const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233') +await client.connect() - console.log('Funding new wallet from faucet...') - const { wallet } = await client.fundWallet() +console.log('Funding new wallet from faucet...') +const { wallet } = await client.fundWallet() - // Define properties of the escrow - const dest_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet - const delay = 30 // how long to escrow the funds, in seconds - const amount = '12345' // drops of XRP to send in the escrow +// Set the escrow finish time ----------------------------------------------- +const delay = 30 // Seconds in the future when the escrow should mature +const finishAfter = new Date() // Current time +finishAfter.setSeconds(finishAfter.getSeconds() + delay) +console.log('This escrow will finish after:', finishAfter) +// Convert finishAfter to seconds since the Ripple Epoch: +const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString()) - const { escrowSeq, finishAfterRippleTime } = await send_timed_escrow( - client, - wallet, - dest_address, - amount, - delay - ) - - await wait_for_escrow(client, finishAfterRippleTime) - - await finish_escrow(client, wallet, escrowSeq) - - client.disconnect() +// Send EscrowCreate transaction -------------------------------------------- +const escrowCreate = { + TransactionType: 'EscrowCreate', + Account: wallet.address, + Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe', // Testnet faucet + Amount: '123456', // drops of XRP + FinishAfter: finishAfterRippleTime } +xrpl.validate(escrowCreate) -/* send_timed_escrow - * Create a time-based escrow. - * Parameters: - * client (xrpl.Client): network-connected client - * wallet (xrpl.Wallet): sender wallet - * dest_address (string): receiver address in base58 - * amount (string): how many drops of XRP to send in escrow - * delay (int): number of seconds until the escrow is mature - * Returns: object with the following keys - * response (xrpl.TxResponse): transaction result from submitAndWait - * escrowSeq (int): sequence number of the created escrow (int) - * finishAfterRippleTime (int): the FinishAfter time of the created escrow, - * in seconds since the Ripple Epoch - */ -async function send_timed_escrow (client, wallet, dest_address, amount, delay) { - // Set the escrow finish time - const finishAfter = new Date() - finishAfter.setSeconds(finishAfter.getSeconds() + delay) - console.log('This escrow will finish after:', finishAfter) - // Convert finishAfter to seconds since the Ripple Epoch: - const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString()) +console.log('Signing and submitting the transaction:', + JSON.stringify(escrowCreate, null, 2)) +const response = await client.submitAndWait(escrowCreate, { + wallet, + autofill: true +}) +console.log(JSON.stringify(response.result, null, 2)) - // Construct the EscrowCreate transaction - const escrowCreate = { - TransactionType: 'EscrowCreate', - Account: wallet.address, - Destination: dest_address, - Amount: amount, - FinishAfter: finishAfterRippleTime - } - xrpl.validate(escrowCreate) +// Save the sequence number so you can identify the escrow later. +const escrowSeq = response.result.tx_json.Sequence +console.log(`Escrow sequence is ${escrowSeq}.`) - // Send the transaction - console.log('Signing and submitting the transaction:', - JSON.stringify(escrowCreate, null, 2)) - const response = await client.submitAndWait(escrowCreate, { - wallet, - autofill: true - }) - - // Display the transaction results & return them - console.log(JSON.stringify(response.result, null, 2)) - const escrowSeq = response.result.tx_json.Sequence - console.log(`Escrow sequence is ${escrowSeq}.`) - return { - response, - escrowSeq, - finishAfterRippleTime - } -} - -/* wait_for_escrow ------------------------------------------------------------ - * Check the ledger close time to see if an escrow can be finished. - * If it's not ready yet, wait a number of seconds equal to the difference - * from the latest ledger close time to the escrow's FinishAfter time. - * Parameters: - * client (xrpl.Client): network-connected client - * finishAfterRippleTime (int): the FinishAfter time of the escrow, - * in seconds since the Ripple Epoch - * Returns: null - */ -async function wait_for_escrow (client, finishAfterRippleTime) { - // Check if escrow can be finished - let escrowReady = false - while (!escrowReady) { - // Check the close time of the latest validated ledger. - // Close times are rounded by about 10 seconds, so the exact time the escrow - // is ready to finish may vary by +/- 10 seconds. - const validatedLedger = await client.request({ - command: 'ledger', - ledger_index: 'validated' - }) - const ledgerCloseTime = validatedLedger.result.ledger.close_time - console.log('Latest validated ledger closed at', - xrpl.rippleTimeToISOTime(ledgerCloseTime)) - if (ledgerCloseTime > finishAfterRippleTime) { - escrowReady = true - console.log('Escrow is ready to be finished.') - } else { - let timeDifference = finishAfterRippleTime - ledgerCloseTime - if (timeDifference === 0) { timeDifference = 1 } - console.log(`Waiting another ${timeDifference} second(s).`) - await sleep(timeDifference) - } - } -} +// Wait for the escrow to be finishable ------------------------------------- +console.log(`Waiting ${delay} seconds for the escrow to mature...`) +await sleep(delay) /* Sleep function that can be used with await */ function sleep (delayInSeconds) { @@ -122,35 +46,48 @@ function sleep (delayInSeconds) { return new Promise((resolve) => setTimeout(resolve, delayInMs)) } -/* finish_escrow -------------------------------------------------------------- - * Finish an escrow that your account owns. - * Parameters: - * client (xrpl.Client): network-connected client - * wallet (xrpl.Wallet): escrow owner and transaction sender's wallet - * escrowSeq (int): the Sequence number of the escrow to finish - * Returns: null - */ -async function finish_escrow (client, wallet, escrowSeq) { - // Construct the EscrowFinish transaction - const escrowFinish = { - TransactionType: 'EscrowFinish', - Account: wallet.address, - Owner: wallet.address, - OfferSequence: escrowSeq - } - xrpl.validate(escrowFinish) - - // Send the transaction - console.log('Signing and submitting the transaction:', - JSON.stringify(escrowFinish, null, 2)) - const response2 = await client.submitAndWait(escrowFinish, { - wallet, - autofill: true +// Check if escrow can be finished ------------------------------------------- +let escrowReady = false +while (!escrowReady) { + // Check the close time of the latest validated ledger. + // Close times are rounded by about 10 seconds, so the exact time the escrow + // is ready to finish may vary by +/- 10 seconds. + const validatedLedger = await client.request({ + command: 'ledger', + ledger_index: 'validated' }) - - // Display the transaction results - console.log(JSON.stringify(response2.result, null, 2)) - if (response2.result.meta.TransactionResult === 'tesSUCCESS') { - console.log('Escrow finished successfully.') + const ledgerCloseTime = validatedLedger.result.ledger.close_time + console.log('Latest validated ledger closed at', + xrpl.rippleTimeToISOTime(ledgerCloseTime)) + if (ledgerCloseTime > finishAfterRippleTime) { + escrowReady = true + console.log('Escrow is mature.') + } else { + let timeDifference = finishAfterRippleTime - ledgerCloseTime + if (timeDifference === 0) { timeDifference = 1 } + console.log(`Waiting another ${timeDifference} second(s).`) + await sleep(timeDifference) } } + +// Send EscrowFinish transaction -------------------------------------------- +const escrowFinish = { + TransactionType: 'EscrowFinish', + Account: wallet.address, + Owner: wallet.address, + OfferSequence: escrowSeq +} +xrpl.validate(escrowFinish) + +console.log('Signing and submitting the transaction:', + JSON.stringify(escrowFinish, null, 2)) +const response2 = await client.submitAndWait(escrowFinish, { + wallet, + autofill: true +}) +console.log(JSON.stringify(response2.result, null, 2)) +if (response2.result.meta.TransactionResult === 'tesSUCCESS') { + console.log('Escrow finished successfully.') +} + +client.disconnect() diff --git a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md index c2071ce25c2..9f476153dae 100644 --- a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md +++ b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-timed-escrow.md @@ -42,70 +42,82 @@ npm i {% /tab %} {% /tabs %} -### 2. Import dependencies and define main function +### 2. Set up client and account -After importing the XRPL client library, the tutorial code defines the main function which controls the flow of the script. This function does several things: +To get started, import the client library and instantiate an API client. For this tutorial, you also need one account, which you can get from the faucet. -1. Connect to the network and get a new wallet from the testnet faucet. -2. Define the properties of the escrow as hard-coded constants. -3. Use helper functions to create the escrow, wait for it to be ready, and then finish it. These functions are defined later in the file. -4. Disconnect from the network when done. +{% tabs %} +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" before="// Set the escrow finish time" /%} +{% /tab %} +{% /tabs %} + +### 3. Calculate the finish time + +To make a timed escrow, you need to set the maturity time of the escrow, which is a timestamp after which the escrow can be finished, formatted as [seconds since the Ripple Epoch][]. You can calculate the maturity time by adding a delay to the current time and then using the client library's conversion function. The sample code uses a delay of 30 seconds: {% tabs %} {% tab label="JavaScript" %} -{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" before="/* send_timed_escrow" /%} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="// Set the escrow finish time" before="// Send EscrowCreate transaction" /%} {% /tab %} {% /tabs %} -### 3. Create the escrow +{% admonition type="danger" name="Warning" %}If you use a UNIX time without converting to the equivalent Ripple time first, that sets the maturity time to an extra **30 years** in the future!{% /admonition %} + +If you want your escrow to have an expiration time, after which it can only be canceled, you can calculate it the same way. -Next, the `send_timed_escrow(...)` function implements the following: +### 4. Create the escrow -1. Calculate the maturity time of the escrow (when it should be possible to finish it), and convert it to the correct format ([seconds since the Ripple Epoch][]). - {% admonition type="danger" name="Warning" %}If you use a UNIX time in the `FinishAfter` field without converting to the equivalent Ripple time first, that sets the unlock time to an extra **30 years** in the future!{% /admonition %} -2. Construct an [EscrowCreate transaction][]. - {% admonition type="info" name="Note" %}If you are sending a token escrow, you must also add an expiration time in the `CancelAfter` field, in the same time format. This time must be after the maturity time.{% /admonition %} -3. Submit the transaction to the network and wait for it to be validated by consensus. -4. Return the details of the escrow, particularly the autofilled sequence number. You need this sequence number to identify the escrow in later transactions. +To send the escrow, construct an [EscrowCreate transaction][] and then submit it to the network. The fields of this transaction define the properties of the escrow. The sample code uses hard-coded values to send 0.123456 XRP back to the Testnet faucet: {% tabs %} {% tab label="JavaScript" %} -{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* send_timed_escrow" before="/* wait_for_escrow" /%} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="// Send EscrowCreate transaction" before="// Save the sequence number" /%} + +{% admonition type="info" name="Note" %}To give the escrow an expiration time, add a `CancelAfter` field to the transaction. An expiration time is optional for timed XRP escrows but required for token escrows. This time must be after the maturity time.{% /admonition %} + +Save the sequence number of the EscrowCreate transaction. (In this example, the sequence number is autofilled.) You need this sequence number to identify the escrow when you want to finish (or cancel) it later. + +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="// Save the sequence number" before="// Wait for the escrow" /%} {% /tab %} {% /tabs %} -### 4. Wait for the escrow +### 5. Wait for the escrow -The `wait_for_escrow(...)` function implements the following: - -1. Check the official close time of the most recent validated ledger. -2. Wait a number of seconds based on the difference between that close time and the time when the escrow is ready to be finished. -3. Repeat until the escrow is ready. The actual, official close time of ledgers [is rounded](../../../../concepts/ledgers/ledger-close-times.md) by up to 10 seconds, so there is some variance in how long it actually takes for an escrow to be ready. +With the escrow successfully created, the funds are now locked up until the maturity time. Since this tutorial used a delay of 30 seconds, have the script sleep for that long: {% tabs %} {% tab label="JavaScript" %} -{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* wait_for_escrow" before="/* Sleep function" /%} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="// Wait for the escrow" before="/* Sleep function" /%} + +JavaScript doesn't have a native `sleep(...)` function, but you can implement one to be used with `await`, as a convenience: -Additionally, since JavaScript doesn't have a native `sleep(...)` function, the sample code implements one to be used with `await`, as a convenience: +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* Sleep function" before="// Check if escrow can be finished" /%} +{% /tab %} +{% /tabs %} -{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* Sleep function" before="/* finish_escrow" /%} +At this point, the escrow should be mature, but that depends on the official close time of the previous ledger. Ledger close times can vary based on the consensus process, and [are rounded](../../../../concepts/ledgers/ledger-close-times.md) by up to 10 seconds. To account for this variance, use an approach such as the following: +1. Check the official close time of the most recent validated ledger. +2. Wait a number of seconds based on the difference between that close time and the maturity time of the escrow. +3. Repeat until the escrow is mature. + +{% tabs %} +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="// Check if escrow can be finished" before="// Send EscrowFinish transaction" /%} {% /tab %} {% /tabs %} -### 5. Finish the escrow +### 6. Finish the escrow -The `finish_escrow(...)` function implements the following: +Now that the escrow is mature, you can finish it. Construct an [EscrowFinish transaction][], using the sequence number that you recorded when you created the escrow, then submit it to the network. -1. Construct an [EscrowFinish transaction][], using the sequence number recorded when the escrow was created. - {% admonition type="success" name="Tip" %}Anyone can finish a timed escrow when it is ready. Regardless of who does so—the sender, receiver, or even a third party—the escrow delivers the funds to its intended recipient.{% /admonition %} -2. Submit the transaction to the network and wait for it to be validated by consensus. -3. Display the details of the validated transaction. +{% admonition type="success" name="Tip" %}Anyone can finish a timed escrow when it is ready. Regardless of who does so—the sender, receiver, or even a third party—the escrow delivers the funds to its intended recipient.{% /admonition %} {% tabs %} {% tab label="JavaScript" %} -{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="/* finish_escrow" /%} +{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="// Send EscrowFinish transaction" /%} {% /tab %} {% /tabs %} From 847b0f71b94db31424fdf8b22e683c25e56a20a6 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Fri, 14 Nov 2025 16:24:46 -0800 Subject: [PATCH 6/6] Escrow tutorials: Add conditional example & workaround for faucet tecDIR_FULL --- .../escrow/js/send-conditional-escrow.js | 89 +++++++++++++++++++ _code-samples/escrow/js/send-timed-escrow.js | 13 ++- 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 _code-samples/escrow/js/send-conditional-escrow.js diff --git a/_code-samples/escrow/js/send-conditional-escrow.js b/_code-samples/escrow/js/send-conditional-escrow.js new file mode 100644 index 00000000000..594b4d303b8 --- /dev/null +++ b/_code-samples/escrow/js/send-conditional-escrow.js @@ -0,0 +1,89 @@ +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() + +console.log('Funding new wallet from faucet...') +const { wallet } = await client.fundWallet() +const destination_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet +// Alternative: Get another account to send the escrow to. Use this if you get +// a tecDIR_FULL error trying to create escrows to the Testnet faucet. +// const destination_address = (await client.fundWallet()).wallet.address + +// Create the crypto-condition for release ---------------------------------- +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) + +// Set the escrow expiration ------------------------------------------------ +const cancelDelay = 300 // Seconds in the future when the escrow should expire +const cancelAfter = new Date() // Current time +cancelAfter.setSeconds(cancelAfter.getSeconds() + cancelDelay) +console.log('This escrow will expire after:', cancelAfter) +// Convert cancelAfter to seconds since the Ripple Epoch: +const cancelAfterRippleTime = xrpl.isoTimeToRippleTime(cancelAfter.toISOString()) + +// Send EscrowCreate transaction -------------------------------------------- +const escrowCreate = { + TransactionType: 'EscrowCreate', + Account: wallet.address, + Destination: destination_address, + Amount: '123456', // drops of XRP + Condition: conditionHex, + CancelAfter: cancelAfterRippleTime +} +xrpl.validate(escrowCreate) + +console.log('Signing and submitting the transaction:', + JSON.stringify(escrowCreate, null, 2)) +const response = await client.submitAndWait(escrowCreate, { + wallet, + autofill: true // Note: fee is higher based on condition size in bytes +}) + +// Check result of submitting ----------------------------------------------- +console.log(JSON.stringify(response.result, null, 2)) +const escrowCreateResultCode = response.result.meta.TransactionResult +if (escrowCreateResultCode === 'tesSUCCESS') { + console.log('Escrow created successfully.') +} else { + console.error(`EscrowCreate failed with code ${escrowCreateResultCode}.`) + process.exit(1) +} + +// Save the sequence number so you can identify the escrow later. +const escrowSeq = response.result.tx_json.Sequence +console.log(`Escrow sequence is ${escrowSeq}.`) + + +// Send EscrowFinish transaction -------------------------------------------- +const escrowFinish = { + TransactionType: 'EscrowFinish', + Account: wallet.address, + Owner: wallet.address, + OfferSequence: escrowSeq, + Condition: conditionHex, + Fulfillment: fulfillmentHex +} +xrpl.validate(escrowFinish) + +console.log('Signing and submitting the transaction:', + JSON.stringify(escrowFinish, null, 2)) +const response2 = await client.submitAndWait(escrowFinish, { + wallet, + autofill: true // Note: fee is higher based on fulfillment size in bytes +}) +console.log(JSON.stringify(response2.result, null, 2)) +if (response2.result.meta.TransactionResult === 'tesSUCCESS') { + console.log('Escrow finished successfully.') +} else { + console.log(`Failed with result code ${response2.result.meta.TransactionResult}`) +} + +client.disconnect() diff --git a/_code-samples/escrow/js/send-timed-escrow.js b/_code-samples/escrow/js/send-timed-escrow.js index 5b0b98c8924..723d026741b 100644 --- a/_code-samples/escrow/js/send-timed-escrow.js +++ b/_code-samples/escrow/js/send-timed-escrow.js @@ -5,6 +5,10 @@ await client.connect() console.log('Funding new wallet from faucet...') const { wallet } = await client.fundWallet() +const destination_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet +// Alternative: Get another account to send the escrow to. Use this if you get +// a tecDIR_FULL error trying to create escrows to the Testnet faucet. +// const destination_address = (await client.fundWallet()).wallet.address // Set the escrow finish time ----------------------------------------------- const delay = 30 // Seconds in the future when the escrow should mature @@ -18,7 +22,7 @@ const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString() const escrowCreate = { TransactionType: 'EscrowCreate', Account: wallet.address, - Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe', // Testnet faucet + Destination: destination_address, Amount: '123456', // drops of XRP FinishAfter: finishAfterRippleTime } @@ -31,6 +35,13 @@ const response = await client.submitAndWait(escrowCreate, { autofill: true }) console.log(JSON.stringify(response.result, null, 2)) +const escrowCreateResultCode = response.result.meta.TransactionResult +if (escrowCreateResultCode === 'tesSUCCESS') { + console.log('Escrow created successfully.') +} else { + console.error(`EscrowCreate failed with code ${escrowCreateResultCode}.`) + process.exit(1) +} // Save the sequence number so you can identify the escrow later. const escrowSeq = response.result.tx_json.Sequence