Trustless Co-Buyer Escrow on Liquid Network
Two buyers. One seller. Zero middlemen.
A SimplicityHL smart contract that holds funds until both parties confirm — then releases automatically. No trust required.
01 — The Problem
Sends their share and waits. If Buyer B or the seller disappears — money is gone. No recourse, no reversal.
Same problem, reversed. Someone always absorbs all the risk. A trust deadlock with no exit and no on-chain resolution.
Escrow services charge fees, require identity checks, and add a third party you still have to trust. That's the whole problem.
02 — How It Works
jet::bip_0340_verify → full amount to sellerjet::check_lock_time(param::EXPIRY_TIME) passes → deposits returned
03 — The Contract
/* * DuoPay — 2-of-2 Co-Buyer Escrow * Left → release: both buyers sign → seller paid * Right → timeout: expiry passed + both sign → refund * * Parameters set at compile time via param:: namespace: * BUYER_A_PUBKEY, BUYER_B_PUBKEY — BIP-340 public keys * EXPIRY_TIME — block height for refund unlock * BUYER_A_AMOUNT, BUYER_B_AMOUNT — deposits in satoshis */ fn get_output_explicit_amount(index: u32) -> u64 { let pair: (Asset1, Amount1) = unwrap(jet::output_amount(index)); let (_, amount): (Asset1, Amount1) = pair; let amount: u64 = unwrap_right::<(u1, u256)>(amount); amount } fn check_signature(pubkey: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pubkey, msg), sig); } fn release_path(sig_a: Signature, sig_b: Signature) { check_signature(param::BUYER_A_PUBKEY, sig_a); check_signature(param::BUYER_B_PUBKEY, sig_b); let (carry, total): (bool, u64) = jet::add_64(param::BUYER_A_AMOUNT, param::BUYER_B_AMOUNT); assert!(jet::eq_1(<bool>::into(carry), 0)); assert!(jet::eq_64(get_output_explicit_amount(0), total)); } fn timeout_path(sig_a: Signature, sig_b: Signature) { jet::check_lock_time(param::EXPIRY_TIME); check_signature(param::BUYER_A_PUBKEY, sig_a); check_signature(param::BUYER_B_PUBKEY, sig_b); assert!(jet::eq_64(get_output_explicit_amount(0), param::BUYER_A_AMOUNT)); assert!(jet::eq_64(get_output_explicit_amount(1), param::BUYER_B_AMOUNT)); } fn main() { let sig_a: Signature = witness::SIG_A; let sig_b: Signature = witness::SIG_B; match witness::PATH { Left(params: ()) => release_path(sig_a, sig_b), Right(params: ()) => timeout_path(sig_a, sig_b), } }
Written in real SimplicityHL syntax — same patterns as option_offer.simf in the Blockstream simplicity-contracts repo.
Uses jet::bip_0340_verify, jet::check_lock_time, jet::sig_all_hash, jet::add_64, jet::eq_64 — all documented jets.
witness::PATH, param:: namespaces match official contract structure exactly.
04 — Why Simplicity
No loops, no recursion. Exactly two outcomes: release or refund. Both buyers can verify the complete execution path before depositing a single satoshi.
Both parties can prove no unauthorized spend path exists. The code is the guarantee — not a company promise, not a terms of service, not a human arbitrator.
Each DuoPay contract is an isolated UTXO. Bugs in other contracts cannot drain yours. Reentrancy attacks are structurally impossible — not just prevented by code.
Statically bounded execution cost. Both buyers see the exact fee before committing. No gas estimation games. No failed transactions from insufficient gas.
Global state allows malicious contracts to call back mid-execution and drain an escrow before the transfer completes. A well-known, recurring attack vector.
UTXO model means each contract is an isolated coin. There is no shared state to re-enter. The attack is structurally impossible, not just guarded against.
Gas can spike unexpectedly. Transactions can fail out-of-gas at runtime. Buyers cannot know exact costs before committing funds.
Execution cost is statically bounded at compile time. Both buyers see the exact fee before signing anything. No surprises.
Most production escrows on Ethereum still include an admin key or arbitrator address — a human who can be bribed, hacked, or simply go offline.
DuoPay has no admin key. No arbitrator. The timeout path is enforced by the chain itself via jet::check_lock_time. No third party exists to corrupt.
05 — The Vision
DuoPay is designed to become a full trustless payment platform — eliminating the security risks of WhatsApp, Telegram, and TikTok payments entirely. Every transfer verified on-chain. Every deal sealed with code, not promises.
Create a deal, share a link. The other party sees terms verified on-chain and deposits. No screenshots. No "trust me." The blockchain is the receipt.
Create, track, and manage deals in a clean interface. Deposit status verified in real time on-chain — no edited screenshots, no "I sent it, check again."
06 — Who Needs This
07 — Roadmap