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 Case | Benefit |
|---|---|
| Gain inbound | Convert outbound to inbound liquidity |
| Cash out | Move Lightning funds to cold storage |
| Load wallet | Fund Lightning from on-chain |
| Rebalance | Adjust 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:
- Agent requests swap (amount, on-chain address)
- Provider generates payment hash
- Agent pays Lightning invoice (funds locked by hash)
- Provider sends on-chain tx (locked by same hash)
- Agent claims on-chain with preimage
- 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:
- Agent requests swap (amount)
- Agent sends on-chain to provider’s HTLC address
- Provider pays agent’s Lightning invoice
- 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
| Provider | Type | API | Fees |
|---|---|---|---|
| Lightning Loop | Loop In/Out | gRPC, REST | Variable |
| Boltz | Both directions | REST | ~0.1-0.5% |
| Deezy | Loop Out | REST | ~0.1% |
| PeerSwap | P2P | Protocol | On-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
| Component | Description | Typical |
|---|---|---|
| Swap fee | Provider’s service fee | 0.1-1% |
| On-chain fee | Bitcoin network fee | Variable |
| Prepay | Anti-DoS deposit | 1-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
| Provider | Min | Max |
|---|---|---|
| Loop | 250k sats | 16M sats |
| Boltz | 10k sats | 50M 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.
Related Topics
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%"
}