epic: btc/qtc swaps through arbitrated multisig escrow #54

Open
opened 2026-09-15 14:40:56 +00:00 by grenade · 3 comments
Owner

Goal

A swap between QTC and BTC that runs entirely on infrastructure we and the makers control, where makers set their own prices, and where no single party can take funds. The QTC leg is held in a per-trade 2-of-3 multisig (buyer, seller, arbiter) using the multisig pallet that exists on runtime 152, so nothing here waits on a runtime upgrade. The BTC leg is a plain payment observed by an automated judge on our own Bitcoin node.

This is trust-minimized, not trustless, and every screen and document says so: the escrow replaces the chain's enforcement with an arbiter who can never move funds alone and can never redirect them, only choose between the two outcomes the parties agreed to before funding.

Mechanism (facts from the pallet on 152)

  • create_multisig([buyer, seller, arbiter], threshold 2, nonce = trade id) yields a deterministic address any party can verify before funding. Creation costs 0.03 QTC, burned. Signers are sorted, so order is irrelevant.
  • A proposal carries the exact call bytes and an expiry (at most 2 weeks). Approval and execution are separate; execute resubmits the bytes and the pallet checks equality, so what is displayed is what runs. Only the proposer can cancel. A proposal costs 0.05 QTC plus a 0.01 QTC refundable deposit, plus 1% per signer.
  • Exactly two calls can ever move the escrow, both fixed as bytes before anyone signs:
    • refund: utility.batch_all[ M → seller ], proposed by the seller at funding time with the trade deadline as expiry. Needs the arbiter (dispute) or the buyer (mutual abort).
    • release: utility.batch_all[ M → buyer (amount − fee), M → fee account (fee) ], proposed by the buyer only after paying BTC. Needs the seller (happy path) or the arbiter (seller unresponsive, payment proven).
  • The seller never pre-approves the release. The arbiter never proposes, approves only a call that byte-matches one of the two templates for that trade, and never approves both for the same trade.
  • Order: seller funds M and proposes refund → buyer verifies M's balance and the refund proposal → buyer pays BTC to the seller's declared address → buyer proposes release → seller approves and executes, or the judge confirms the BTC payment and the arbiter approves.
  • The judge's rule for BTC is objective: the declared address received at least the agreed amount, with N confirmations, before the deadline block on Quantus. No human is in the loop for a crypto leg.

Safety concerns, by party

Buyer (pays BTC, receives QTC)

  • Loses funds only if the seller and the arbiter both sign a refund after BTC was paid. Mitigation: the judge's decisions are deterministic, published and reproducible from public chain data; the fee is only earned on release, so the arbiter is paid to be right, not to refund.
  • Stuck if the seller vanishes and the arbiter is unavailable. Mitigation: arbiter liveness is an SLO with monitoring; the cold path exists for hot-signer outages.
  • Price moves during the confirmation window. Mitigation: quotes carry a validity, and the deadline is short enough that a maker's spread covers it.
  • Bitcoin reorgs. Mitigation: N confirmations, chosen per amount, before the judge counts a payment.

Seller (funds QTC, receives BTC)

  • Loses funds only if the buyer and the arbiter both sign a release without BTC arriving. Same mitigation as above; the judge's evidence is a Bitcoin transaction anyone can check.
  • Capital locked until release or refund; there is no timeout that returns funds without a second signature. Mitigation: the refund proposal exists from the moment of funding, so a single arbiter approval frees it; expired proposals leave the funds reachable by a fresh one.
  • Declares a BTC address once; a wrong address is unrecoverable. Mitigation: address confirmation and checksum in the wallet, shown in the review screen decoded from what will be sent to the coordinator.

Arbiter (our infrastructure)

  • Key compromise is the catastrophic case: an attacker holding the arbiter key enters trades as a counterparty and approves their own release on every one. Mitigation: per-trade keys pre-derived offline and shipped to the hot signer only for open trades, per-hour and per-trade caps, a kill switch, and a cold Keystone-signed path above the cap.
  • Liveness is an obligation: downtime locks funds for everyone. Mitigation: monitored, redundant hot signers behind one policy; the policy is code, not a person.
  • The trade graph (who traded with whom, when, how much) is a breach liability. Mitigation: minimum retention, expire closed trades, never store BTC addresses beyond the trade's life.
  • Griefing: spam trades cost the parties chain fees but cost the arbiter watching and storage. Mitigation: bonds and unbonded caps.

