# Create Conditional Escrows Using Python This example shows how to: 1. Create escrow payments that become available when an account enters a fulfillment code. 2. Complete a conditional escrow transaction. 3. Cancel a conditional escrow transaction. [![Conditional Escrow Tester Form](/assets/quickstart-py-conditional-escrow-1.173f529069d78d8b54f2bec7d270671296e6c3cdf3a8052b3b1b2162f24e520e.ac57e6ef.png)](/assets/quickstart-py-conditional-escrow-1.173f529069d78d8b54f2bec7d270671296e6c3cdf3a8052b3b1b2162f24e520e.ac57e6ef.png) ## Prerequisites Download and expand the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/) archive. You need the `cryptoconditions` module to generate your condition/fulfillment pair. You can install the module using [pip](https://pip.pypa.io/en/stable/). In a terminal window, install the `cryptoconditions` module with this command: ```bash pip install cryptoconditions ``` ## Usage ### Get Test Accounts To get test accounts: 1. Open and run `lesson9-conditional-escrow.py`. 2. Get test accounts. 1. If you have existing account seeds 1. Paste Standby account seed in the **Standby Seed** field. 2. Click **Get Standby Account**. 3. Click **Get Standby Account Info**. 4. Paste Operational account seed in the **Operational Seed** field. 5. Click **Get Operational Account**. 6. Click **Get Op Account Info**. 2. If you do not have account seeds: 1. Click **Get Standby Account**. 2. Click **Get Standby Account Info**. 3. Click **Get Operational Account**. 4. Click **Get Op Account Info**. [![Escrow Example with Account Information](/assets/quickstart-py-conditional-escrow-2.4a4d6d127b84328124976e9f795527f1b5762be34153585249995b60a7bb73c9.ac57e6ef.png)](/assets/quickstart-py-conditional-escrow-2.4a4d6d127b84328124976e9f795527f1b5762be34153585249995b60a7bb73c9.ac57e6ef.png) #### Get a Condition and Fulfillment Click **Get Condition** to generate a condition/fulfillment pair and populate the fields on the form. You can copy the values and store them in a text file for safe keeping. [![Escrow Example with Condition and Fulfillment](/assets/quickstart-py-conditional-escrow-3.226200a8c54594e066c705471b61c03e56474bd1fc4fc299dc5f8fe5429bc4c7.ac57e6ef.png)](/assets/quickstart-py-conditional-escrow-3.226200a8c54594e066c705471b61c03e56474bd1fc4fc299dc5f8fe5429bc4c7.ac57e6ef.png) ### Create Conditional Escrow div iframe When you create a conditional escrow, you need to specify the `Condition` value you generated above. You must also set a cancel date and time, after which the escrow is no longer available. To create a conditional escrow: 1. Enter an **Amount** to transfer. 2. Copy the **Operational Account** value. 3. Paste it in the **Destination Account** field. 4. Enter the **Escrow Cancel (seconds)** value. 5. Click **Create Escrow**. 6. Copy and save the *Sequence Number* of the escrow called out in the **Standby Result** 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. [![Created Escrow Transaction](/assets/quickstart-py-conditional-escrow-4.bb213831a74dfc8101a0166ee459d7c2e1b156a6741f0d4b634df00e7f51cff7.ac57e6ef.png)](/assets/quickstart-py-conditional-escrow-4.bb213831a74dfc8101a0166ee459d7c2e1b156a6741f0d4b634df00e7f51cff7.ac57e6ef.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. Paste the sequence number in the Operational account **Sequence Number** field. 2. Enter the **Escrow Condition** value. 3. Enter the **Escrow Fulfillment** code for the `Condition`. 4. Copy the **Standby Account** value. 5. Paste it into the **Escrow Owner** field. 6. Click **Finish Conditional Escrow**. The transaction completes and balances are updated for both the Standby and Operational accounts. [![Finished Escrow Transaction](/assets/quickstart-py-conditional-escrow-5.437e1a76c088679f3f9024ef42ad63c1cc2e652eb442641b3c753beddae679ea.ac57e6ef.png)](/assets/quickstart-py-conditional-escrow-5.437e1a76c088679f3f9024ef42ad63c1cc2e652eb442641b3c753beddae679ea.ac57e6ef.png) ## Get Escrows Click **Get Escrows** for either the Standby account or the Operational account to see their current list of escrows. ## 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 (about 10-15 drops), but the actual escrow cannot be cancelled until after the Escrow Cancel time. ## 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. Create a new escrow as described in [Create Conditional Escrow](#create-conditional-escrow), above. 2. Click **Get Escrows** to get the escrow information. 3. Copy the *PreviousTxnLgrSeq* value from the results. ![Transaction ID in Get Escrows results](/assets/quickstart-py-conditional-escrow-6.7659f74f51c276db202ed381176f59b10df807cd17552bcb88a7b895d115b9c6.ac57e6ef.png) 4. Paste the *PreviousTxnLgrSeq* in the **Transaction to Look Up** field. ![Transaction to Look Up field](/assets/quickstart-py-conditional-escrow-7.0b75a2d9f38c7736a31ec6b4d0125bdaa91b398c11d683e99c4d1f8c08797d34.ac57e6ef.png) 5. Click **Get Transaction**. 6. Locate the *Sequence* value in the results. ![Sequence number in results](/assets/quickstart-py-conditional-escrow-8.5fce22abe859c09b9aee582e34ad87c4166136c75030f2977054289b8c6f14a2.ac57e6ef.png) # Code Walkthrough You can download the [Modular Tutorials](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)in the source repository for this website. ## mod9.py Import dependencies. ```python import xrpl from xrpl.clients import JsonRpcClient from xrpl.wallet import Wallet from datetime import datetime from xrpl.models.transactions import EscrowCreate, EscrowFinish from os import urandom from cryptoconditions import PreimageSha256 ``` Create a global variable pointing to Testnet. ```python testnet_url = "https://s.altnet.rippletest.net:51234" ``` ### generate_condition Generate the *condition* and *fulfillment* values for the escrow. ```python def generate_condition(): ``` Set a variable to 32 random bytes. ```python randy = urandom(32) ``` Use the 32-byte random variable as the argument for the `PreimageSha256` functino. ```python fulfillment = PreimageSha256(preimage=randy) ``` Return the binary condition and the binary serialized (fulfillment) value. ```python return (fulfillment.condition_binary.hex().upper(), fulfillment.serialize_binary().hex().upper()) ``` ### add_seconds Create a date in the Ripple epoch, adding the specified number of seconds. ```python def add_seconds(numOfSeconds): ``` Create a new_date variable. ```python new_date = datetime.now() ``` Convert the date to a Ripple time object. ```python if new_date != '': new_date = xrpl.utils.datetime_to_ripple_time(new_date) ``` Add the requested seconds to the Ripple formatted date variable. ```python new_date = new_date + int(numOfSeconds) ``` Return the result. ```python return new_date ``` ### create_conditional_escrow You create conditional escrows using the same **EscrowCreate** model you used for a time-based escrow, but instead of a finish time, you provide a condition that must be met to complete the transaction. Pass the *seed* for the sending account, the *amount* to hold in escrow, the *destination* account to receive the escrow funds, the number of seconds until the escrow will *cancel*, and a *condition* value that will be matched with a *fulfillment* value to complete the escrow. ```python def create_conditional_escrow(seed, amount, destination, cancel, condition): ``` Instantiate a wallet and connect to Testnet. ```python wallet=Wallet.from_seed(seed) client=JsonRpcClient(testnet_url) ``` Create a *cancel_date* variable, adding your specified number of seconds to the current Ripple epoch date. ```python cancel_date = add_seconds(cancel) ``` Define the transaction with your provided values. ```python escrow_tx=xrpl.models.transactions.EscrowCreate( account=wallet.address, amount=amount, destination=destination, cancel_after=cancel_date, condition=condition ) ``` Submit the transaction and return the results. ```python reply="" try: response=xrpl.transaction.submit_and_wait(escrow_tx,client,wallet) reply=response.result except xrpl.transaction.XRPLReliableSubmissionException as e: reply=f"Submit failed: {e}" return reply ``` ### finish_conditional_escrow At any time prior to the cancel date, the destination account can fulfill the escrow. Pass the *seed* for the receiving account, the *owner* (sending account), the *sequence* number for the escrow, the *condition* value, and the matching *fulfillment* value. ```python def finish_conditional_escrow(seed, owner, sequence, condition, fulfillment): ``` Instantiate the account wallet and connect to Testnet. ```python wallet=Wallet.from_seed(seed) client=JsonRpcClient(testnet_url) ``` Define the **EscrowFinish** transaction, including both the condition and the fulfillment values. ```python finish_tx=xrpl.models.transactions.EscrowFinish( account=wallet.address, owner=owner, offer_sequence=int(sequence), condition=condition, fulfillment=fulfillment ) ``` Submit the transaction and report the results. ```python reply="" try: response=xrpl.transaction.submit_and_wait(finish_tx,client,wallet) reply=response.result except xrpl.transaction.XRPLReliableSubmissionException as e: reply=f"Submit failed: {e}" return reply ``` ## lesson9-conditional-escrow.py This example builds on `lesson8-time-escrow.py` to reuse fields, buttons, and functions that apply to both time-based and conditional escrows. Updates are highlighted below. ```python import tkinter as tk import xrpl import json from mod1 import get_account, get_account_info, send_xrp from mod8 import get_escrows, cancel_time_escrow, get_transaction ``` Import new functions for conditional escrows from module 9. ```python from mod9 import create_conditional_escrow, finish_conditional_escrow, generate_condition ``` Add handlers for creating and finishing conditional escrows. ```python def get_condition(): results = generate_condition() ent_standby_escrow_condition.delete(0, tk.END) ent_standby_escrow_condition.insert(0, results[0]) ent_operational_escrow_fulfillment.delete(0, tk.END) ent_operational_escrow_fulfillment.insert(0, results[1]) def standby_create_conditional_escrow(): results = create_conditional_escrow( ent_standby_seed.get(), ent_standby_amount.get(), ent_standby_destination.get(), ent_standby_escrow_cancel.get(), ent_standby_escrow_condition.get() ) text_standby_results.delete("1.0", tk.END) text_standby_results.insert("1.0", json.dumps(results, indent=4)) def operational_finish_conditional_escrow(): results = finish_conditional_escrow( ent_operational_seed.get(), ent_operational_escrow_owner.get(), ent_operational_sequence_number.get(), ent_standby_escrow_condition.get(), ent_operational_escrow_fulfillment.get() ) text_operational_results.delete("1.0", tk.END) text_operational_results.insert("1.0", json.dumps(results, indent=4)) ## Mod 8 Handlers def operational_get_escrows(): results = get_escrows(ent_operational_account.get()) text_operational_results.delete("1.0", tk.END) text_operational_results.insert("1.0", json.dumps(results, indent=4)) def standby_cancel_time_escrow(): results = cancel_time_escrow( ent_standby_seed.get(), ent_standby_escrow_owner.get(), ent_standby_escrow_sequence_number.get() ) text_standby_results.delete("1.0", tk.END) text_standby_results.insert("1.0", json.dumps(results, indent=4)) def operational_get_transaction(): results = get_transaction(ent_operational_account.get(), ent_operational_look_up.get()) text_operational_results.delete("1.0", tk.END) text_operational_results.insert("1.0", json.dumps(results, indent=4)) ## Mod 1 Handlers def get_standby_account(): new_wallet = get_account(ent_standby_seed.get()) ent_standby_account.delete(0, tk.END) ent_standby_seed.delete(0, tk.END) ent_standby_account.insert(0, new_wallet.classic_address) ent_standby_seed.insert(0, new_wallet.seed) def get_standby_account_info(): accountInfo = get_account_info(ent_standby_account.get()) ent_standby_balance.delete(0, tk.END) ent_standby_balance.insert(0,accountInfo['Balance']) text_standby_results.delete("1.0", tk.END) text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4)) def standby_send_xrp(): response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(), ent_standby_destination.get()) text_standby_results.delete("1.0", tk.END) text_standby_results.insert("1.0",json.dumps(response.result, indent=4)) get_standby_account_info() get_operational_account_info() def get_operational_account(): new_wallet = get_account(ent_operational_seed.get()) ent_operational_account.delete(0, tk.END) ent_operational_account.insert(0, new_wallet.classic_address) ent_operational_seed.delete(0, tk.END) ent_operational_seed.insert(0, new_wallet.seed) def get_operational_account_info(): accountInfo = get_account_info(ent_operational_account.get()) ent_operational_balance.delete(0, tk.END) ent_operational_balance.insert(0,accountInfo['Balance']) text_operational_results.delete("1.0", tk.END) text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4)) def operational_send_xrp(): response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(), ent_operational_destination.get()) text_operational_results.delete("1.0", tk.END) text_operational_results.insert("1.0",json.dumps(response.result,indent=4)) get_standby_account_info() get_operational_account_info() ``` Rename the window. ```python window = tk.Tk() window.title("Conditional Escrow Example") # Form frame frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3) frm_form.pack() # Create the Label and Entry widgets for "Standby Account" lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed") ent_standby_seed = tk.Entry(master=frm_form, width=50) lbl_standby_account = tk.Label(master=frm_form, text="Standby Account") ent_standby_account = tk.Entry(master=frm_form, width=50) lbl_standy_amount = tk.Label(master=frm_form, text="Amount") ent_standby_amount = tk.Entry(master=frm_form, width=50) lbl_standby_destination = tk.Label(master=frm_form, text="Destination") ent_standby_destination = tk.Entry(master=frm_form, width=50) lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance") ent_standby_balance = tk.Entry(master=frm_form, width=50) ``` Add a field for the escrow condition. ```python lbl_standby_escrow_condition = tk.Label(master=frm_form, text="Escrow Condition") ent_standby_escrow_condition = tk.Entry(master=frm_form, width=50) lbl_standby_escrow_cancel = tk.Label(master=frm_form, text="Escrow Cancel (seconds)") ent_standby_escrow_cancel = tk.Entry(master=frm_form, width=50) lbl_standby_escrow_sequence_number = tk.Label(master=frm_form, text="Sequence Number") ent_standby_escrow_sequence_number = tk.Entry(master=frm_form, width=50) lbl_standby_escrow_owner = tk.Label(master=frm_form, text="Escrow Owner") ent_standby_escrow_owner = tk.Entry(master=frm_form, width=50) lbl_standby_results = tk.Label(master=frm_form, text="Results") text_standby_results = tk.Text(master=frm_form, height = 20, width = 65) # Place fields in a grid. lbl_standy_seed.grid(row=0, column=0, sticky="e") ent_standby_seed.grid(row=0, column=1) lbl_standby_account.grid(row=2, column=0, sticky="e") ent_standby_account.grid(row=2, column=1) lbl_standy_amount.grid(row=3, column=0, sticky="e") ent_standby_amount.grid(row=3, column=1) lbl_standby_destination.grid(row=4, column=0, sticky="e") ent_standby_destination.grid(row=4, column=1) lbl_standby_balance.grid(row=5, column=0, sticky="e") ent_standby_balance.grid(row=5, column=1) ``` Insert the condition field in the standby grid. ```python lbl_standby_escrow_condition.grid(row=6, column=0, sticky="e") ent_standby_escrow_condition.grid(row=6, column=1) lbl_standby_escrow_cancel.grid(row=7, column=0, sticky="e") ent_standby_escrow_cancel.grid(row=7, column=1) lbl_standby_escrow_sequence_number.grid(row=8, column=0, sticky="e") ent_standby_escrow_sequence_number.grid(row=8, column=1) lbl_standby_escrow_owner.grid(row=9, column=0, sticky="e") ent_standby_escrow_owner.grid(row=9, column=1) lbl_standby_results.grid(row=10, column=0, sticky="ne") text_standby_results.grid(row=10, column=1, sticky="nw") ############################################### ## Operational Account ######################## ############################################### # Create the Label and Entry widgets for "Operational Account" lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed") ent_operational_seed = tk.Entry(master=frm_form, width=50) lbl_operational_account = tk.Label(master=frm_form, text="Operational Account") ent_operational_account = tk.Entry(master=frm_form, width=50) lbl_operational_amount = tk.Label(master=frm_form, text="Amount") ent_operational_amount = tk.Entry(master=frm_form, width=50) lbl_operational_destination = tk.Label(master=frm_form, text="Destination") ent_operational_destination = tk.Entry(master=frm_form, width=50) lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance") ent_operational_balance = tk.Entry(master=frm_form, width=50) ``` Add a field for the escrow fulfillment value. ```python lbl_operational_escrow_fulfillment = tk.Label(master=frm_form, text="Escrow Fulfillment") ent_operational_escrow_fulfillment = tk.Entry(master=frm_form, width=50) lbl_operational_sequence_number = tk.Label(master=frm_form, text="Sequence Number") ent_operational_sequence_number = tk.Entry(master=frm_form, width=50) lbl_operational_escrow_owner=tk.Label(master=frm_form, text="Escrow Owner") ent_operational_escrow_owner=tk.Entry(master=frm_form, width=50) lbl_operational_look_up = tk.Label(master=frm_form, text="Transaction to Look Up") ent_operational_look_up = tk.Entry(master=frm_form, width=50) lbl_operational_results = tk.Label(master=frm_form,text='Results') text_operational_results = tk.Text(master=frm_form, height = 20, width = 65) #Place the widgets in a grid lbl_operational_seed.grid(row=0, column=4, sticky="e") ent_operational_seed.grid(row=0, column=5, sticky="w") lbl_operational_account.grid(row=2,column=4, sticky="e") ent_operational_account.grid(row=2,column=5, sticky="w") lbl_operational_amount.grid(row=3, column=4, sticky="e") ent_operational_amount.grid(row=3, column=5, sticky="w") lbl_operational_destination.grid(row=4, column=4, sticky="e") ent_operational_destination.grid(row=4, column=5, sticky="w") lbl_operational_balance.grid(row=5, column=4, sticky="e") ent_operational_balance.grid(row=5, column=5, sticky="w") ``` Insert the **Fulfillment** field in the operational grid, moving the other fields down so as to align the **Condition** and **Fulfillment** fields horizontally. ```python lbl_operational_escrow_fulfillment.grid(row=6, column=4, sticky="e") ent_operational_escrow_fulfillment.grid(row=6, column=5, sticky="w") lbl_operational_sequence_number.grid(row=7, column=4, sticky="e") ent_operational_sequence_number.grid(row=7, column=5, sticky="w") lbl_operational_escrow_owner.grid(row=8, column=4, sticky="e") ent_operational_escrow_owner.grid(row=8, column=5, sticky="w") lbl_operational_look_up.grid(row=9, column=4, sticky="e") ent_operational_look_up.grid(row=9, column=5, sticky="w") lbl_operational_results.grid(row=10, column=4, sticky="ne") text_operational_results.grid(row=10, column=5, sticky="nw") ############################################# ## Buttons ################################## ############################################# # Create the Get Standby Account Buttons btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account", command = get_standby_account) btn_get_standby_account.grid(row = 0, column = 2, sticky = "nsew") btn_get_standby_account_info = tk.Button(master=frm_form, text="Get Standby Account Info", command = get_standby_account_info) btn_get_standby_account_info.grid(row = 1, column = 2, sticky = "nsew") btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >", command = standby_send_xrp) btn_standby_send_xrp.grid(row = 2, column = 2, sticky = "nsew") ``` Add a **Create Conditional Escrow** button to the Standby grid. ```python btn_standby_get_condition = tk.Button(master=frm_form, text="Get Condition", command = get_condition) btn_standby_get_condition.grid(row=4, column=2, sticky="nsew") btn_standby_create_escrow = tk.Button(master=frm_form, text="Create Conditional Escrow", command = standby_create_conditional_escrow) btn_standby_create_escrow.grid(row=5, column = 2, sticky="nsew") btn_standby_cancel_escrow = tk.Button(master=frm_form, text="Cancel Escrow", command = standby_cancel_time_escrow) btn_standby_cancel_escrow.grid(row=6,column = 2, sticky="nsew") # Create the Operational Account Buttons btn_get_operational_account = tk.Button(master=frm_form, text="Get Operational Account", command = get_operational_account) btn_get_operational_account.grid(row=0, column=3, sticky = "nsew") btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info", command = get_operational_account_info) btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew") btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP", command = operational_send_xrp) btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew") ``` Add a **Finish Escrow** button to the operational grid. ```python btn_op_finish_escrow = tk.Button(master=frm_form, text="Finish Escrow", command = operational_finish_conditional_escrow) btn_op_finish_escrow.grid(row = 4, column = 3, sticky="nsew") btn_op_get_escrows = tk.Button(master=frm_form, text="Get Escrows", command = operational_get_escrows) btn_op_get_escrows.grid(row = 5, column = 3, sticky="nsew") btn_op_get_transaction = tk.Button(master=frm_form, text="Get Transaction", command = operational_get_transaction) btn_op_get_transaction.grid(row = 6, column = 3, sticky = "nsew") # Start the application window.mainloop() ```