Mesh LogoMesh

Escrow

Enable secure peer-to-peer asset exchanges with multi-signature verification

The Escrow contract enables two parties to securely exchange assets. One party initiates the escrow by depositing assets, the other party deposits their assets, and both parties sign to complete the exchange. Either party can cancel before completion to retrieve their assets.

Use Cases

  • Peer-to-peer trading without intermediaries
  • Trustless asset swaps
  • Secure over-the-counter (OTC) deals
  • Cross-asset exchanges (NFT for ADA, token for token)
  • Multi-party agreements

Quick Start

Install the Package

npm install @meshsdk/contract @meshsdk/core

Initialize the Contract

import { MeshEscrowContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";

const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
});

const contract = new MeshEscrowContract({
  mesh: meshTxBuilder,
  fetcher: provider,
  wallet: wallet,
  networkId: 0,
});

Contract Logic

The Escrow contract follows a multi-step process:

StepActionDescription
1InitiateParty A deposits assets and creates the escrow
2DepositParty B deposits their assets into the same escrow
3CompleteBoth parties sign to swap assets

Cancellation Rules

  • Either party can cancel at any time before completion
  • Cancellation returns all assets to their original owners
  • Partial escrows (only one deposit) return assets to the depositor

Completion Requirements

  • Both parties must have deposited assets
  • Both parties must sign the completion transaction
  • This is a multi-signature transaction

Available Actions

Initiate Escrow

Start a new escrow by depositing your assets.

Method Signature

contract.initiateEscrow(escrowAmount: Asset[]): Promise<string>

Parameters

ParameterTypeDescription
escrowAmountAsset[]Array of assets to deposit

Code Example

import { MeshEscrowContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder, Asset } from "@meshsdk/core";

// Initialize provider and contract
const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
});

const contract = new MeshEscrowContract({
  mesh: meshTxBuilder,
  fetcher: provider,
  wallet: wallet,
  networkId: 0,
});

// Define assets to deposit (Party A offers 10 ADA)
const escrowAmount: Asset[] = [
  {
    unit: "lovelace",
    quantity: "10000000",
  },
];

// Initiate the escrow
const tx = await contract.initiateEscrow(escrowAmount);
const signedTx = await wallet.signTx(tx);
const txHash = await wallet.submitTx(signedTx);

console.log("Escrow initiated. Transaction hash:", txHash);
// Share this txHash with Party B

What Happens on Success

  • Assets transfer from Party A's wallet to the contract
  • Party A's address is recorded in the escrow datum
  • Transaction hash is needed for Party B to deposit

Recipient Deposit

Party B deposits their assets into an existing escrow.

Method Signature

contract.recipientDeposit(escrowUtxo: UTxO, depositAmount: Asset[]): Promise<string>

Parameters

ParameterTypeDescription
escrowUtxoUTxOThe UTxO from the initiate transaction
depositAmountAsset[]Array of assets Party B is depositing

Code Example

import { MeshEscrowContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder, Asset } from "@meshsdk/core";

// Initialize provider and contract (Party B's wallet)
const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
});

const contract = new MeshEscrowContract({
  mesh: meshTxBuilder,
  fetcher: provider,
  wallet: partyBWallet, // Party B's wallet
  networkId: 0,
});

// Get the escrow UTxO from Party A's transaction
const escrowUtxo = await contract.getUtxoByTxHash("<initiate-transaction-hash>");

// Define assets Party B is depositing (an NFT)
const depositAmount: Asset[] = [
  {
    unit: "d9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e",
    quantity: "1",
  },
];

// Deposit into the escrow
const tx = await contract.recipientDeposit(escrowUtxo, depositAmount);
const signedTx = await partyBWallet.signTx(tx);
const txHash = await partyBWallet.submitTx(signedTx);

console.log("Deposit complete. Transaction hash:", txHash);

What Happens on Success

  • Party B's assets transfer to the contract
  • Both parties' addresses are now in the escrow datum
  • Escrow is ready for completion

Complete Escrow

Finalize the escrow and swap assets between both parties. This requires signatures from both participants.

Method Signature

contract.completeEscrow(escrowUtxo: UTxO): Promise<string>

Parameters

ParameterTypeDescription
escrowUtxoUTxOThe UTxO containing the escrow

Code Example (Two-Step Signing)

Step 1: Party A signs

import { MeshEscrowContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";

// Party A initiates completion
const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
});

const contract = new MeshEscrowContract({
  mesh: meshTxBuilder,
  fetcher: provider,
  wallet: partyAWallet,
  networkId: 0,
});

// Get the escrow UTxO (after Party B deposited)
const escrowUtxo = await contract.getUtxoByTxHash("<deposit-transaction-hash>");

// Build and partially sign the completion transaction
const tx = await contract.completeEscrow(escrowUtxo);
const signedTxByA = await partyAWallet.signTx(tx, true); // partial sign

console.log("Party A signed. Send this to Party B:", signedTxByA);

Step 2: Party B signs and submits

// Party B receives the partially signed transaction
const signedTxByA = "<partially-signed-tx-from-party-a>";