Makers (run their own daemon)

  • Hold a BTC key and a QTC key on a machine that is online. Mitigation: the daemon's keys are its own, sized to its inventory; the protocol never asks a maker to hold more than one trade's worth in a hot key.
  • Carry price risk on both assets. Mitigation: their spread is theirs to set; quotes have short validity.

Everyone

  • A Quantus runtime upgrade can change multisig semantics. Mitigation: the wallet and the arbiter pin the spec version they were verified against and refuse to open new trades on an unverified one.
  • Quantus is proof-of-work; the seller's funding and the release both need a confirmation depth. Mitigation: the coordinator only advances a trade on finalized blocks.

Operator-owned decisions

  • Fee rate and fee account (fee is a transfer inside the release batch; refund carries no fee). Needed before the arbiter child.
  • Confirmation depth N per amount tier on Bitcoin, and the finalized-depth on Quantus.
  • Bond policy: required deposits for each side, caps for unbonded counterparties.
  • Which host runs bitcoind and the arbiter hot signer; where the cold path lives.
  • Hot signer caps and who holds the kill switch.

Definition of done

On Planck plus Bitcoin signet, from an unmodified wallet against the deployed coordinator and arbiter: a taker buys QTC with BTC from a maker daemon and receives it after the seller's approval; a second trade where the maker goes silent resolves through the judge and the arbiter's approval; a third where the buyer never pays refunds through the seller's standing proposal and the arbiter; a fourth where the arbiter's hot signer is down resolves through the cold path. Each with measured wall-clock time and chain fees recorded on this epic. Then the same on mainnet with real BTC at the smallest sensible amount.

