diff --git a/_code-samples/escrow/js/send-conditional-escrow.js b/_code-samples/escrow/js/send-conditional-escrow.js index 594b4d303b..14c3fa9be2 100644 --- a/_code-samples/escrow/js/send-conditional-escrow.js +++ b/_code-samples/escrow/js/send-conditional-escrow.js @@ -7,10 +7,10 @@ await client.connect() console.log('Funding new wallet from faucet...') const { wallet } = await client.fundWallet() -const destination_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet +// 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 +const destination_address = (await client.fundWallet()).wallet.address // Create the crypto-condition for release ---------------------------------- const preimage = randomBytes(32) diff --git a/_code-samples/escrow/js/send-timed-escrow.js b/_code-samples/escrow/js/send-timed-escrow.js index 723d026741..25117ecbc8 100644 --- a/_code-samples/escrow/js/send-timed-escrow.js +++ b/_code-samples/escrow/js/send-timed-escrow.js @@ -5,10 +5,10 @@ await client.connect() console.log('Funding new wallet from faucet...') const { wallet } = await client.fundWallet() -const destination_address = 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe' // Testnet faucet +// 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 +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 diff --git a/_code-samples/escrow/py/cancel_escrow.py b/_code-samples/escrow/py/cancel_escrow.py index 38cd32a88c..6d154cde26 100644 --- a/_code-samples/escrow/py/cancel_escrow.py +++ b/_code-samples/escrow/py/cancel_escrow.py @@ -1,31 +1,128 @@ +import json +from datetime import datetime, timedelta, UTC +from time import sleep + from xrpl.clients import JsonRpcClient -from xrpl.models import EscrowCancel +from xrpl.models import EscrowCreate, EscrowCancel +from xrpl.models.requests import AccountObjects, Ledger, Tx from xrpl.transaction import submit_and_wait +from xrpl.utils import datetime_to_ripple_time, ripple_time_to_datetime, get_balance_changes from xrpl.wallet import generate_faucet_wallet -client = JsonRpcClient("https://s.altnet.rippletest.net:51234") # Connect to the testnetwork +# Set up client and get a wallet +client = JsonRpcClient("https://s.altnet.rippletest.net:51234") +print("Funding new wallet from faucet...") +wallet = generate_faucet_wallet(client, debug=True) +# 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. +destination_address = generate_faucet_wallet(client, debug=True).address -# Cancel an escrow -# An Escrow can only be canceled if it was created with a CancelAfter time - -escrow_sequence = 30215126 - -# Sender wallet object -sender_wallet = generate_faucet_wallet(client=client) - -# Build escrow cancel transaction -cancel_txn = EscrowCancel( - account=sender_wallet.address, - owner=sender_wallet.address, - offer_sequence=escrow_sequence +# Create an escrow that won't be finished ----------------------------------- +cancel_delay = 30 +cancel_after = datetime.now(tz=UTC) + timedelta(seconds=cancel_delay) +print("This escrow will expire after", cancel_after) +cancel_after_rippletime = datetime_to_ripple_time(cancel_after) +# Use a crypto-condition that nobody knows the fulfillment for +condition_hex = "A02580200000000000000000000000000000000000000000000000000000000000000000810120" +escrow_create = EscrowCreate( + account=wallet.address, + destination=destination_address, + amount="123456", # drops of XRP + condition=condition_hex, + cancel_after=cancel_after_rippletime ) +print("Signing and submitting the EscrowCreate transaction.") +response = submit_and_wait(escrow_create, client, wallet, autofill=True) +print(json.dumps(response.result, indent=2)) -# Autofill, sign, then submit transaction and wait for result -stxn_response = submit_and_wait(cancel_txn, client, sender_wallet) +result_code = response.result["meta"]["TransactionResult"] +if result_code != "tesSUCCESS": + print(f"EscrowCreate failed with result code {result_code}") + exit(1) -# Parse response and return result -stxn_result = stxn_response.result +# Wait for the escrow to be finishable -------------------------------------- +# Since ledger close times can be rounded by up to 10 seconds, wait an extra +# 10 seconds to make sure the escrow has officially expired. +sleep(cancel_delay + 10) -# Parse result and print out the transaction result and transaction hash -print(stxn_result["meta"]["TransactionResult"]) -print(stxn_result["hash"]) +# Look up the official close time of the validated ledger ------------------- +validated_ledger = client.request(Ledger(ledger_index="validated")) +close_time = validated_ledger.result["ledger"]["close_time"] +print("Latest validated ledger closed at", + ripple_time_to_datetime(close_time) +) +ledger_hash = validated_ledger.result["ledger"]["ledger_hash"] + +# Look up escrows connected to the account ---------------------------------- +expired_escrow = None +marker=None +while True: + try: + response = client.request(AccountObjects( + account=wallet.address, + ledger_hash=ledger_hash, + type="escrow", + marker=marker + )) + except Exception as e: + print(f"Error: account_objects failed: {e}") + exit(1) + + for escrow in response.result["account_objects"]: + if "CancelAfter" not in escrow: + print("This escrow does not have an expiration") + elif escrow["CancelAfter"] < close_time: + print("This escrow has expired.") + expired_escrow = escrow + break + else: + expiration_time = ripple_time_to_datetime(escrow["CancelAfter"]) + print(f"This escrow expires at {expiration_time}.") + + if "marker" in response.result.keys(): + marker=marker + else: + # This is the last page of results + break + +if not expired_escrow: + print("Did not find any expired escrows.") + exit(1) + +# Find the sequence number of the expired escrow ---------------------------- +response = client.request(Tx(transaction=escrow["PreviousTxnID"])) +if not response.is_successful(): + print("Couldn't get transaction. Maybe this server doesn't have enough " + "transaction history available?") + exit(1) + +if response.result["tx_json"]["TransactionType"] == "EscrowCreate": + # Save this sequence number for canceling the escrow + escrow_seq = response.result["tx_json"]["Sequence"] +else: + # Currently, this is impossible since no current transaction can update + # an escrow without finishing or canceling it. But in the future, if + # that becomes possible, you would have to look at the transaction + # metadata to find the previous transaction and repeat until you found + # the transaction that created the escrow. + print("The escrow's previous transaction wasn't Create?!") + exit(1) + +# Send EscrowCancel transaction --------------------------------------------- +escrow_cancel = EscrowCancel( + account=wallet.address, + owner=expired_escrow["Account"], + offer_sequence=escrow_seq +) +print("Signing and submitting the EscrowCancel transaction.") +response2 = submit_and_wait(escrow_cancel, client, wallet, autofill=True) +print(json.dumps(response2.result, indent=2)) + +result_code = response2.result["meta"]["TransactionResult"] +if result_code != "tesSUCCESS": + print(f"EscrowCancel failed with result code {result_code}") + exit(1) + +print("Escrow canceled. Balance changes:") +print(json.dumps(get_balance_changes(response2.result["meta"]), indent=2)) diff --git a/_code-samples/escrow/py/send_conditional_escrow.py b/_code-samples/escrow/py/send_conditional_escrow.py index 9863d47394..7b2b95691d 100644 --- a/_code-samples/escrow/py/send_conditional_escrow.py +++ b/_code-samples/escrow/py/send_conditional_escrow.py @@ -13,10 +13,10 @@ from xrpl.wallet import generate_faucet_wallet client = JsonRpcClient("https://s.altnet.rippletest.net:51234") print("Funding new wallet from faucet...") wallet = generate_faucet_wallet(client, debug=True) -destination_address = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe" # Testnet faucet +#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. -# destination_address = generate_faucet_wallet(client, debug=True).address +destination_address = generate_faucet_wallet(client, debug=True).address # Create the crypto-condition for release ----------------------------------- preimage = urandom(32) diff --git a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/cancel-an-expired-escrow.md b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/cancel-an-expired-escrow.md index 9b11580aa0..6e6105fec5 100644 --- a/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/cancel-an-expired-escrow.md +++ b/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/cancel-an-expired-escrow.md @@ -1,14 +1,67 @@ --- -html: cancel-an-expired-escrow.html -parent: use-escrows.html seo: description: Cancel an expired escrow. labels: - Escrow - - Smart Contracts --- # Cancel an Expired Escrow +This tutorial demonstrates how to cancel an [escrow](../../../../concepts/payment-types/escrow.md) that has passed its expiration time. You can use this to reclaim funds that you escrowed but were never claimed by the recipient. + +## Goals + +By following this tutorial, you should learn how to: + +- Compare a timestamp from the ledger to the current time. +- Cancel an expired 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. +- Already know how to send a [timed](./send-a-timed-escrow.md) or [conditional](./send-a-conditional-escrow.md) escrow. + +## Source Code + +You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/escrow/" %}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 %} + +{% tab label="Python" %} +From the code sample folder, set up a virtual environment and use `pip` to install dependencies: + +```sh +python -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` +{% /tab %} +{% /tabs %} + +***TODO: finish refactoring*** + + + + + + + + + + An escrow in the XRP Ledger is expired when its `CancelAfter` time is lower than the `close_time` of the latest validated ledger. Escrows without a `CancelAfter` time never expire. ## 1. Get the latest validated ledger diff --git a/redirects.yaml b/redirects.yaml index 5544da1d4e..ce9aabfb53 100644 --- a/redirects.yaml +++ b/redirects.yaml @@ -1,3 +1,6 @@ +/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/use-an-escrow-as-a-smart-contract: + to: /docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditional-escrow + type: 301 /docs/infrastructure/installation/rippled-1-3-migration-instructions/: to: /docs/infrastructure/installation/ type: 301 @@ -2335,6 +2338,9 @@ code_of_conduct.ja: type: 301 # Japanese +/ja/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/use-an-escrow-as-a-smart-contract: + to: /ja/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditional-escrow + type: 301 /ja/docs/infrastructure/installation/rippled-1-3-migration-instructions/: to: /ja/docs/infrastructure/installation/ type: 301