An escrow on the XRP Ledger allows users to lock XRP and release it under specific conditions. This feature enables conditional payments, ensuring funds are only transferred when criteria are met. If conditions aren't fulfilled, the escrow can be canceled, returning the funds to the sender.
Start coding
Create a new file or edit index.ts
import dayjs from 'dayjs';
import { Client, isoTimeToRippleTime, xrpToDrops } from 'xrpl';
import { generateConditionAndFulfillment, escrowTransaction } from './helpers';
const main = async () => {
console.log('lets get started...');
// Connect the client to the network
const client = new Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
const { wallet: walletOne } = await client.fundWallet();
const { wallet: walletTwo } = await client.fundWallet();
console.log({ walletOne, walletTwo });
};
main();
Create the escrow
In order to create an escrow on the XRP Ledger, you need to specify the amount of XRP to lock, the destination account, and the conditions for release, such as a time-based or cryptographic condition. Additionally, you must ensure the transaction is properly signed and submitted to the network, and verify its success to confirm the escrow is active.
// Time after which the destination user can claim the funds
const WAITING_TIME = 10; // seconds
// Define the time from when the Destination wallet can claim the money in the escrow. So here it would be 10 seconds after the escrow creation.
const finishAfter = dayjs().add(WAITING_TIME, 'seconds').toISOString();
// Generate the condition and fulfillment
const { condition, fulfillment } = generateConditionAndFulfillment();
const escrowCreateResponse = await escrowTransaction({
txn: {
Account: walletOne.address,
TransactionType: 'EscrowCreate',
Amount: xrpToDrops('1'),
Destination: walletTwo.address,
FinishAfter: isoTimeToRippleTime(finishAfter),
Condition: condition,
},
client,
wallet: walletOne,
});
// We need the sequence to finish an escrow, if it is not there, stop the function
if (!escrowCreateResponse.result.Sequence) {
await client.disconnect();
return;
}
Create a helpers.ts file and add the generateConditionAndFulfillment and escrowTransaction function:
import crypto from 'crypto';
import {
Client,
EscrowCreate,
EscrowFinish,
EscrowCancel,
Wallet,
Transaction,
} from 'xrpl';
// @ts-expect-error no types available
import cc from 'five-bells-condition';
export const generateConditionAndFulfillment = () => {
console.log(
"******* LET'S GENERATE A CRYPTO CONDITION AND FULFILLMENT *******"
);
console.log();
// use cryptographically secure random bytes generation
const preimage = crypto.randomBytes(32);
const fulfillment = new cc.PreimageSha256();
fulfillment.setPreimage(preimage);
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 (keep secret until you want to finish the escrow):',
fulfillment_hex
);
console.log();
return {
condition,
fulfillment: fulfillment_hex,
};
};
export type TransactionProps<T extends Transaction> = {
txn: T;
client: Client;
wallet: Wallet;
};
export const escrowTransaction = async <T extends Transaction>({
txn,
client,
wallet,
}: TransactionProps<T>) => {
const escrowResponse = await client.submitAndWait(txn, {
autofill: true,
wallet,
});
console.log(JSON.stringify(escrowResponse, null, 2));
return escrowResponse;
};
Finishing the escrow
To finish an escrow on the XRP Ledger, you must wait until the specified conditions are met, such as a time-based or cryptographic condition. Then, submit an "EscrowFinish" transaction, providing the necessary details like the condition, fulfillment, and sequence number, to release the locked funds to the designated recipient.
// Wait "WAITING_TIME" seconds before finishing the escrow
console.log(`Waiting ${WAITING_TIME} seconds`);
const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
await sleep(WAITING_TIME * 1000);
await escrowTransaction({
txn: {
Account: walletTwo.address,
TransactionType: 'EscrowFinish',
Condition: condition,
Fulfillment: fulfillment,
OfferSequence: escrowCreateResponse.result.Sequence,
Owner: walletOne.address,
},
client,
wallet: walletTwo, // Make sure this is the wallet which was in the "Destination" field during the escrow creation
});
console.log('Escrow transaction sent successfully');
await client.disconnect();
After this step, the escrow transaction has been successfully submitted to the XRP Ledger, signaling the completion of the escrow process. The funds should now be released to the designated recipient, provided all conditions have been met. The client is then disconnected from the network, indicating the end of the transaction session.
Extra credits
Canceling your escrow
If the conditions aren't met, you can cancel the escrow with the EscrowCancel transaction type:
await escrowTransaction({
txn: {
Account: walletOne.address, // The account submitting the cancel request
TransactionType: 'EscrowCancel',
Owner: walletOne.address, // The account that created the escrow
OfferSequence: escrowCreateResponse.result.Sequence, // The sequence number of the EscrowCreate transaction
},
client,
wallet: walletOne, // The wallet of the account that created the escrow
});
Use the escrow as a smart contract
You can use XRP Ledger escrows as smart contracts that release XRP after a certain time has passed or after a cryptographic condition has been fulfilled.