Menu
Lightning Intermediate 6 min read

Submarine Swaps

Atomic swaps between on-chain Bitcoin and Lightning. Loop In, Loop Out, and trustless exchange mechanisms.

submarine-swap loop atomic on-chain off-chain

Submarine Swaps

A submarine swap is an atomic exchange between on-chain Bitcoin and Lightning Bitcoin. It lets you move funds between layers without trusting a counterparty.

Why Submarine Swaps Matter for Agents

Use CaseBenefit
Gain inboundConvert outbound to inbound liquidity
Cash outMove Lightning funds to cold storage
Load walletFund Lightning from on-chain
RebalanceAdjust channel liquidity

How Submarine Swaps Work

Loop Out (Lightning → On-Chain)

Send Lightning payment, receive on-chain Bitcoin.

Agent ──[LN payment]──→ Swap Provider

Agent ←──[on-chain tx]────────┘

Use case: Convert outbound liquidity to inbound.

Flow:

  1. Agent requests swap (amount, on-chain address)
  2. Provider generates payment hash
  3. Agent pays Lightning invoice (funds locked by hash)
  4. Provider sends on-chain tx (locked by same hash)
  5. Agent claims on-chain with preimage
  6. Provider claims Lightning with revealed preimage

Loop In (On-Chain → Lightning)

Send on-chain Bitcoin, receive Lightning payment.

Agent ──[on-chain tx]──→ Swap Provider

Agent ←──[LN payment]─────────┘

Use case: Fund Lightning channel from cold storage.

Flow:

  1. Agent requests swap (amount)
  2. Agent sends on-chain to provider’s HTLC address
  3. Provider pays agent’s Lightning invoice
  4. Preimage revealed, provider claims on-chain

Atomic Guarantees

Submarine swaps are trustless because of hash time locks:

On-chain HTLC:
  IF (hash matches AND provider_sig) → Provider claims
  ELSE IF (timeout passed AND agent_sig) → Agent refunds

Lightning HTLC:
  Same hash locks both sides
  If on-chain fails, Lightning fails too

Result: Either both sides complete or neither does.

Swap Providers

ProviderTypeAPIFees
Lightning LoopLoop In/OutgRPC, RESTVariable
BoltzBoth directionsREST~0.1-0.5%
DeezyLoop OutREST~0.1%
PeerSwapP2PProtocolOn-chain only

Using Lightning Loop

Loop Out via CLI

# Check current terms
loop quote out 500000

# Execute Loop Out
loop out \
  --amt 500000 \
  --conf_target 6 \
  --addr bc1q...

Loop Out via API

import loop_pb2 as loop
import loop_pb2_grpc as loopstub

# Quote
quote = stub.LoopOutQuote(
    loop.QuoteRequest(amt=500000)
)
print(f"Swap fee: {quote.swap_fee_sat}")
print(f"Prepay: {quote.prepay_amt_sat}")

# Execute
response = stub.LoopOut(
    loop.LoopOutRequest(
        amt=500000,
        dest=on_chain_address,
        max_swap_fee=quote.swap_fee_sat,
        max_prepay_amt=quote.prepay_amt_sat
    )
)

Using Boltz Exchange

Loop Out (Lightning → On-Chain)

import requests

BOLTZ_URL = "https://api.boltz.exchange"

# Get swap info
info = requests.get(f"{BOLTZ_URL}/getpairs").json()
btc_pair = info["pairs"]["BTC/BTC"]

# Create swap
swap = requests.post(
    f"{BOLTZ_URL}/createswap",
    json={
        "type": "submarine",
        "pairId": "BTC/BTC",
        "orderSide": "sell",  # Sell LN, buy on-chain
        "invoice": lightning_invoice,
        "refundPublicKey": agent_pubkey_hex
    }
).json()

# Pay the Lightning invoice Boltz provides
# Then Boltz sends on-chain to your address

Loop In (On-Chain → Lightning)

# Create reverse swap
swap = requests.post(
    f"{BOLTZ_URL}/createswap",
    json={
        "type": "reversesubmarine",
        "pairId": "BTC/BTC",
        "orderSide": "buy",  # Buy LN, sell on-chain
        "invoiceAmount": 500000,
        "claimPublicKey": agent_pubkey_hex
    }
).json()

# Send on-chain to swap["address"]
# Claim Lightning payment when it arrives

Costs

Fee Components

ComponentDescriptionTypical
Swap feeProvider’s service fee0.1-1%
On-chain feeBitcoin network feeVariable
PrepayAnti-DoS deposit1-5k sats

Cost Comparison

Loop Out 500,000 sats:
├── Swap fee: ~2,500 sats (0.5%)
├── On-chain fee: ~500 sats (1 sat/vB)
└── Total: ~3,000 sats

vs Manual (close + reopen):
├── Close channel: ~1,000 sats
├── Open channel: ~1,000 sats
├── Wait time: Hours
└── Total: ~2,000 sats + time cost

Swaps are faster but slightly more expensive.

Liquidity Management with Swaps

Gaining Inbound Liquidity

def ensure_inbound_liquidity(target_inbound):
    current_inbound = sum(ch.remote_balance for ch in channels)

    if current_inbound < target_inbound:
        swap_amount = target_inbound - current_inbound
        perform_loop_out(swap_amount)

Automated Rebalancing

def auto_rebalance(channels):
    for ch in channels:
        ratio = ch.local_balance / ch.capacity

        if ratio > 0.8:  # Too much outbound
            loop_out(ch.capacity * 0.3)
        elif ratio < 0.2:  # Too much inbound
            loop_in(ch.capacity * 0.3)

Swap Limits

ProviderMinMax
Loop250k sats16M sats
Boltz10k sats50M sats

Security Considerations

Timeout Safety

Swap HTLCs have timeouts. If something fails:

  • Agent’s protection: Can refund after timeout
  • Provider’s protection: Can claim after timeout

Verification

def verify_swap_address(expected_hash, actual_address):
    """Verify on-chain HTLC address before sending."""
    # Decode address, verify hash matches
    script = decode_address(actual_address)
    return expected_hash in script

Privacy

Swap providers see:

  • Your on-chain address
  • Your Lightning node (potentially)
  • Amounts

Mitigation: Use fresh addresses, Tor.


Machine-Readable Summary

{
  "topic": "submarine-swaps",
  "key_concepts": [
    "loop-in",
    "loop-out",
    "atomic-swap",
    "htlc"
  ],
  "providers": [
    {
      "name": "lightning-loop",
      "api": "grpc",
      "min_sat": 250000
    },
    {
      "name": "boltz",
      "api": "rest",
      "min_sat": 10000
    }
  ],
  "typical_fee": "0.1-1%"
}