Update cancel escrow tutorial w/ rewritten (Python) sample code

This commit is contained in:
mDuo13
2025-12-05 16:52:40 -08:00
parent fbf3668e57
commit 22c883fd67
19 changed files with 81 additions and 565 deletions

View File

@@ -1,119 +0,0 @@
---
html: cancel-an-expired-escrow.html
parent: use-escrows.html
seo:
description: 有効期限切れのEscrowを取り消します。
labels:
- Escrow
- スマートコントラクト
---
# 有効期限切れEscrowの取消し
## 1.有効期限切れEscrowの確認
XRP LedgerのEscrowが有効期限切れとなるのは、その`CancelAfter`の時刻が検証済みレジャーの`close_time`よりも前である場合です。Escrowに`CancelAfter`時刻が指定されていない場合は、Escrowが有効期限切れになることはありません。最新の検証済みレジャーの閉鎖時刻は、[ledgerメソッド][]を使用して検索できます。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-request-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-response-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
[account_objectsメソッド][]を使用してEscrowを検索し、`CancelAfter`の時刻と比較できます。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-request-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-response-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 2.EscrowCancelトランザクションの送信
XRP Ledgerでは、[EscrowCancelトランザクション][]に[署名して送信する](../../../../concepts/transactions/index.md#トランザクションへの署名とトランザクションの送信)ことで、***誰でも***有効期限切れのEscrowを取り消すことができます。トランザクションの`Owner`フィールドを、そのEscrowを作成した`EscrowCreate`トランザクションの`Account`に設定します。`OfferSequence`フィールドを、`EscrowCreate`トランザクションの`Sequence`に設定します。
{% partial file="/@l10n/ja/docs/_snippets/secret-key-warning.md" /%}
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
トランザクションの識別用`hash`値をメモしておきます。これにより、検証済みレジャーバージョンに記録されるときにその最終ステータスを確認できます。
## 3.検証の待機
{% partial file="/@l10n/ja/docs/_snippets/wait-for-validation.md" /%}
## 4.最終結果の確認
EscrowCancelトランザクションの識別用ハッシュを指定した[txメソッド][]を使用してトランザクションの最終ステータスを確認します。トランザクションのメタデータで`LedgerEntryType``Escrow`である`DeletedNode`を探します。また、エスクローに預託された支払いの送金元の`ModifiedNode`(タイプが`AccountRoot`)も探します。オブジェクトの`FinalFields`に、`Balance`フィールドのXRP返金額の増分が表示されている必要があります。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
上記の例では、`r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT`がEscrowの送金元であり、`Balance`が99999**8**9990 dropから99999**9**9990 dropに増加していることから、エスクローに預託されていた10,000 XRP dropが返金されたことがわかりますdrop = 0.01XRP
{% admonition type="success" name="ヒント" %}Escrowを実行する[EscrowFinishトランザクション][]で使用する`OfferSequence`が不明な場合は、Escrowの`PreviousTxnID`フィールドのトランザクションの識別用ハッシュを指定した[txメソッド][]を使用して、そのEscrowを作成したトランザクションを検索します。Escrowを終了するときには、そのトランザクションの`Sequence`の値を`OfferSequence`の値として使用します。{% /admonition %}
{% raw-partial file="/@l10n/ja/docs/_snippets/common-links.md" /%}

View File

@@ -1,7 +0,0 @@
{
"id": 2,
"command": "account_objects",
"account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"ledger_index": "validated",
"type": "escrow"
}

View File

@@ -1,26 +0,0 @@
{
"id": 2,
"status": "success",
"type": "response",
"result": {
"account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"account_objects": [
{
"Account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"Amount": "10000",
"CancelAfter": 559913895,
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"FinishAfter": 559892324,
"Flags": 0,
"LedgerEntryType": "Escrow",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "4756C22BBB7FC23D9081FDB180806939D6FEBC967BE0EC2DB95B166AF9C086E9",
"PreviousTxnLgrSeq": 2764813,
"index": "7243A9750FA4BE3E63F75F6DACFD79AD6B6C76947F6BDC46CD0F52DBEEF64C89"
}
],
"ledger_hash": "82F24FFA72AED16F467BBE79D387E92FDA39F29038B26E79464CDEDFB506E366",
"ledger_index": 2764826,
"validated": true
}
}