// Party B adds their signature and submits
const signedTxByBoth = await partyBWallet.signTx(signedTxByA, true);
const txHash = await partyBWallet.submitTx(signedTxByBoth);

console.log("Escrow completed. Transaction hash:", txHash);

What Happens on Success

Cancel Escrow

Cancel the escrow and return assets to their original owners.

Method Signature

contract.cancelEscrow(escrowUtxo: UTxO): Promise<string>

Parameters

ParameterTypeDescription
escrowUtxoUTxOThe UTxO containing the escrow

Code Example

import { MeshEscrowContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";

// Initialize provider and contract
const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
});

const contract = new MeshEscrowContract({
  mesh: meshTxBuilder,
  fetcher: provider,
  wallet: wallet, // Either party can cancel
  networkId: 0,
});

// Get the escrow UTxO
const escrowUtxo = await contract.getUtxoByTxHash("<escrow-transaction-hash>");

// Cancel the escrow
const tx = await contract.cancelEscrow(escrowUtxo);
const signedTx = await wallet.signTx(tx);
const txHash = await wallet.submitTx(signedTx);

console.log("Escrow cancelled. Transaction hash:", txHash);

What Happens on Success

  • All assets return to their original depositors
  • Escrow UTxO is consumed
  • No further action possible on this escrow

Full Working Example

import { MeshEscrowContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder, Asset } from "@meshsdk/core";
import { MeshCardanoBrowserWallet } from "@meshsdk/wallet";

async function escrowDemo() {
  const provider = new BlockfrostProvider("<Your-API-Key>");

  // Party A: Wants to trade 10 ADA for an NFT
  const partyAWallet = await MeshCardanoBrowserWallet.enable("eternl");

  const contractA = new MeshEscrowContract({
    mesh: new MeshTxBuilder({ fetcher: provider, submitter: provider }),
    fetcher: provider,
    wallet: partyAWallet,
    networkId: 0,
  });

  // Step 1: Party A initiates with 10 ADA
  const partyADeposit: Asset[] = [
    { unit: "lovelace", quantity: "10000000" },
  ];

  const initTx = await contractA.initiateEscrow(partyADeposit);
  const signedInitTx = await partyAWallet.signTx(initTx);
  const initTxHash = await partyAWallet.submitTx(signedInitTx);

  console.log("Escrow initiated:", initTxHash);
  await new Promise((r) => setTimeout(r, 60000));

  // Step 2: Party B deposits their NFT
  const partyBWallet = await MeshCardanoBrowserWallet.enable("nami");

  const contractB = new MeshEscrowContract({
    mesh: new MeshTxBuilder({ fetcher: provider, submitter: provider }),
    fetcher: provider,
    wallet: partyBWallet,
    networkId: 0,
  });

  const escrowUtxo = await contractB.getUtxoByTxHash(initTxHash);

  const partyBDeposit: Asset[] = [
    {
      unit: "d9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e",
      quantity: "1",
    },
  ];

  const depositTx = await contractB.recipientDeposit(escrowUtxo, partyBDeposit);
  const signedDepositTx = await partyBWallet.signTx(depositTx);
  const depositTxHash = await partyBWallet.submitTx(signedDepositTx);

  console.log("Party B deposited:", depositTxHash);
  await new Promise((r) => setTimeout(r, 60000));

  // Step 3: Complete the escrow (multi-sig)
  const finalUtxo = await contractA.getUtxoByTxHash(depositTxHash);

  // Party A signs first
  const completeTx = await contractA.completeEscrow(finalUtxo);
  const signedByA = await partyAWallet.signTx(completeTx, true, false);

  // Party B signs and submits
  const signedByBoth = await partyBWallet.signTx(signedByA, true, false);
  const completeTxHash = await partyBWallet.submitTx(signedByBoth);

  console.log("Escrow completed:", completeTxHash);
}

escrowDemo().catch(console.error);

Troubleshooting

Common Errors

ErrorCauseSolution
Script validation failedMissing signatureEnsure both parties sign the completion transaction
UTxO not foundWrong transaction hashVerify you are using the latest escrow UTxO
Not a participantWrong walletOnly escrow participants can interact
Already completedEscrow finalizedCannot modify a completed escrow

Debugging Tips

  1. Track transaction hashes: Each step produces a new transaction hash
  2. Use partial signing: Always pass true to signTx() for escrow operations
  3. Share transactions securely: Use secure channels to exchange partially signed transactions
  4. Verify before signing: Check the escrow contents before completing

Multi-Signature Flow

Party A                    Contract                    Party B
   |                          |                          |
   |------ initiateEscrow --->|                          |
   |                          |<---- recipientDeposit ---|
   |                          |                          |
   |---- completeEscrow ----->|                          |
   |     (partial sign)       |                          |
   |                          |                          |
   |          signedTxByA -------------------------------->|
   |                          |                          |
   |                          |<----- signTx + submit ---|
   |                          |       (both signatures)  |
   |<----- Party B's assets --|---- Party A's assets --->|

On this page