← Back to Docs

How Coinshift Works

Deep dive into the swap engine architecture, state machine, security checks, and trust model.

Overview

Coinshift is a trustless swap system for a BIP-300 sidechain. It enables peer-to-peer exchanges between L2 (sidechain) coins and L1 (parent chain) assets. The system currently supports L2 → L1 swaps: Alice offers L2 coins in exchange for L1 assets (BTC, BCH, LTC, Signet, etc.) sent by Bob.

The sidechain's mainchain (for deposits/withdrawals and BMM) can be different from the swap target chain. For example, the sidechain can run on regtest while swaps target signet.

Core Architecture

Sidechain Node
  |
  +-- State Management (lib/state/)
  |     Swap storage, output locking/unlocking, tx validation
  |
  +-- Block Processing (lib/state/block.rs)
  |     SwapCreate: create swap + lock outputs
  |     SwapClaim: verify state + unlock outputs
  |
  +-- L1 Monitoring (lib/state/two_way_peg_data.rs)
  |     process_coinshift_transactions() during 2WPD connect
  |     query_and_update_swap(): RPC match, update state
  |
  +-- RPC Client (lib/parent_chain_rpc.rs)
        Query swap target chain (Signet, Mainnet, Regtest...)
        find_transactions_by_address_and_amount()
        get_transaction(), get_transaction_confirmations()
            |
            v
    Parent Chain (L1) - swap target
    (Bitcoin, Signet, BCH, LTC, Regtest, etc.)

Complete Swap Flow

1. Swap Creation (Alice)

  1. Alice creates swap offer via RPC create_swap() with: her L1 address, L1 amount wanted, L2 amount offered, optional L2 recipient restriction, and required confirmations.
  2. Swap ID is computed deterministically: SwapId = blake3(l1_addr || l1_amt || l2_sender || l2_recipient)
  3. SwapCreate transaction is built by the wallet (metadata + locked L2 inputs), signed, and broadcast.
  4. Block processing validates the swap (ID match, uniqueness, structure, sufficient value), locks all outputs to the swap, and saves with state Pending.

2. L1 Transaction Monitoring (Bob)

  1. Bob sends L1 transaction to Alice's l1_recipient_address for the exact l1_amount.
  2. When 2WPD runs (mainchain tip changes), process_coinshift_transactions() is called.
  3. Matching: For each pending/waiting swap, the system queries the swap target chain's RPC: find_transactions_by_address_and_amount().
  4. Update: If confirmations ≥ required, state moves to ReadyToClaim. Otherwise, WaitingConfirmations.

3. Swap Claiming (Bob)

  1. Bob creates SwapClaim via claim_swap() with swap_id and optionally his L2 address.
  2. Validation: Swap exists, state is ReadyToClaim, inputs are locked to this swap, L1 tx was detected.
  3. Block processing: Unlocks all inputs locked to this swap, sets state to Completed.
  4. Bob's L2 address receives the coins. Swap is done.

Security Checks

Implemented

Check Location
Swap ID verification validate_swap_create(): computed ID must match tx
Swap uniqueness Swap must not already exist
Output locking SwapCreate locks outputs; only SwapClaim can unlock
Locked-input checks Non-SwapClaim txs cannot spend locked outputs
Recipient & amount matching RPC match by address + exact amount
State machine enforcement Pending → WaitingConfirmations → ReadyToClaim → Completed
Confirmation threshold ReadyToClaim only when confirmations ≥ required
Swap expiration Expired swaps marked Cancelled, coins returned
L1 tx uniqueness get_swap_by_l1_txid() checked before accepting L1 tx
Block inclusion required Only accepts L1 matches with confirmations > 0 and blockheight present

Parent-Chain Payment Confirmation

For deposits and withdrawals (two-way peg), the sidechain verifies payment using:

  1. Mainchain header chain (SPV-style): Parent blocks stored only if their parent is already in the archive, forming a verified chain.
  2. Proof-of-work: Total work accumulated along the chain; verified during peer sync.
  3. BMM verification: Sidechain blocks only valid when a mainchain block commits to them (bmm_commitment == sidechain_block_hash).
  4. 2WPD from verified blocks only: Deposit/withdrawal events come only from mainchain blocks already in the verified archive.

For Coinshift swaps, L1 payment is confirmed by RPC to the swap target chain: match by address + amount, then confirmation count. No separate header chain or merkle proof for swap L1 transactions.

Data Structures

Swap

pub struct Swap {
    pub id: SwapId,
    pub direction: SwapDirection,
    pub parent_chain: ParentChainType,
    pub l1_txid: SwapTxId,
    pub required_confirmations: u32,
    pub state: SwapState,
    pub l2_recipient: Option<Address>,
    pub l2_amount: bitcoin::Amount,
    pub l1_recipient_address: Option<String>,
    pub l1_amount: Option<bitcoin::Amount>,
    pub l1_claimer_address: Option<String>,
    pub created_at_height: u32,
    pub expires_at_height: Option<u32>,
    pub l1_txid_validated_at_block_hash: Option<BlockHash>,
    pub l1_txid_validated_at_height: Option<u32>,
}

Databases

Trust Model

Trusted

Protected Against

Integration Points

Component Location
Block processing lib/state/block.rs — SwapCreate (lock), SwapClaim (unlock)
L1 monitoring lib/state/two_way_peg_data.rs during 2WPD connect
RPC client lib/parent_chain_rpc.rs
Swap validation lib/state/swap.rs
State persistence lib/state/mod.rs

Not Implemented (Planned)

The following features are documented in design specs but not present in the current codebase:

Swap L1 verification currently uses RPC only, which is documented and tested.