View File

@@ -1,5 +0,0 @@
{
"id": 4,
"command": "ledger",
"ledger_index": "validated"
}

View File

@@ -1,19 +0,0 @@
{
"id": 1,
"status": "success",
"type": "response",
"result": {
"ledger": {
# ... (trimmed) ...
"close_time": 560302643,
"close_time_human": "2017-Oct-02 23:37:23",
"close_time_resolution": 10,
# ... (trimmed) ...
},
"ledger_hash": "668F0647A6F3CC277496245DBBE9BD2E3B8E70E7AA824E97EF3237FE7E1EE3F2",
"ledger_index": 2906341,
"validated": true
}
}

View File

@@ -1,11 +0,0 @@
{
"id": 5,
"command": "submit",
"secret": "s████████████████████████████",
"tx_json": {
"Account": "rhgdnc82FwHFUKXp9ZcpgwXWRAxKf5Buqp",
"TransactionType": "EscrowCancel",
"Owner": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"OfferSequence": 1
}
}

View File

@@ -1,23 +0,0 @@
{
"id": 5,
"status": "success",
"type": "response",
"result": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"tx_blob": "1200042280000000240000000320190000000168400000000000000A7321027FB1CF34395F18901CD294F77752EEE25277C6E87A224FC7388AA7EF872DB43D74473045022100AC45749FC4291F7811B2D8AC01CA04FEE38910CB7216FB0C5C0AEBC9C0A95F4302203F213C71C00136A0ADC670EFE350874BCB2E559AC02059CEEDFB846685948F2B81142866B7B47574C8A70D5E71FFB95FFDB18951427B82144E87970CD3EA984CF48B1AA6AB6C77DC4AB059FC",
"tx_json": {
"Account": "rhgdnc82FwHFUKXp9ZcpgwXWRAxKf5Buqp",
"Fee": "10",
"Flags": 2147483648,
"OfferSequence": 1,
"Owner": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"Sequence": 3,
"SigningPubKey": "027FB1CF34395F18901CD294F77752EEE25277C6E87A224FC7388AA7EF872DB43D",
"TransactionType": "EscrowCancel",
"TxnSignature": "3045022100AC45749FC4291F7811B2D8AC01CA04FEE38910CB7216FB0C5C0AEBC9C0A95F4302203F213C71C00136A0ADC670EFE350874BCB2E559AC02059CEEDFB846685948F2B",
"hash": "65F36C5514153D94F0ADE5CE747061A5E70B73B56B4C66DA5040D99CAF252831"
}
}
}

View File

@@ -1,5 +0,0 @@
{
"id": 6,
"command": "tx",
"transaction": "65F36C5514153D94F0ADE5CE747061A5E70B73B56B4C66DA5040D99CAF252831"
}

View File

