mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-21 20:25:51 +00:00
576 lines
22 KiB
Markdown
576 lines
22 KiB
Markdown
---
|
|
seo:
|
|
description: Create, finish, or cancel condition-based escrow transactions.
|
|
labels:
|
|
- Accounts
|
|
- Modular Tutorials
|
|
- Transaction Sending
|
|
- XRP
|
|
- Escrow
|
|
---
|
|
# Create Conditional Escrows Using JavaScript
|
|
|
|
This example shows how to:
|
|
|
|
1. Create escrow payments that become available when any account enters a fulfillment code.
|
|
|
|
2. Complete a conditional escrow transaction.
|
|
|
|
3. Cancel a conditional escrow transaction.
|
|
|
|
[](/docs/img/mt-conditional-escrow-1-empty-form.png)
|
|
|
|
|
|
## Prerequisites
|
|
|
|
Download and expand the [Modular Tutorials](../../../../_code-samples/modular-tutorials/payment-modular-tutorials.zip)<!-- {.github-code-download} --> archive.
|
|
|
|
## Usage
|
|
|
|
### Create Escrow
|
|
|
|
You create a condition-based escrow using a fulfillment code associated with a condition code. Create the condition/fulfillment pair using the `five-bells-condition` application.
|
|
|
|
Install `five-bells-condition`:
|
|
|
|
1. In a terminal window, navigate to your chosen local directory.
|
|
2. Enter the command `npm install five-bells-condition`.
|
|
|
|
To create a condition/fulfillment pair:
|
|
|
|
1. In a terminal window, navigate to your chosen local directory.
|
|
2. Enter the command `node getConditionAndFulfillment.js`.
|
|
3. Copy and save the generated Condition and Fulfillment pair.
|
|
|
|
[](/docs/img/mt-conditional-escrow-2-getConditionAndFulfillment.png)
|
|
|
|
To get test accounts:
|
|
|
|
1. Open `create-conditional-escrow.html` in a browser
|
|
2. Get test accounts.
|
|
1. If you copied the gathered information from another tutorial:
|
|
1. Paste the gathered information to the **Result** field.
|
|
2. Click **Distribute Account Info**.
|
|
2. If you have an existing account seed:
|
|
1. Paste the account seed to the **Account 1 Seed** or **Account 2 Seed** field.
|
|
2. Click **Get Account 1 from Seed** or **Get Account 2 from Seed**.
|
|
2. If you do not have existing accounts:
|
|
1. Click **Get New Account 1**.
|
|
2. Click **Get New Account 2**.
|
|
|
|
[](/docs/img/mt-conditional-escrow-3-form-with-accounts.png)
|
|
|
|
### Create Conditional Escrow
|
|
|
|
When you create a conditional escrow, you need to specify the amount you want to reserve and the `Condition` value you generated above. You can also set a cancel date and time, after which the escrow is no longer available. For testing, the **Cancel** time is in seconds: in practice, you might set a **Cancel** time in days, weeks, months, or years.
|
|
|
|
To create a conditional escrow:
|
|
|
|
1. Enter an **Amount** to transfer.
|
|
3. Enter the **Destination** field (for example, use Account 2 Address).
|
|
4. Enter the **Escrow Condition** value.
|
|
5. Enter the **Escrow Cancel (seconds)** value.
|
|
6. Click **Create Escrow**.
|
|
7. Copy and save the _Sequence Number_ of the escrow called out in the **Results** field.
|
|
|
|
The escrow is created on the XRP Ledger instance, reserving your requested XRP amount plus the transaction cost.
|
|
|
|
When you create an escrow, capture and save the _Sequence Number_ so that you can use it to finish the escrow transaction.
|
|
|
|
[](/docs/img/mt-conditional-escrow-4-escrow-create.png)
|
|
|
|
## Finish Conditional Escrow
|
|
|
|
Any account can finish the conditional escrow any time before the _Escrow Cancel_ time. Following on the example above, you can use the _Sequence Number_ to finish the transaction once the Escrow Cancel time has passed.
|
|
|
|
To finish a conditional escrow:
|
|
|
|
1. Enter the **Escrow Condition** code for the escrow.
|
|
2. Enter the corresponding **Escrow Fulfillment** code.
|
|
3. Enter the **Escrow Owner** (the account address of the account that created the escrow).
|
|
4. Enter the sequence number in the **Escrow Sequence Number** field.
|
|
5. Click **Finish Escrow**.
|
|
|
|
The transaction is completed and balances adjusted for both accounts.
|
|
|
|
[](/docs/img/mt-conditional-escrow-5-escrow-fulfill.png)
|
|
|
|
## Get Escrows
|
|
|
|
Click **Get Escrows** to see the current list of escrows generated by or destined for the current account.
|
|
|
|
## Cancel Escrow
|
|
|
|
When the Escrow Cancel time passes, the escrow is no longer available to the recipient. The initiator of the escrow can reclaim the XRP, less the transaction fees. Any account can cancel an escrow once the cancel time has elapsed. Accounts that try to cancel the transaction prior to the **Escrow Cancel** time are charged the nominal transaction cost (typically 12 drops), but the actual escrow cannot be cancelled until after the Escrow Cancel time.
|
|
|
|
To cancel an expired escrow:
|
|
|
|
1. Enter the sequence number in the **Escrow Sequence Number** field.
|
|
2. Click **Cancel Escrow**.
|
|
|
|
## Oh No! I Forgot to Save the Sequence Number!
|
|
|
|
If you forget to save the sequence number, you can find it in the escrow transaction record.
|
|
|
|
1. If needed, create a new escrow as described in [Create Escrow](#create-escrow), above.
|
|
2. Click **Get Escrows** to get the escrow information.
|
|
3. Copy the _PreviousTxnID_ value from the results.
|
|
[](/docs/img/mt-conditional-escrow-6-get-escrows.png)
|
|
4. Paste the _PreviousTxnID_ in the **Transaction** field.
|
|
5. Click **Get Transaction**.
|
|
6. Locate the _ModifiedNode.PreviousFields.Sequence_ value in the results.
|
|
[](/docs/img/mt-conditional-escrow-7-sequence-value.png)
|
|
|
|
# Code Walkthrough
|
|
|
|
Download the [Modular Tutorials](../../../../_code-samples/modular-tutorials/payment-modular-tutorials.zip)<!-- {.github-code-download} --> archive.
|
|
|
|
## five-bells.cjs
|
|
|
|
To generate a condition/fulfillment pair, use Node.js to run the `five-bells.js` script.
|
|
|
|
```javascript
|
|
const cc = require('five-bells-condition')
|
|
const crypto = require('crypto')
|
|
|
|
// 1. Generate a random 32-byte seed
|
|
const preimageData = crypto.randomBytes(32)
|
|
|
|
// 2. Create a PreimageSha256 fulfillment object
|
|
const fulfillment = new cc.PreimageSha256()
|
|
|
|
// 3. Set the preimage
|
|
fulfillment.setPreimage(preimageData)
|
|
|
|
// 4. Generate the condition (binary)
|
|
const conditionBinary = fulfillment.getConditionBinary()
|
|
|
|
// 5. Generate the fulfillment (binary)
|
|
const fulfillmentBinary = fulfillment.serializeBinary()
|
|
|
|
// Convert to hex for easier use
|
|
const conditionHex = conditionBinary.toString('hex').toUpperCase()
|
|
const fulfillmentHex = fulfillmentBinary.toString('hex').toUpperCase()
|
|
|
|
console.log('Condition (hex):', conditionHex)
|
|
console.log('Fulfillment (hex):', fulfillmentHex)
|
|
```
|
|
|
|
## create-conditional-escrow.js
|
|
|
|
|
|
### createConditionalEscrow()
|
|
|
|
Connect to the ledger and get the account wallet.
|
|
|
|
```javascript
|
|
async function createConditionalEscrow() {
|
|
let net = getNet()
|
|
const client = new xrpl.Client(net)
|
|
await client.connect()
|
|
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
|
const sendAmount = amountField.value
|
|
let results = `===Connected to ${net}===\n===Creating conditional escrow.===\n\n`
|
|
resultField.value = results
|
|
```
|
|
|
|
Prepare the cancel date by adding the number of seconds in the **Escrow Cancel Date** field to the current date and time. In practice, the cancel date might be in days, weeks, months, or years. Using seconds allows you to test scenarios with expired escrows.
|
|
|
|
```javascript
|
|
let escrow_cancel_date = new Date()
|
|
escrow_cancel_date = addSeconds(parseInt(escrowCancelDateField.value))
|
|
```
|
|
|
|
Prepare the transaction object.
|
|
|
|
```javascript
|
|
const escrowTx = await client.autofill({
|
|
"TransactionType": "EscrowCreate",
|
|
"Account": wallet.address,
|
|
"Amount": xrpl.xrpToDrops(sendAmount),
|
|
"Destination": destinationField.value,
|
|
"CancelAfter": escrow_cancel_date,
|
|
"Condition": escrowConditionField.value
|
|
})
|
|
```
|
|
|
|
Sign the prepared transaction object.
|
|
|
|
```javascript
|
|
const signed = wallet.sign(escrowTx)
|
|
```
|
|
|
|
Submit the signed object and wait for the results.
|
|
|
|
```javascript
|
|
const tx = await client.submitAndWait(signed.tx_blob)
|
|
```
|
|
|
|
Report the results, parsing the _Sequence Number_ for later use.
|
|
|
|
```javascript
|
|
results = "\n=== *** Sequence Number (Save!): " + tx.result.tx_json.Sequence
|
|
results += "\n\n===Balance changes===\n" +
|
|
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
|
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
|
resultField.value += results
|
|
```
|
|
|
|
Catch and report any errors, then disconnect from the XRP Ledger.
|
|
|
|
```javascript
|
|
catch (error) {
|
|
results += "\n===Error: " + error.message
|
|
resultField.value = results
|
|
}
|
|
finally {
|
|
// -------------------------------------------------------- Disconnect
|
|
client.disconnect()
|
|
}// End of createTimeEscrow()
|
|
```
|
|
|
|
### finishConditionalEscrow()
|
|
|
|
Connect to the ledger and get the account wallet from the account seed.
|
|
|
|
```javascript
|
|
async function finishConditionalEscrow() {
|
|
let net = getNet()
|
|
const client = new xrpl.Client(net)
|
|
await client.connect()
|
|
let results = `===Connected to ${net}===\n===Fulfilling conditional escrow.===\n`
|
|
resultField.value = results
|
|
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
|
```
|
|
|
|
Prepare the transaction object.
|
|
|
|
```javascript
|
|
const prepared = await client.autofill({
|
|
"TransactionType": "EscrowFinish",
|
|
"Account": accountAddressField.value,
|
|
"Owner": escrowOwnerField.value,
|
|
"OfferSequence": parseInt(escrowSequenceNumberField.value),
|
|
"Condition": escrowConditionField.value,
|
|
"Fulfillment": escrowFulfillmentField.value
|
|
})
|
|
```
|
|
|
|
Sign the prepared transaction object.
|
|
|
|
```javascript
|
|
const signed = wallet.sign(prepared)
|
|
```
|
|
|
|
Submit the signed transaction and wait for the results.
|
|
|
|
```javascript
|
|
const tx = await client.submitAndWait(signed.tx_blob)
|
|
```
|
|
|
|
Report the results
|
|
|
|
```javascript
|
|
results = "\n===Balance changes===" +
|
|
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
|
resultField.value += results
|
|
```
|
|
|
|
Catch and report any errors, then disconnect from the XRP Ledger.
|
|
|
|
```javascript
|
|
catch (error) {
|
|
results += "\n===Error: " + error.message + ".===\n"
|
|
resultField.value = results
|
|
}
|
|
finally {
|
|
// -------------------------------------------------------- Disconnect
|
|
client.disconnect()
|
|
}
|
|
```
|
|
|
|
## create-conditional-escrow.html
|
|
|
|
```html
|
|
<html>
|
|
<head>
|
|
<title>Create a Conditional Escrow</title>
|
|
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
|
<link href="modular-tutorials.css" rel="stylesheet">
|
|
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
|
|
<script src="account-support.js"></script>
|
|
<script src="create-time-escrow.js"></script>
|
|
<script src='create-conditional-escrow.js'></script>
|
|
<script>
|
|
if (typeof module !== "undefined") {
|
|
const xrpl = require('xrpl')
|
|
}
|
|
</script>
|
|
</head>
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ********************** The Form ****************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<body>
|
|
<h1>Create a Conditional Escrow</h1>
|
|
<form id="theForm">
|
|
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
|
|
Choose your ledger instance:
|
|
</span>
|
|
|
|
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
|
|
<label for="dn">Devnet</label>
|
|
|
|
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
|
|
<label for="tn">Testnet</label>
|
|
<br /><br />
|
|
<table>
|
|
<tr>
|
|
<td>
|
|
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
|
|
</td>
|
|
<td>
|
|
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
|
|
</td>
|
|
<td>
|
|
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
|
|
</td>
|
|
<td>
|
|
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="account1name" size="40"></input>
|
|
</td>
|
|
<td>
|
|
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
|
|
<label for="account2name">Account 2 Name</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="account2name" size="40"></input>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
|
<label for="account1address">Account 1 Address</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="account1address" size="40"></input>
|
|
</td>
|
|
<td>
|
|
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
|
<label for="account2address">Account 2 Address</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="account2address" size="40"></input>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
|
<label for="account1seed">Account 1 Seed</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="account1seed" size="40"></input>
|
|
</td>
|
|
<td>
|
|
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
|
<label for="account2seed">Account 2 Seed</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="account2seed" size="40"></input>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<hr />
|
|
<table>
|
|
<tr valign="top">
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Name of the currently selected account.">
|
|
<label for="accountNameField">Account Name</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="accountNameField" size="40" readonly></input>
|
|
<input type="radio" id="account1" name="accounts" value="account1">
|
|
<label for="account1">Account 1</label>
|
|
</td>
|
|
</tr>
|
|
<tr valign="top">
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Address of the currently selected account.">
|
|
<label for="accountAddressField">Account Address</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="accountAddressField" size="40" readonly></input>
|
|
<input type="radio" id="account2" name="accounts" value="account2">
|
|
<label for="account2">Account 2</label>
|
|
</td>
|
|
</tr>
|
|
<tr valign="top">
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
|
|
<label for="accountSeedField">Account Seed</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="accountSeedField" size="40" readonly></input>
|
|
<br>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
|
|
<label for="xrpBalanceField">XRP Balance</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="xrpBalanceField" size="40" readonly></input>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Amount of XRP to send.">
|
|
<label for="amountField">Amount</label>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="amountField" size="40"></input>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Destination account address where the escrow is sent.">
|
|
<lable for="destinationField">Destination</lable>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="destinationField" size="40"></input>
|
|
<br>
|
|
</td>
|
|
<td align="left" valign="top">
|
|
<button type="button" onClick="createConditionalEscrow()">Create Escrow</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Condition code used to begin the escrow transaction.">
|
|
<lable for="escrowConditionField">Escrow Condition</lable>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="escrowConditionField" size="40"></input>
|
|
<br>
|
|
</td>
|
|
<td align="left" valign="top">
|
|
<button type="button" onClick="getEscrows()">Get Escrows</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Fullfillment code to complete the escrow transaction.">
|
|
<lable for="escrowFulfillmentField">Escrow Fulfillment</lable>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="escrowFulfillmentField" size="40"></input>
|
|
<br>
|
|
</td>
|
|
<td>
|
|
<button type="button" onClick="finishConditionalEscrow()">Finish Escrow</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Escrow cancel time, in seconds.">
|
|
<lable for="escrowCancelDateField">Escrow Cancel Time</lable>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="escrowCancelDateField" size="40"></input>
|
|
<br>
|
|
</td>
|
|
<td align="left" valign="top">
|
|
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Escrow sequence number, used when finishing the escrow.">
|
|
<lable for="escrowSequenceNumberField">Escrow Sequence Number</lable>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="escrowSequenceNumberField" size="40"></input>
|
|
<br>
|
|
</td>
|
|
<td>
|
|
<button type="button" onClick="getTransaction()">Get Transaction</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Escrow owner, the account that created the escrow.">
|
|
<lable for="escrowOwnerField">Escrow Owner</lable>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="escrowOwnerField" size="40"></input>
|
|
<br>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="right">
|
|
<span class="tooltip" tooltip-data="Transaction number, used with the Get Transaction button.">
|
|
<lable for="transactionField">Transaction</lable>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<input type="text" id="transactionField" size="40"></input>
|
|
<br>
|
|
</td>
|
|
<td>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="2">
|
|
<p align="right">
|
|
<textarea id="resultField" cols="80" rows="20"></textarea>
|
|
</p>
|
|
</td>
|
|
<td align="left" valign="top">
|
|
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
|
|
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</form>
|
|
</body>
|
|
<script>
|
|
const radioButtons = document.querySelectorAll('input[type="radio"]');
|
|
radioButtons.forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
if (this.value === 'account1') {
|
|
populate1()
|
|
} else if (this.value === 'account2') {
|
|
populate2()
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</html>
|
|
```
|