# Send a Conditionally-Held Escrow ## 1. Generate condition and fulfillment XRP Ledger escrows require PREIMAGE-SHA-256 [crypto-conditions](https://tools.ietf.org/html/draft-thomas-crypto-conditions-04). To calculate a condition and fulfillment in the proper format, you should use a crypto-conditions library such as [five-bells-condition](https://github.com/interledgerjs/five-bells-condition). To generate the fulfillment: - Use a cryptographically secure source of randomness to generate at least 32 random bytes. - Follow Interledger Protocol's [PSK specification](https://github.com/interledger/rfcs/blob/master/deprecated/0016-pre-shared-key/0016-pre-shared-key.md) and use an HMAC-SHA-256 of the ILP packet as the fulfillment. Example code for a random fulfillment and condition: JavaScript ```js const cc = require('five-bells-condition') const crypto = require('crypto') const preimageData = crypto.randomBytes(32) const fulfillment = new cc.PreimageSha256() fulfillment.setPreimage(preimageData) const condition = fulfillment.getConditionBinary().toString('hex').toUpperCase() console.log('Condition:', condition) // Keep secret until you want to finish the escrow const fulfillment_hex = fulfillment.serializeBinary().toString('hex').toUpperCase() console.log('Fulfillment:', fulfillment_hex) ``` Python ```py from os import urandom from cryptoconditions import PreimageSha256 secret = urandom(32) fulfillment = PreimageSha256(preimage=secret) print("Condition", fulfillment.condition_binary.hex().upper()) # Keep secret until you want to finish the escrow print("Fulfillment", fulfillment.serialize_binary().hex().upper()) ``` Save the condition and the fulfillment for later. Be sure to keep the fulfillment secret until you want to finish executing the held payment. Anyone who knows the fulfillment can finish the escrow, releasing the held funds to their intended destination. ## 2. Calculate release or cancel time A Conditional `Escrow` transaction must contain either a `CancelAfter` or `FinishAfter` field, or both. The `CancelAfter` field lets the XRP revert to the sender if the condition is not fulfilled before the specified time. The `FinishAfter` field specifies a time before which the escrow cannot execute, even if someone sends the correct fulfillment. Whichever field you provide, the time it specifies must be in the future. Example for setting a `CancelAfter` time of 24 hours in the future: JavaScript ```js const rippleOffset = 946684800 const CancelAfter = Math.floor(Date.now() / 1000) + (24*60*60) - rippleOffset console.log(CancelAfter) // Example: 556927412 ``` Python 2/3 ```python from time import time ripple_offset = 946684800 cancel_after = int(time()) + (24*60*60) - 946684800 print(cancel_after) # Example: 556927412 ``` In the XRP Ledger, you must specify time as **[seconds since the Ripple Epoch](/docs/references/protocol/data-types/basic-data-types#specifying-time)**. If you use a UNIX time in the `CancelAfter` or `FinishAfter` field without converting it, that sets the unlock time to an extra **30 years** in the future! ## 3. Submit EscrowCreate transaction [Sign and submit](/docs/concepts/transactions#signing-and-submitting-transactions) an [EscrowCreate transaction](/docs/references/protocol/transactions/types/escrowcreate). Set the `Condition` field of the transaction to the time when the held payment should be released. Set the `Destination` to the recipient, which can be the same address as the sender. Include the `CancelAfter` or `FinishAfter` time you calculated in the previous step. Set the `Amount` to the total amount of [XRP, in drops](/docs/references/protocol/data-types/basic-data-types#specifying-currency-amounts), to escrow. Caution Never submit a secret key to a server you do not control. Do not send a secret key unencrypted over the network. Websocket Request: { "id": 1, "command": "submit", "secret": "s████████████████████████████", "tx_json": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "TransactionType": "EscrowCreate", "Amount": "100000", "Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "CancelAfter": 556927412 } } Response: { "id": 1, "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": "120001228000000024000000052024213209B46140000000000186A068400000000000000A732103E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61744730450221008AC8BDC2151D5EF956197F0E6E89A4F49DEADC1AC38367870E444B1EA8D88D97022075E31427B455DFF87F0F22B849C71FC3987A91C19D63B6D0242E808347EC8A8F701127A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD81012081149A2AA667E1517EFA8A6B552AB2EDB859A99F26B283144B4E9C06F24296074F7BC48F92A97916C6DC5EA9", "tx_json": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Amount": "100000", "CancelAfter": 556927412, "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Fee": "10", "Flags": 2147483648, "Sequence": 5, "SigningPubKey": "03E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61", "TransactionType": "EscrowCreate", "TxnSignature": "30450221008AC8BDC2151D5EF956197F0E6E89A4F49DEADC1AC38367870E444B1EA8D88D97022075E31427B455DFF87F0F22B849C71FC3987A91C19D63B6D0242E808347EC8A8F", "hash": "E22D1F6EB006CAD35E0DBD3B4F3748427055E4C143EBE95AA6603823AEEAD324" } } } Javascript // Prepare EscrowCreate transaction ------------------------------------ const escrowCreateTransaction = { "TransactionType": "EscrowCreate", "Account": wallet.address, "Destination": wallet.address, "Amount": "6000000", //drops XRP "DestinationTag": 2023, "Condition": conditionHex, // Omit this for time-held escrows "Fee": "12", "FinishAfter": xrpl.isoTimeToRippleTime(finishAfter.toISOString()), }; xrpl.validate(escrowCreateTransaction); // Sign and submit the transaction ---------------------------------------- console.log('Signing and submitting the transaction:', JSON.stringify(escrowCreateTransaction, null, "\t"), "\n" ); const response = await client.submitAndWait(escrowCreateTransaction, { wallet }); console.log(`Sequence number: ${response.result.tx_json.Sequence}`); console.log(`Finished submitting! ${JSON.stringify(response.result, null, "\t")}`); Python # Build escrow create transaction create_txn = EscrowCreate( account=sender_wallet.address, amount=xrp_to_drops(amount_to_escrow), destination=receiver_addr, finish_after=claim_date, cancel_after=expiry_date, condition=condition # Omit this for time-held escrows ) # Autofill, sign, then submit transaction and wait for result stxn_response = submit_and_wait(create_txn, client, sender_wallet) # Return result of transaction stxn_result = stxn_response.result # Parse result and print out the neccesary info print(stxn_result["tx_json"]["Account"]) print(stxn_result["tx_json"]["Sequence"]) print(stxn_result["meta"]["TransactionResult"]) print(stxn_result["hash"]) ## 4. Wait for validation On a live network (including Mainnet, Testnet, or Devnet), you can wait 4-7 seconds for the ledger to close automatically. If you're running `rippled` in stand-alone mode, use the [ledger_accept method](/docs/references/http-websocket-apis/admin-api-methods/server-control-methods/ledger_accept) to manually close the ledger. ## 5. Confirm that the escrow was created Use the [tx method](/docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx) with the transaction's identifying hash to check its final status. In particular, look for a `CreatedNode` in the transaction metadata to indicate that it created an [Escrow ledger object](/docs/concepts/payment-types/escrow). Request: Websocket { "id": 3, "command": "tx", "transaction": "E22D1F6EB006CAD35E0DBD3B4F3748427055E4C143EBE95AA6603823AEEAD324" } Response: Websocket { "id": 3, "status": "success", "type": "response", "result": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Amount": "100000", "CancelAfter": 556927412, "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Fee": "10", "Flags": 2147483648, "Sequence": 5, "SigningPubKey": "03E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61", "TransactionType": "EscrowCreate", "TxnSignature": "30450221008AC8BDC2151D5EF956197F0E6E89A4F49DEADC1AC38367870E444B1EA8D88D97022075E31427B455DFF87F0F22B849C71FC3987A91C19D63B6D0242E808347EC8A8F", "date": 556841101, "hash": "E22D1F6EB006CAD35E0DBD3B4F3748427055E4C143EBE95AA6603823AEEAD324", "inLedger": 1772019, "ledger_index": 1772019, "meta": { "AffectedNodes": [ { "ModifiedNode": { "LedgerEntryType": "AccountRoot", "LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8", "PreviousTxnID": "52C4F626FE6F33699B6BE8ADF362836DDCE9B0B1294BFAA15D65D61501350BE6", "PreviousTxnLgrSeq": 1771204 } }, { "ModifiedNode": { "FinalFields": { "Flags": 0, "Owner": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "RootIndex": "4B4EBB6D8563075813D47491CC325865DFD3DC2E94889F0F39D59D9C059DD81F" }, "LedgerEntryType": "DirectoryNode", "LedgerIndex": "4B4EBB6D8563075813D47491CC325865DFD3DC2E94889F0F39D59D9C059DD81F" } }, { "ModifiedNode": { "FinalFields": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Balance": "9999798970", "Flags": 0, "OwnerCount": 1, "Sequence": 6 }, "LedgerEntryType": "AccountRoot", "LedgerIndex": "5F3B7107F4B524367A173A2B0EAB66E8CC4D2178C1B0C0528CB2F73A8B6BF254", "PreviousFields": { "Balance": "9999898980", "OwnerCount": 0, "Sequence": 5 }, "PreviousTxnID": "52C4F626FE6F33699B6BE8ADF362836DDCE9B0B1294BFAA15D65D61501350BE6", "PreviousTxnLgrSeq": 1771204 } }, { "CreatedNode": { "LedgerEntryType": "Escrow", "LedgerIndex": "E2CF730A31FD419382350C9DBD8DB7CD775BA5AA9B97A9BE9AB07304AA217A75", "NewFields": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Amount": "100000", "CancelAfter": 556927412, "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" } } } ], "TransactionIndex": 0, "TransactionResult": "tesSUCCESS" }, "validated": true } } ## 6. Submit EscrowFinish transaction [Sign and submit](/docs/concepts/transactions#signing-and-submitting-transactions) an [EscrowFinish transaction](/docs/references/protocol/transactions/types/escrowfinish) to execute the release of the funds after the `FinishAfter` time has passed. Set the `Owner` field of the transaction to the `Account` address from the EscrowCreate transaction, and the `OfferSequence` to the `Sequence` number from the EscrowCreate transaction. Set the `Condition` and `Fulfillment` fields to the condition and fulfillment values, in hexadecimal, that you generated in step 1. Set the `Fee` ([transaction cost](/docs/concepts/transactions/transaction-cost)) value based on the size of the fulfillment in bytes: a conditional EscrowFinish requires at least 330 drops of XRP plus 10 drops per 16 bytes in the size of the fulfillment. If you included a `FinishAfter` field in the EscrowCreate transaction, you cannot execute it before that time has passed, even if you provide the correct fulfillment for the Escrow's condition. The EscrowFinish transaction fails with the [result code](/docs/references/protocol/transactions/transaction-results) `tecNO_PERMISSION` if the previously-closed ledger's close time is before the `FinishAfter` time. If the escrow has expired, you can only [cancel the escrow](/docs/tutorials/how-tos/use-specialized-payment-types/use-escrows/cancel-an-expired-escrow) instead. Caution Never submit a secret key to a server you do not control. Do not send a secret key unencrypted over the network. Websocket Request: { "id": 4, "command": "submit", "secret": "s████████████████████████████", "tx_json": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "TransactionType": "EscrowFinish", "Owner": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "OfferSequence": 5, "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "Fulfillment": "A0228020D280D1A02BAD0D2EBC0528B92E9BF37AC3E2530832C2C52620307135156F1048", "Fee": "500" } } Response: { "id": 4, "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": "120002228000000024000000062019000000056840000000000001F4732103E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B617446304402207DE4EA9C8655E75BA01F96345B3F62074313EB42C15D9C4871E30F02202D2BA50220070E52AD308A31AC71E33BA342F31B68D1D1B2A7A3A3ED6E8552CA3DCF14FBB2701024A0228020D280D1A02BAD0D2EBC0528B92E9BF37AC3E2530832C2C52620307135156F1048701127A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD81012081149A2AA667E1517EFA8A6B552AB2EDB859A99F26B282149A2AA667E1517EFA8A6B552AB2EDB859A99F26B2", "tx_json": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "Fee": "500", "Flags": 2147483648, "Fulfillment": "A0228020D280D1A02BAD0D2EBC0528B92E9BF37AC3E2530832C2C52620307135156F1048", "OfferSequence": 5, "Owner": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Sequence": 6, "SigningPubKey": "03E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61", "TransactionType": "EscrowFinish", "TxnSignature": "304402207DE4EA9C8655E75BA01F96345B3F62074313EB42C15D9C4871E30F02202D2BA50220070E52AD308A31AC71E33BA342F31B68D1D1B2A7A3A3ED6E8552CA3DCF14FBB2", "hash": "0E88368CAFC69A722ED829FAE6E2DD3575AE9C192691E60B5ACDF706E219B2BF" } } } Javascript // 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")}`); Python # 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"]) 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. ## 7. Wait for validation On a live network (including Mainnet, Testnet, or Devnet), you can wait 4-7 seconds for the ledger to close automatically. If you're running `rippled` in stand-alone mode, use the [ledger_accept method](/docs/references/http-websocket-apis/admin-api-methods/server-control-methods/ledger_accept) to manually close the ledger. ## 8. Confirm final result Use the [tx method](/docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx) with the EscrowFinish transaction's identifying hash to check its final status. In particular, look in the transaction metadata for a `ModifiedNode` of type `AccountRoot` for the destination of the escrowed payment. The `FinalFields` of the object should show the increase in XRP in the `Balance` field. Request: { "id": 20, "command": "tx", "transaction": "0E88368CAFC69A722ED829FAE6E2DD3575AE9C192691E60B5ACDF706E219B2BF" } Response: { "id": 20, "status": "success", "type": "response", "result": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "Fee": "500", "Flags": 2147483648, "Fulfillment": "A0228020D280D1A02BAD0D2EBC0528B92E9BF37AC3E2530832C2C52620307135156F1048", "OfferSequence": 2, "Owner": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Sequence": 4, "SigningPubKey": "03E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61", "TransactionType": "EscrowFinish", "TxnSignature": "3045022100925FEBE21C2E57F81C472A4E5869CAB1D0164C472A46532F39F6F9F7ED6846D002202CF9D9063ADC4CC0ADF4C4692B7EE165C5D124CAA855649389E245D993F41D4D", "date": 556838610, "hash": "0E88368CAFC69A722ED829FAE6E2DD3575AE9C192691E60B5ACDF706E219B2BF", "inLedger": 1771204, "ledger_index": 1771204, "meta": { "AffectedNodes": [ { "ModifiedNode": { "FinalFields": { "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Balance": "400100000", "Flags": 0, "OwnerCount": 0, "Sequence": 1 }, "LedgerEntryType": "AccountRoot", "LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8", "PreviousFields": { "Balance": "400000000" }, "PreviousTxnID": "795CBC8AFAAB9DC7BD9944C7FAEABF9BB0802A84520BC649213AD6A2C3256C95", "PreviousTxnLgrSeq": 1770775 } }, { "ModifiedNode": { "FinalFields": { "Flags": 0, "Owner": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "RootIndex": "4B4EBB6D8563075813D47491CC325865DFD3DC2E94889F0F39D59D9C059DD81F" }, "LedgerEntryType": "DirectoryNode", "LedgerIndex": "4B4EBB6D8563075813D47491CC325865DFD3DC2E94889F0F39D59D9C059DD81F" } }, { "ModifiedNode": { "FinalFields": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Balance": "9999898980", "Flags": 0, "OwnerCount": 0, "Sequence": 5 }, "LedgerEntryType": "AccountRoot", "LedgerIndex": "5F3B7107F4B524367A173A2B0EAB66E8CC4D2178C1B0C0528CB2F73A8B6BF254", "PreviousFields": { "Balance": "9999899480", "OwnerCount": 1, "Sequence": 4 }, "PreviousTxnID": "5C2A1E7B209A7404D3722A010D331A8C1C853109A47DDF620DE5E3D59F026581", "PreviousTxnLgrSeq": 1771042 } }, { "DeletedNode": { "FinalFields": { "Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB", "Amount": "100000", "Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120", "Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "FinishAfter": 556838185, "Flags": 0, "OwnerNode": "0000000000000000", "PreviousTxnID": "795CBC8AFAAB9DC7BD9944C7FAEABF9BB0802A84520BC649213AD6A2C3256C95", "PreviousTxnLgrSeq": 1770775 }, "LedgerEntryType": "Escrow", "LedgerIndex": "DC524D17B3F650E7A215B332F418E54AE59B0DFC5392E74958B0037AFDFE8C8D" } } ], "TransactionIndex": 1, "TransactionResult": "tesSUCCESS" }, "validated": true } } ## See Also - [Crypto-Conditions Specification](https://tools.ietf.org/html/draft-thomas-crypto-conditions-04) - **Concepts:** - [What is XRP?](/docs/introduction/what-is-xrp) - [Payment Types](/docs/concepts/payment-types) - [Escrow](/docs/concepts/payment-types/escrow) - **Tutorials:** - [Send XRP](/docs/tutorials/how-tos/send-xrp) - [Look Up Transaction Results](/docs/concepts/transactions/finality-of-results/look-up-transaction-results) - [Reliable Transaction Submission](/docs/concepts/transactions/reliable-transaction-submission) - **References:** - [EscrowCancel transaction](/docs/references/protocol/transactions/types/escrowcancel) - [EscrowCreate transaction](/docs/references/protocol/transactions/types/escrowcreate) - [EscrowFinish transaction](/docs/references/protocol/transactions/types/escrowfinish) - [account_objects method](/docs/references/http-websocket-apis/public-api-methods/account-methods/account_objects) - [tx method](/docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx) - [Escrow ledger object](/docs/references/protocol/ledger-data/ledger-entry-types/escrow)