Deep dive into the swap engine architecture, state machine, security checks, and trust model.
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.
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.)
create_swap() with: her L1 address, L1 amount wanted, L2 amount offered, optional L2 recipient restriction, and required confirmations.SwapId = blake3(l1_addr || l1_amt || l2_sender || l2_recipient)Pending.l1_recipient_address for the exact l1_amount.process_coinshift_transactions() is called.find_transactions_by_address_and_amount().ReadyToClaim. Otherwise, WaitingConfirmations.claim_swap() with swap_id and optionally his L2 address.ReadyToClaim, inputs are locked to this swap, L1 tx was detected.Completed.| 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 |
For deposits and withdrawals (two-way peg), the sidechain verifies payment using:
bmm_commitment == sidechain_block_hash).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.
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>,
}
SwapId → Swap(ParentChainType, SwapTxId) → SwapId(SwapState, SwapId) → ()Address → Vec<SwapId>OutPointKey → SwapIdReadyToClaim| 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 |
The following features are documented in design specs but not present in the current codebase:
merkle_proof_verified field on Swap structSwap L1 verification currently uses RPC only, which is documented and tested.