## Goal A swap between QTC and BTC that runs entirely on infrastructure we and the makers control, where makers set their own prices, and where no single party can take funds. The QTC leg is held in a per-trade 2-of-3 multisig (buyer, seller, arbiter) using the multisig pallet that exists on runtime 152, so nothing here waits on a runtime upgrade. The BTC leg is a plain payment observed by an automated judge on our own Bitcoin node. This is trust-minimized, not trustless, and every screen and document says so: the escrow replaces the chain's enforcement with an arbiter who can never move funds alone and can never redirect them, only choose between the two outcomes the parties agreed to before funding. ## Mechanism (facts from the pallet on 152) - `create_multisig([buyer, seller, arbiter], threshold 2, nonce = trade id)` yields a deterministic address any party can verify before funding. Creation costs 0.03 QTC, burned. Signers are sorted, so order is irrelevant. - A proposal carries the exact call bytes and an expiry (at most 2 weeks). Approval and execution are separate; `execute` resubmits the bytes and the pallet checks equality, so what is displayed is what runs. Only the proposer can cancel. A proposal costs 0.05 QTC plus a 0.01 QTC refundable deposit, plus 1% per signer. - Exactly two calls can ever move the escrow, both fixed as bytes before anyone signs: - **refund**: `utility.batch_all[ M → seller ]`, proposed by the seller at funding time with the trade deadline as expiry. Needs the arbiter (dispute) or the buyer (mutual abort). - **release**: `utility.batch_all[ M → buyer (amount − fee), M → fee account (fee) ]`, proposed by the buyer only after paying BTC. Needs the seller (happy path) or the arbiter (seller unresponsive, payment proven). - The seller never pre-approves the release. The arbiter never proposes, approves only a call that byte-matches one of the two templates for that trade, and never approves both for the same trade. - Order: seller funds M and proposes refund → buyer verifies M's balance and the refund proposal → buyer pays BTC to the seller's declared address → buyer proposes release → seller approves and executes, or the judge confirms the BTC payment and the arbiter approves. - The judge's rule for BTC is objective: the declared address received at least the agreed amount, with N confirmations, before the deadline block on Quantus. No human is in the loop for a crypto leg. ## Safety concerns, by party **Buyer (pays BTC, receives QTC)** - Loses funds only if the seller and the arbiter both sign a refund after BTC was paid. Mitigation: the judge's decisions are deterministic, published and reproducible from public chain data; the fee is only earned on release, so the arbiter is paid to be right, not to refund. - Stuck if the seller vanishes and the arbiter is unavailable. Mitigation: arbiter liveness is an SLO with monitoring; the cold path exists for hot-signer outages. - Price moves during the confirmation window. Mitigation: quotes carry a validity, and the deadline is short enough that a maker's spread covers it. - Bitcoin reorgs. Mitigation: N confirmations, chosen per amount, before the judge counts a payment. **Seller (funds QTC, receives BTC)** - Loses funds only if the buyer and the arbiter both sign a release without BTC arriving. Same mitigation as above; the judge's evidence is a Bitcoin transaction anyone can check. - Capital locked until release or refund; there is no timeout that returns funds without a second signature. Mitigation: the refund proposal exists from the moment of funding, so a single arbiter approval frees it; expired proposals leave the funds reachable by a fresh one. - Declares a BTC address once; a wrong address is unrecoverable. Mitigation: address confirmation and checksum in the wallet, shown in the review screen decoded from what will be sent to the coordinator. **Arbiter (our infrastructure)** - Key compromise is the catastrophic case: an attacker holding the arbiter key enters trades as a counterparty and approves their own release on every one. Mitigation: per-trade keys pre-derived offline and shipped to the hot signer only for open trades, per-hour and per-trade caps, a kill switch, and a cold Keystone-signed path above the cap. - Liveness is an obligation: downtime locks funds for everyone. Mitigation: monitored, redundant hot signers behind one policy; the policy is code, not a person. - The trade graph (who traded with whom, when, how much) is a breach liability. Mitigation: minimum retention, expire closed trades, never store BTC addresses beyond the trade's life. - Griefing: spam trades cost the parties chain fees but cost the arbiter watching and storage. Mitigation: bonds and unbonded caps. **Makers (run their own daemon)** - Hold a BTC key and a QTC key on a machine that is online. Mitigation: the daemon's keys are its own, sized to its inventory; the protocol never asks a maker to hold more than one trade's worth in a hot key. - Carry price risk on both assets. Mitigation: their spread is theirs to set; quotes have short validity. **Everyone** - A Quantus runtime upgrade can change multisig semantics. Mitigation: the wallet and the arbiter pin the spec version they were verified against and refuse to open new trades on an unverified one. - Quantus is proof-of-work; the seller's funding and the release both need a confirmation depth. Mitigation: the coordinator only advances a trade on finalized blocks. ## Operator-owned decisions - Fee rate and fee account (fee is a transfer inside the release batch; refund carries no fee). **Needed before the arbiter child.** - Confirmation depth N per amount tier on Bitcoin, and the finalized-depth on Quantus. - Bond policy: required deposits for each side, caps for unbonded counterparties. - Which host runs bitcoind and the arbiter hot signer; where the cold path lives. - Hot signer caps and who holds the kill switch. ## Definition of done On Planck plus Bitcoin signet, from an unmodified wallet against the deployed coordinator and arbiter: a taker buys QTC with BTC from a maker daemon and receives it after the seller's approval; a second trade where the maker goes silent resolves through the judge and the arbiter's approval; a third where the buyer never pays refunds through the seller's standing proposal and the arbiter; a fourth where the arbiter's hot signer is down resolves through the cold path. Each with measured wall-clock time and chain fees recorded on this epic. Then the same on mainnet with real BTC at the smallest sensible amount.
grenade added the epic label 2026-09-15 14:41:00 +00:00
Author
Owner

Children, in dependency order: #55 (multisig spike on a dev node) and #56 (bitcoind on our infra) can start now, in parallel → #57 (protocol document and entities) → #58 (wallet-arbiter), #59 (coordinator in wallet-api) → #60 (wallet-maker), #61 (taker flow in the wallet) → #62 (bonds and limits) → #63 (rehearsal on Planck plus signet, then mainnet).