@@ -1,101 +0,0 @@
{
"id": 6,
"status": "success",
"type": "response",
"result": {
"Account": "rhgdnc82FwHFUKXp9ZcpgwXWRAxKf5Buqp",
"Fee": "10",
"Flags": 2147483648,
"OfferSequence": 1,
"Owner": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"Sequence": 3,
"SigningPubKey": "027FB1CF34395F18901CD294F77752EEE25277C6E87A224FC7388AA7EF872DB43D",
"TransactionType": "EscrowCancel",
"TxnSignature": "3045022100AC45749FC4291F7811B2D8AC01CA04FEE38910CB7216FB0C5C0AEBC9C0A95F4302203F213C71C00136A0ADC670EFE350874BCB2E559AC02059CEEDFB846685948F2B",
"date": 560302841,
"hash": "65F36C5514153D94F0ADE5CE747061A5E70B73B56B4C66DA5040D99CAF252831",
"inLedger": 2906406,
"ledger_index": 2906406,
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
"PreviousTxnID": "4756C22BBB7FC23D9081FDB180806939D6FEBC967BE0EC2DB95B166AF9C086E9",
"PreviousTxnLgrSeq": 2764813
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rhgdnc82FwHFUKXp9ZcpgwXWRAxKf5Buqp",
"Balance": "9999999970",
"Flags": 0,
"OwnerCount": 0,
"Sequence": 4
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "3430FA3A160FA8F9842FA4A8B5549ECDCB3783E585D0F9796A1736DEAE35F6FE",
"PreviousFields": {
"Balance": "9999999980",
"Sequence": 3
},
"PreviousTxnID": "DA6F5CA8CE13A03B8BC58515E085F2FEF90B3C08230B5AEC8DE4FAF39F79010B",
"PreviousTxnLgrSeq": 2906391
}
},
{
"DeletedNode": {
"FinalFields": {
"Account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"Amount": "10000",
"CancelAfter": 559913895,
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"FinishAfter": 559892324,
"Flags": 0,
"OwnerNode": "0000000000000000",
"PreviousTxnID": "4756C22BBB7FC23D9081FDB180806939D6FEBC967BE0EC2DB95B166AF9C086E9",
"PreviousTxnLgrSeq": 2764813
},
"LedgerEntryType": "Escrow",
"LedgerIndex": "7243A9750FA4BE3E63F75F6DACFD79AD6B6C76947F6BDC46CD0F52DBEEF64C89"
}
},
{
"ModifiedNode": {
"FinalFields": {
"Flags": 0,
"Owner": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"RootIndex": "DACDBEBD31D14EAC4207A45DB88734AD14D26D908507F41D2FC623BDD91C582F"
},
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "DACDBEBD31D14EAC4207A45DB88734AD14D26D908507F41D2FC623BDD91C582F"
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"Balance": "9999999990",
"Flags": 0,
"OwnerCount": 0,
"Sequence": 2
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "F5F1834B80A8B5DA878270AB4DE4EA444281181349375F1D21E46D5F3F0ABAC8",
"PreviousFields": {
"Balance": "9999989990",
"OwnerCount": 1
},
"PreviousTxnID": "4756C22BBB7FC23D9081FDB180806939D6FEBC967BE0EC2DB95B166AF9C086E9",
"PreviousTxnLgrSeq": 2764813
}
}
],
"TransactionIndex": 2,
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
}

View File

@@ -1,60 +0,0 @@
'use strict'
const xrpl = require('xrpl')
// Preqrequisites:
// 1. Create an escrow using the create-escrow.js snippet
// 2. Replace the OfferSequence with the sequence number of the escrow you created
// 3. Replace the Condition and Fulfillment with the values from the escrow you created
// 4. Paste the seed of the account that created the escrow
// 5. Run the snippet
const seed = "sEd7jfWyNG6J71dEojB3W9YdHp2KCjy"; // Test seed. Don't use
const offerSequence = null;
const condition = "";
const fulfillment = "";
const main = async () => {
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);
if((!offerSequence)|| (condition === "" || fulfillment === "")){
throw new Error("Please specify the sequence number, condition and fulfillment of the escrow you created");
};
// Prepare EscrowFinish transaction ---------------------------------
const escrowFinishTransaction = {
"Account": wallet.address,
"TransactionType": "EscrowFinish",
"Owner": wallet.address,
// This should equal the sequence number of the escrow transaction
"OfferSequence": offerSequence,
// Crypto condition that must be met before escrow can be completed, passed on escrow creation.
// Omit this for time-held escrows.
"Condition": condition,
// Fulfillment of the condition, passed on escrow creation.
// Omit this for time-held escrows.
"Fulfillment": fulfillment,
};
xrpl.validate(escrowFinishTransaction);
// Sign and submit the transaction ----------------------------------------
console.log('Signing and submitting the transaction:', JSON.stringify(escrowFinishTransaction, null, "\t"));
const response = await client.submitAndWait(escrowFinishTransaction, { wallet });
console.log(`Finished submitting! ${JSON.stringify(response.result, null, "\t")}`);
await client.disconnect();
} catch (error) {
console.log(error);
}
}
main()

View File

@@ -1,9 +0,0 @@
const cc = require('five-bells-condition')
const crypto = require('crypto')
const preimageData = crypto.randomBytes(32)
const myFulfillment = new cc.PreimageSha256()
myFulfillment.setPreimage(preimageData)
console.log('Condition:', myFulfillment.getConditionBinary().toString('hex').toUpperCase())
console.log('Fulfillment:', myFulfillment.serializeBinary().toString('hex').toUpperCase())

