Compare commits

...

5 Commits

Author SHA1 Message Date
mDuo13
95196d3195 Make tutorial for linear version of the timed escrow code 2025-10-16 13:38:13 -07:00
mDuo13
8f301ed391 rm reference to pre-refactor escrow code sample 2025-10-16 13:29:35 -07:00
mDuo13
892f9202c5 Finish updating timed escrow tutorial 2025-10-10 16:38:04 -07:00
mDuo13
8c25a5ea33 Start implementing tutorial for updated timed escrow sample 2025-09-17 18:19:53 -07:00
mDuo13
242a6cc31b Show two possible formats of Escrow sample code 2025-09-17 16:39:30 -07:00
7 changed files with 243 additions and 285 deletions

View File

@@ -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()

View File

@@ -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"
}

View File

@@ -0,0 +1,93 @@
import xrpl from 'xrpl'
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()
// 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())
// Send EscrowCreate transaction --------------------------------------------
const escrowCreate = {
TransactionType: 'EscrowCreate',
Account: wallet.address,
Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe', // Testnet faucet
Amount: '123456', // 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))
// Save the sequence number so you can identify the escrow later.
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)
/* Sleep function that can be used with await */
function sleep (delayInSeconds) {
const delayInMs = delayInSeconds * 1000
return new Promise((resolve) => setTimeout(resolve, delayInMs))
}
// 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 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()

View File

@@ -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 %}
<!-- TODO: re-add a working JS example. Removed the old one due to the sample code being refactored -->
## 4. Wait for validation
{% raw-partial file="/docs/_snippets/wait-for-validation.md" /%}

View File

@@ -1,208 +0,0 @@
---
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.
labels:
- Escrow
- Smart Contracts
---
# Send a Time-Held Escrow
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
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:
{% 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" /%}
{% /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 %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-request.json" language="json" /%}
{% /tab %}
{% /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" /%}
{% tabs %}
{% tab label="Websocket" %}
Request:
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowfinish-time.json" language="json" /%}
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 %}
{% 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" /%}
## 8. Confirm final result
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:
{% 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 %}
{% /tabs %}
## See Also
- **Concepts:**
- [What is XRP?](../../../../introduction/what-is-xrp.md)
- [Payment Types](../../../../concepts/payment-types/index.md)
- [Escrow](../../../../concepts/payment-types/escrow.md)
- **Tutorials:**
- [Send XRP](../../send-xrp.md)
- [Look Up Transaction Results](../../../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
- [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md)
- **References:**
- [EscrowCancel transaction][]
- [EscrowCreate transaction][]
- [EscrowFinish transaction][]
- [account_objects method][]
- [tx method][]
- [Escrow ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -0,0 +1,143 @@
---
seo:
description: Send an escrow whose only condition for release is that a specific time has passed.
labels:
- 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. Set up client and account
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.
{% 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" from="// Set the escrow finish time" before="// Send EscrowCreate transaction" /%}
{% /tab %}
{% /tabs %}
{% 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.
### 4. Create the escrow
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 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 %}
### 5. Wait for the escrow
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 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:
{% 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 %}
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 %}
### 6. Finish the escrow
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.
{% 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="// Send EscrowFinish transaction" /%}
{% /tab %}
{% /tabs %}
## See Also
- **Concepts:**
- [What is XRP?](../../../../introduction/what-is-xrp.md)
- [Payment Types](../../../../concepts/payment-types/index.md)
- [Escrow](../../../../concepts/payment-types/escrow.md)
- **Tutorials:**
- [Send XRP](../../send-xrp.md)
- [Look Up Transaction Results](../../../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
- [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md)
- **References:**
- [EscrowCancel transaction][]
- [EscrowCreate transaction][]
- [EscrowFinish transaction][]
- [account_objects method][]
- [tx method][]
- [Escrow ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -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