Create Conditional Escrows Using JavaScript
This example shows how to:
Create escrow payments that become available when any account enters a fulfillment code.
Complete a conditional escrow transaction.
Cancel a conditional escrow transaction.
Prerequisites
Download and expand the Modular Tutorials 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
:
- In a terminal window, navigate to your chosen local directory.
- Enter the command
npm install five-bells-condition
.
To create a condition/fulfillment pair:
- In a terminal window, navigate to your chosen local directory.
- Enter the command
node getConditionAndFulfillment.js
. - Copy and save the generated Condition and Fulfillment pair.
To get test accounts:
- Open
create-conditional-escrow.html
in a browser - Get test accounts.
- If you copied the gathered information from another tutorial:
- Paste the gathered information to the Result field.
- Click Distribute Account Info.
- If you have an existing account seed:
- Paste the account seed to the Account 1 Seed or Account 2 Seed field.
- Click Get Account 1 from Seed or Get Account 2 from Seed.
- If you do not have existing accounts:
- Click Get New Account 1.
- Click Get New Account 2.
- If you copied the gathered information from another tutorial:
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:
- Enter an Amount to transfer.
- Enter the Destination field (for example, use Account 2 Address).
- Enter the Escrow Condition value.
- Enter the Escrow Cancel (seconds) value.
- Click Create Escrow.
- 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.
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:
- Enter the Escrow Condition code for the escrow.
- Enter the corresponding Escrow Fulfillment code.
- Enter the Escrow Owner (the account address of the account that created the escrow).
- Enter the sequence number in the Escrow Sequence Number field.
- Click Finish Escrow.
The transaction is completed and balances adjusted for both accounts.
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:
- Enter the sequence number in the Escrow Sequence Number field.
- 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.
- If needed, create a new escrow as described in Create Escrow, above.
- Click Get Escrows to get the escrow information.
- Copy the PreviousTxnID value from the results.
- Paste the PreviousTxnID in the Transaction field.
- Click Get Transaction.
- Locate the ModifiedNode.PreviousFields.Sequence value in the results.
Code Walkthrough
Download the Modular Tutorials archive.
five-bells.cjs
To generate a condition/fulfillment pair, use Node.js to run the five-bells.js
script.
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.
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.
let escrow_cancel_date = new Date()
escrow_cancel_date = addSeconds(parseInt(escrowCancelDateField.value))
Prepare the transaction object.
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.
const signed = wallet.sign(escrowTx)
Submit the signed object and wait for the results.
const tx = await client.submitAndWait(signed.tx_blob)
Report the results, parsing the Sequence Number for later use.
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.
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.
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.
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.
const signed = wallet.sign(prepared)
Submit the signed transaction and wait for the results.
const tx = await client.submitAndWait(signed.tx_blob)
Report the results
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.
catch (error) {
results += "\n===Error: " + error.message + ".===\n"
resultField.value = results
}
finally {
// -------------------------------------------------------- Disconnect
client.disconnect()
}
create-conditional-escrow.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/[email protected]/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>