View File

@@ -98,7 +98,10 @@ const response2 = await client.submitAndWait(escrowFinish, {
})
console.log(JSON.stringify(response2.result, null, 2))
if (response2.result.meta.TransactionResult === 'tesSUCCESS') {
console.log('Escrow finished successfully.')
console.log('Escrow finished successfully. Balance changes:')
console.log(
JSON.stringify(xrpl.getBalanceChanges(response2.result.meta), null, 2)
)
}
client.disconnect()

View File

@@ -41,7 +41,7 @@ if result_code != "tesSUCCESS":
print(f"EscrowCreate failed with result code {result_code}")
exit(1)
# Wait for the escrow to be finishable --------------------------------------
# Wait for the escrow to expire ---------------------------------------------
# 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)
@@ -100,13 +100,16 @@ if not response.is_successful():
if response.result["tx_json"]["TransactionType"] == "EscrowCreate":
# Save this sequence number for canceling the escrow
escrow_seq = response.result["tx_json"]["Sequence"]
if escrow_seq == 0:
# This transaction used a Ticket, so use the TicketSequence instead.
escrow_seq = response.result["tx_json"]["TicketSequence"]
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?!")
print("The escrow's previous transaction wasn't EscrowCreate!")
exit(1)
# Send EscrowCancel transaction ---------------------------------------------

View File

@@ -1,44 +0,0 @@
from xrpl.clients import JsonRpcClient
from xrpl.models import EscrowFinish
from xrpl.transaction import submit_and_wait
from xrpl.wallet import generate_faucet_wallet
client = JsonRpcClient("https://s.altnet.rippletest.net:51234") # Connect to the testnetwork
# Complete an escrow
# Cannot be called until the finish time is reached
# Required fields (modify to match an escrow you create)
escrow_creator = generate_faucet_wallet(client=client).address
escrow_sequence = 27641268
# Optional fields
# Crypto condition that must be met before escrow can be completed, passed on escrow creation
condition = "A02580203882E2EB9B44130530541C4CC360D079F265792C4A7ED3840968897CB7DF2DA1810120"
# Crypto fulfillment of the condtion
fulfillment = "A0228020AED2C5FE4D147D310D3CFEBD9BFA81AD0F63CE1ADD92E00379DDDAF8E090E24C"
# Sender wallet object
sender_wallet = generate_faucet_wallet(client=client)
# Build escrow finish transaction
finish_txn = EscrowFinish(
account=sender_wallet.address,
owner=escrow_creator,
offer_sequence=escrow_sequence, # The sequence number of the escrow transaction
condition=condition, # Omit this for time-held escrows
fulfillment=fulfillment # Omit this for time-held escrows
)
# Autofill, sign, then submit transaction and wait for result
stxn_response = submit_and_wait(finish_txn, client, sender_wallet)
# Parse response and return result
stxn_result = stxn_response.result
# Parse result and print out the transaction result and transaction hash
print(stxn_result["meta"]["TransactionResult"])
print(stxn_result["hash"])

View File

@@ -1,24 +0,0 @@
from xrpl.clients import JsonRpcClient
from xrpl.models import Tx
client = JsonRpcClient("https://s.altnet.rippletest.net:51234") # Connect to the testnetwork
prev_txn_id = "" # should look like this '84503EA84ADC4A65530C6CC91C904FCEE64CFE2BB973C023476184288698991F'
# Return escrow seq from `PreviousTxnID` for finishing or cancelling escrows
if prev_txn_id == "":
print("No transaction id provided. Use create_escrow.py to generate an escrow transaction, then you can look it up by modifying prev_txn_id to use that transaction's id.")
# Build and send query for PreviousTxnID
req = Tx(transaction=prev_txn_id)
response = client.request(req)
# Return the result
result = response.result
# Print escrow sequence if available
if "Sequence" in result:
print(f'escrow sequence: {result["Sequence"]}')
# Use escrow ticket sequence if escrow sequence is not available
if "TicketSequence" in result:
print(f'escrow ticket sequence: {result["TicketSequence"]}')

View File