Decisions this epic needs before #58: fee rate and fee account, confirmation tiers, caps and kill-switch holder, hosts for bitcoind and the arbiter. Bond sizes before #62. Recorded as comments on those issues when made.

Sequencing note: #57 depends on #55's measured facts, and #61 depends on #28 and #39 from the desktop and Intents epics, so the taker screens land after the v1 send flow exists.

Children, in dependency order: #55 (multisig spike on a dev node) and #56 (bitcoind on our infra) can start now, in parallel → #57 (protocol document and entities) → #58 (wallet-arbiter), #59 (coordinator in wallet-api) → #60 (wallet-maker), #61 (taker flow in the wallet) → #62 (bonds and limits) → #63 (rehearsal on Planck plus signet, then mainnet). Decisions this epic needs before #58: fee rate and fee account, confirmation tiers, caps and kill-switch holder, hosts for bitcoind and the arbiter. Bond sizes before #62. Recorded as comments on those issues when made. Sequencing note: #57 depends on #55's measured facts, and #61 depends on #28 and #39 from the desktop and Intents epics, so the taker screens land after the v1 send flow exists.
Author
Owner

Correction, 2026-09-15: no bonds

The epic body and the original #62 assumed Bisq-style security deposits from both parties. That does not fit this market: only miners and the team hold QTC, so a QTC bond is a barrier to entry for buyers, and buyers are the side without which nothing works. A seller bond adds nothing either, because the judge already denies a seller any gain from walking away once BTC is paid.

Bonds are removed from the design. What bounds a no-show instead, now specified in #62: a payment deadline measured in minutes and enforced as the refund proposal's expiry, automatic refund approval by the arbiter when the deadline passes unpaid, mempool acceptance extending the deadline into the confirmation window, and maker-declared exposure and per-taker limits enforced at trade creation. The seller's cost of a no-show becomes the payment window plus proposal fees, priced into the maker's spread.

Where the epic body says "bonds and unbonded caps" under the arbiter's griefing concern and lists a bond policy as an operator decision, read #62 instead; the body is left as written so this correction stays legible.

## Correction, 2026-09-15: no bonds The epic body and the original #62 assumed Bisq-style security deposits from both parties. That does not fit this market: only miners and the team hold QTC, so a QTC bond is a barrier to entry for buyers, and buyers are the side without which nothing works. A seller bond adds nothing either, because the judge already denies a seller any gain from walking away once BTC is paid. Bonds are removed from the design. What bounds a no-show instead, now specified in #62: a payment deadline measured in minutes and enforced as the refund proposal's expiry, automatic refund approval by the arbiter when the deadline passes unpaid, mempool acceptance extending the deadline into the confirmation window, and maker-declared exposure and per-taker limits enforced at trade creation. The seller's cost of a no-show becomes the payment window plus proposal fees, priced into the maker's spread. Where the epic body says "bonds and unbonded caps" under the arbiter's griefing concern and lists a bond policy as an operator decision, read #62 instead; the body is left as written so this correction stays legible.
Author
Owner

Sequencing decision, 2026-09-15

This epic is deferred to the end of the plan, after every other epic. It depends on the wallet being useful first: a swap needs takers who already hold and move QTC in this wallet, and the taker flow (#61) is built on the v1 send review (#28) and the order state machine (#39) in any case. Nothing here starts, including the spike (#55) and the Bitcoin node (#56), until the desktop, packaging and Intents epics have shipped and there are users to swap.

The design stays filed as written so the decisions and corrections above are not lost.

## Sequencing decision, 2026-09-15 This epic is deferred to the end of the plan, after every other epic. It depends on the wallet being useful first: a swap needs takers who already hold and move QTC in this wallet, and the taker flow (#61) is built on the v1 send review (#28) and the order state machine (#39) in any case. Nothing here starts, including the spike (#55) and the Bitcoin node (#56), until the desktop, packaging and Intents epics have shipped and there are users to swap. The design stays filed as written so the decisions and corrections above are not lost.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: blackbeard/wallet#54