Skip to main content
A fixed-party vault names its parties when you create it, so they are fixed in its address. This page builds an escrow on Mutinynet: a buyer funds it, and an oracle’s signature releases the funds to the seller, or a timeout returns them to the buyer. The contract is Escrow. For the one-owner case, a savings vault, see Your first contract.

Install

You need Rust 1.85 or newer and Node.js 22.13 or newer (below 27). Set type to module in package.json.

Why the escrow needs no signatures

complete and cancel are covenants: the operator and the emulator cosign them when every require passes. Anyone who can build a passing transaction can submit one. That is safe because the address pins each outcome’s outputs: complete must pay the seller’s script and cancel must pay the buyer’s. The submitter picks the moment, never the destination. The seller never signs, and the buyer signs once, when funding the address.

Compile the contract

Save Escrow as escrow.ark and compile it:
Before you run the scripts below:
  • Replace the three keys before you fund the escrow. The scripts use well-known test keys.
  • exit is the operator’s unilateralExitDelay, encoded in seconds with timelockToSequence. A raw delay binds as blocks, and the operator rejects a block-based exit leaf. It also rejects an exit delay below its minimum.
  • The scripts read unilateralExitDelay on each run, and the escrow address depends on it. If the operator changes its delay after you fund, the scripts derive another address and find no output. Pass the delay you funded with instead.
  • partyAScript and partyBScript are 32-byte witness programs (tweakedPublicKey). Spend outputs use the full pkScript.
  • timeoutAt is 1700000000, already past, so cancel works as soon as an output arrives.
Each spend path below has its own file.

Complete

complete pays the seller when the oracle signs. Fund the printed address with 12000 sats, then run the same file again. The seller receives 10000 sats. The buyer receives the rest.
complete.ts

Cancel

cancel returns the output to the buyer. Fund the printed address, then run the same file again.
cancel.ts

Exit if the operator is unavailable

If the operator is unavailable, the parties can still claim the output onchain with unilateral. First unroll it: broadcast the virtual transactions that lead to it. After the output confirms and the exit delay passes, Bitcoin checks the leaf. Both parties sign, and the leaf does not check the outputs. This file pays a Bitcoin address with a 1000-sat fee. Set UNROLLED_TXID, UNROLLED_VOUT, UNROLLED_VALUE, and BITCOIN_ADDRESS to the unrolled output and the destination.
unilateral.ts