@@ -54,7 +54,7 @@ if result_code != "tesSUCCESS":
# Save the sequence number so you can identify the escrow later
escrow_seq = response.result["tx_json"]["Sequence"]
# Send the EscrowFinish transaction -----------------------------------------
# Send EscrowFinish transaction ---------------------------------------------
escrow_finish = EscrowFinish(
account=wallet.address,
owner=wallet.address,

View File

@@ -6,7 +6,7 @@ labels:
---
# 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.
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, or to remove an expired escrow that is stopping you from deleting your account.
## Goals
@@ -51,134 +51,92 @@ pip install -r requirements.txt
{% /tab %}
{% /tabs %}
***TODO: finish refactoring***
### 2. Set up client and account
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
Use the [ledger method][] to look up the latest validated ledger and get the `close_time` value.
Request:
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. You also need the address of another account to send the escrow to. You can fund a second account using the faucet, or use the address of an existing account like the faucet.
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-request-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
Response:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-response-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 2. Look up the escrow
Use the [account_objects method][] and compare `CancelAfter` to `close_time`:
Request:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-request-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
Response:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-response-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 3. Submit EscrowCancel transaction
***Anyone*** can cancel an expired escrow in the XRP Ledger by sending an [EscrowCancel transaction][]. Set the `Owner` field of the transaction to the `Account` of the `EscrowCreate` transaction that created this escrow. Set the `OfferSequence` field to the `Sequence` of the `EscrowCreate` transaction.
{% admonition type="success" name="Tip" %}If you don't know what `OfferSequence` to use, you can look up the transaction that created the Escrow: call the [tx method][] with the value of the Escrow's `PreviousTxnID` field. In `tx` response, use the `Sequence` value of that transaction as the `OfferSequence` value of the EscrowCancel transaction.{% /admonition %}
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
{% tabs %}
{% tab label="Websocket" %}
Request:
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowcancel.json" language="json" /%}
Response:
{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowcancel.json" language="json" /%}
{% /tab %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/escrow/js/cancel-escrow.js" language="js" from="const escrowCancelTransaction" before="await client.disconnect" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" from="# Build escrow cancel" /%}
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" before="# Create an escrow" /%}
{% /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. Create an escrow
## 4. Wait for validation
For purposes of this tutorial, you need an escrow to cancel, so create one that won't be finished before it expires. The sample code uses a conditional escrow with a made-up condition full of zeroes, so nobody knows the fulfillment, and an expiration time 30 seconds into the future. A timed escrow could also work, but it's possible someone else would finish the escrow between its maturity and expiration time.
{% raw-partial file="/docs/_snippets/wait-for-validation.md" /%}
## 5. Confirm final result
Use the [tx method][] with the `EscrowCancel` transaction's identifying hash to check its final status. Look in the transaction metadata for a `DeletedNode` with `LedgerEntryType` of `Escrow`. Also look for a `ModifiedNode` of type `AccountRoot` for the sender of the escrowed payment. The `FinalFields` of the object should show the increase in XRP in the `Balance` field for the returned XRP.
Request:
Anyone can cancel _any_ expired escrow; you don't have to be the sender or receiver. That said, the sender has the most financial incentive to do so, since they get the funds back.
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowcancel.json" language="json" /%}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" from="# Create an escrow" before="# Wait for the escrow to expire" /%}
{% /tab %}
{% /tabs %}
Response:
{% admonition type="success" name="Tip" %}For a more detailed explanation of creating an escrow, see [Send a Timed Escrow](./send-a-timed-escrow.md) or [Send a Conditional Escrow](./send-a-conditional-escrow.md).{% /admonition %}
### 4. Wait for the escrow to expire
An escrow can only be canceled after it has expired, so you have to wait until its `CancelAfter` (expiration) time has passed. Since the expiration time is compared to the official close time of the previous ledger, which may be rounded up to 10 seconds, waiting an extra 10 seconds makes it very likely that the escrow has officially expired.
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowcancel.json" language="json" /%}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" from="# Wait for the escrow to expire" before="# Look up the official close time" /%}
{% /tab %}
{% /tabs %}
In the above example, `r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT` is the sender of the escrow, and the increase in `Balance` from 99999**8**9990 drops to 99999**9**9990 drops represents the return of the escrowed 10,000 drops of XRP (0.01 XRP).
### 5. Look up the official close time of the latest validated ledger
Use the [ledger method][] to get the official close time of the most recently validated ledger version. You can use this number to confirm when an escrow has officially expired.
{% tabs %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" from="# Look up the official close time" before="# Look up escrows" /%}
{% /tab %}
{% /tabs %}
### 6. Look for expired escrows by account
This is one of several ways to find expired escrows. Use the [account_objects method][] to look up escrows linked to your account. (This includes both incoming and outgoing escrows, potentially.) You may need to look through multiple [paginated][Marker] results if you have a lot of objects linked to your account.
{% tabs %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" from="# Look up escrows" before="# Find the sequence number" /%}
{% /tab %}
{% /tabs %}
### 7. Find the sequence number of the expired escrow
To cancel an escrow, you need to know its owner and the sequence number of the transaction that created it. If you already know the sequence number (for example, you saved it when you created the escrow) you can skip this step. The sample code shows how you can look it up for an unknown escrow using the escrow ledger entry's transaction history.
The `PreviousTxnID` field contains the identifying hash of the last transaction to modify the escrow. Generally, this is the EscrowCreate transaction, so you can look up that transaction, using the [tx method][] to get the sequence number from the `Sequence` field. If the transaction used a [Ticket](../../../../concepts/accounts/tickets.md), then the `Sequence` field has a value of `0` and you need to use value of the `TicketSequence` field instead.
{% admonition type="success" name="Tip" %}The `IncludeKeyletFields` amendment, expected to be released in `rippled` 3.0.0, adds a `Sequence` field to new Escrow ledger entries. For any escrow created after that amendment goes live, you can get the sequence number directly from that field.{% /admonition %}
In the case that the previous transaction is not an EscrowCreate transaction, you can use _that_ transaction's metadata to find the prior value of the same escrow's `PreviousTxnID`, and repeat the process until you find the actual EscrowCreate. In the current XRP Ledger protocol (as of late 2025), this case is extremely rare to impossible, so the sample code does not demonstrate this process.
{% tabs %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" from="# Find the sequence number" before="# Send EscrowCancel transaction" /%}
{% /tab %}
{% /tabs %}
### 8. Cancel the escrow
Once you have all the necessary information, send an [EscrowCancel transaction][] to cancel the escrow. If the transaction succeeds, it deletes the escrow entry and returns the escrowed funds to their source.
{% tabs %}
{% tab label="Python" %}
In xrpl-py, you can use the [`get_balance_changes(metadata)`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.utils.html#xrpl.utils.get_balance_changes) utility to parse the validated transaction's metadata for a simplified list of balance changes.
{% code-snippet file="/_code-samples/escrow/py/cancel_escrow.py" language="py" from="# Send EscrowCancel 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)
- [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)
@@ -188,7 +146,8 @@ In the above example, `r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT` is the sender of the
- [EscrowCreate transaction][]
- [EscrowFinish transaction][]
- [account_objects method][]
- [ledger method][]
- [tx method][]
- [Escrow ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
- [Escrow ledger entry](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -22,7 +22,7 @@ By following this tutorial, you should learn how to:
To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger
- Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library, such as **xrpl.js**, installed.
## Source Code

View File

@@ -21,7 +21,7 @@ By following this tutorial, you should learn how to:
To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger
- Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library, such as **xrpl.js**, installed.
## Source Code
@@ -154,10 +154,14 @@ Now that the escrow is mature, you can finish it. Construct an [EscrowFinish tra
{% tabs %}
{% tab label="JavaScript" %}
In xrpl.js, you can use the [`getBalanceChanges(metadata)`](https://js.xrpl.org/functions/getBalanceChanges.html) utility to parse the validated transaction's metadata for a simplified list of balance changes.
{% code-snippet file="/_code-samples/escrow/js/send-timed-escrow.js" language="js" from="// Send EscrowFinish transaction" /%}
{% /tab %}
{% tab label="Python" %}
In xrpl-py, you can use the [`get_balance_changes(metadata)`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.utils.html#xrpl.utils.get_balance_changes) utility to parse the validated transaction's metadata for a simplified list of balance changes.
{% code-snippet file="/_code-samples/escrow/py/send_timed_escrow.py" language="py" from="# Send EscrowFinish transaction" /%}
{% /tab %}
{% /tabs %}