Skip to content

🐳 Backend Development

This document describes how to set up a Docker regtest environment for Boltz Backend development.

Getting Started

The regtest environment of the Boltz Backend is based on our regtest Docker Compose. To start the regtest environment run npm run regtest:start and to stop it again use npm run regtest:stop.

To use the nodes in the container with the Boltz Backend, use a configuration file in ~/.boltz/boltz.conf similar to this one:

toml
# Boltz Backend Configuration
# All paths are relative to the config file unless absolute
# Numbers with underscores are for readability (e.g., 1_000 = 1000)

# =============================================================================
# Top-Level Options (must be before any [section])
# =============================================================================

# datadir = "/var/lib/boltz"
# logpath = "/var/lib/boltz/boltz.log"
# loglevel = "info"  # error, warn, info, verbose, debug, silly (default: info in production, debug in development)
# retryInterval = 15  # Seconds between retries for settling pending submarine swaps (default: 15)

# otlpEndpoint = "http://127.0.0.1:4317/v1/traces"  # OpenTelemetry
# lokiEndpoint = "http://127.0.0.1:3100"  # Loki logging
# profilingEndpoint = "http://127.0.0.1:4040"  # Profiling

# Prepay miner fee for reverse swaps (experimental)
# prepayminerfee = false

# Use P2WSH addresses instead of P2SH nested P2WSH for legacy Submarine Swaps
# swapwitnessaddress = false

# Network identifier
# network = "mainnet"

# =============================================================================
# Server Configuration
# =============================================================================

[api]
host = "127.0.0.1"  # 0.0.0.0 for all interfaces
port = 9_001

[grpc]
host = "127.0.0.1"
port = 9_000
# certificates = "/path/to/certs"
# disableSsl = false  # Development only

# =============================================================================
# Database & Cache
# =============================================================================

[postgres]
host = "127.0.0.1"
port = 5432
database = "boltz"
username = "boltz"
password = "boltz"

# Redis cache for production deployments (optional)
# [cache]
# redisEndpoint = "redis://127.0.0.1:6379"

# =============================================================================
# Observability
# =============================================================================

# [prometheus]
# host = "127.0.0.1"
# port = 9_099

# =============================================================================
# Notifications (Mattermost)
# =============================================================================

# [notification]
# mattermostUrl = "https://mattermost.example.com/"
# token = "your-api-token"
# channel = "boltz-swaps"
# channelAlerts = "boltz-alerts"
# prefix = "[Boltz]"
# interval = 1

# =============================================================================
# Email Notifications (optional)
# =============================================================================

# [email]
# enabled = false
# host = "smtp.example.com"
# port = 587
# secure = false  # Use SSL/TLS
# user = "your-username"
# pass = "your-password"
# to = "admin@example.com"
# from = "boltz@example.com"
# subjectPrefix = "[Boltz]"

# =============================================================================
# Trading Pairs
# =============================================================================

[[pairs]]
# Currency symbols for the trading pair
base = "BTC"
quote = "BTC"

rate = 1  # Hardcoded rate (bypasses exchange APIs)

fee = 0.5       # Fee percentage for reverse/chain swaps
swapInFee = 0.1 # Fee percentage for submarine swaps (overrides `fee`)

maxSwapAmount = 40_294_967
minSwapAmount = 50_000

# invoiceExpiry = 3600  # Defaults to 50% of reverse swap expiry
# swapTypes = ["submarine", "reverse", "chain"]
# hidden = true  # Hide pair from pair list endpoints unless a referral enables it

# Timeouts in blocks for different swap types
[pairs.timeoutDelta]
chain = 1440
reverse = 1440
swapMinimal = 1440
swapMaximal = 2880
swapTaproot = 10080

# [pairs.submarineSwap]
# minSwapAmount = 1_000
# minBatchedAmount = 21  # Minimum amount for batched swaps

# Chain-specific limits (optional)
# [pairs.chainSwap]
# minSwapAmount = 25_000
# buyFee = 0.1
# sellFee = 0.1

[[pairs]]
base = "L-BTC"
quote = "BTC"
fee = 0.25
swapInFee = 0.1
rate = 1
maxSwapAmount = 40_294_967
minSwapAmount = 100

[pairs.timeoutDelta]
chain = 1440
reverse = 1440
swapMinimal = 1440
swapMaximal = 2880
swapTaproot = 10080

[pairs.submarineSwap]
minSwapAmount = 1_000
minBatchedAmount = 21

[pairs.chainSwap]
minSwapAmount = 25_000

[[pairs]]
base = "RBTC"
quote = "BTC"
rate = 1
fee = 0.25
swapInFee = 0.1
maxSwapAmount = 4_294_967
minSwapAmount = 50_000

[pairs.timeoutDelta]
chain = 1440
reverse = 1440
swapMinimal = 1440
swapMaximal = 2880
swapTaproot = 10080

[[pairs]]
base = "RBTC"
quote = "L-BTC"
rate = 1
fee = 0.25
swapInFee = 0.1
maxSwapAmount = 4_294_967
minSwapAmount = 2_500
swapTypes = ["chain"]  # Only enable chain swaps for this pair

[pairs.chainSwap]
buyFee = 0.1
sellFee = 0.1
minSwapAmount = 25_000

[pairs.timeoutDelta]
chain = 1440
reverse = 1440
swapMinimal = 1440
swapMaximal = 2880
swapTaproot = 10080

# =============================================================================
# Currency Configuration
# =============================================================================

[[currencies]]
symbol = "BTC"
# Network identifier (bitcoinMainnet, bitcoinTestnet, bitcoinRegtest, etc.)
network = "bitcoinRegtest"

# Balance requirements (in satoshis)
minWalletBalance = 10_000_000
# maxWalletBalance = 100_000_000
# maxUnusedWalletBalance = 10_000_000

# Lightning channel balance requirements (in satoshis)
# Note: Used for balance monitoring/alerts, not for swap operations
# minLocalBalance = 10_000_000   # Minimum local channel balance (outbound liquidity)
# minRemoteBalance = 10_000_000  # Minimum remote channel balance (inbound liquidity)

# Swap limits (in satoshis)
maxSwapAmount = 40_294_967
minSwapAmount = 10_000

# Zero-confirmation acceptance (in satoshis)
maxZeroConfAmount = 0  # Max 0-conf amount (0 = disabled)
# maxZeroConfRisk = 100_000  # Alternative: max 0-conf based on risk score

# preferredWallet = "core"  # 'core' or undefined (defaults to 'core'; 'lnd' not supported anymore)

# noRoute = ["nodepubkey1", "nodepubkey2"]

# Per-node routing fee overrides (fee ratio: 0.0035 = 0.35%)
# [[currencies.routingOffsetExceptions]]
# nodeId = "nodepubkey"
# offset = 0.0050

[currencies.chain]
host = "127.0.0.1"
port = 18_443

# Authentication (cookie preferred if both configured)
cookie = "./regtest/data/bitcoind/regtest/.cookie"
# user = "rpc"
# password = "password"

wallet = "regtest"

# NOTE: ZeroMQ must be configured in Bitcoin Core (bitcoind -zmqpubrawtx=... -zmqpubrawblock=...)
# Boltz queries ZMQ endpoints automatically via RPC; they are not configured here

# mempoolSpace = "https://mempool.space/api"  # Comma-separated for failover

# Minimum fee rate in sat/vbyte (default: 0.2 for Bitcoin, 0.1 for Elements)
feeFloor = 1

[[currencies.lnds]]
host = "127.0.0.1"
port = 11_009
certpath = "./regtest/data/lnd2/tls.cert"
macaroonpath = "./regtest/data/lnd2/data/chain/bitcoin/regtest/admin.macaroon"

# sslTargetNameOverride = "lnd.example.com"

[[currencies.lnds]]
host = "127.0.0.1"
port = 12_009
certpath = "./regtest/data/lnd3/tls.cert"
macaroonpath = "./regtest/data/lnd3/data/chain/bitcoin/regtest/admin.macaroon"

[currencies.cln]
host = "127.0.0.1"
port = 9737

rootCertPath = "./regtest/data/cln2/regtest/ca.pem"
privateKeyPath = "./regtest/data/cln2/regtest/client-key.pem"
certChainPath = "./regtest/data/cln2/regtest/client.pem"

# disableMpp = false

# Hold invoice service (for reverse swaps)
[currencies.cln.hold]
host = "127.0.0.1"
port = 9738

rootCertPath = "./regtest/data/cln2/regtest/hold/ca.pem"
privateKeyPath = "./regtest/data/cln2/regtest/hold/client-key.pem"
certChainPath = "./regtest/data/cln2/regtest/hold/client.pem"

# =============================================================================
# Liquid Network Configuration
# =============================================================================

[liquid]
symbol = "L-BTC"
network = "liquidRegtest"
maxSwapAmount = 40_294_967
minSwapAmount = 10_000
maxZeroConfAmount = 40_294_967

# preferredWallet = "core"  # 'core' or undefined (defaults to 'core'; 'lnd' not supported anymore)

[liquid.chain]
host = "127.0.0.1"
port = 18_884
cookie = "./regtest/data/elements/elements.cookie"

wallet = "regtest"

# NOTE: ZeroMQ must be configured in Elements daemon (elementsd -zmqpubrawtx=... -zmqpubhashblock=...)
# Boltz queries ZMQ endpoints automatically via RPC; they are not configured here

# mempoolSpace = "https://mempool.space/liquid/api"

# Minimum fee rate in sat/vbyte (default: 0.1)
feeFloor = 0.1

# [liquid.chain.lowball]
# host = "backup.elements"
# port = 18_884
# cookie = "./backup/elements.cookie"

# =============================================================================
# EVM Chain Configuration (RSK)
# =============================================================================

[rsk]
# Network name for display
networkName = "Anvil"
providerEndpoint = "ws://127.0.0.1:8545"
# Optional EVM mnemonic derivation path (default: m/44'/60'/0'/0/0)
# derivationPath = "m/44'/60'/0'/0/0"

# Suggested timelock for commitment swaps in minutes (should be safely above the highest swap timeout)
# commitmentTimelock = 20160

# Number of confirmations required for lockup transactions (default: 1)
# requiredConfirmations = 1

# Multiple providers for failover (optional)
# [[rsk.providers]]
# name = "Primary"
# endpoint = "http://primary.provider:8545"
#
# [[rsk.providers]]
# name = "Backup"
# endpoint = "http://backup.provider:8545"

[[rsk.contracts]]
etherSwap = "0x8464135c8F25Da09e49BC8782676a84730C318bC"
erc20Swap = "0x71C95911E9a5D330f4D621842EC243EE1343292e"

[[rsk.tokens]]
symbol = "RBTC"  # Native token (no decimals/contractAddress)

# [[rsk.tokens]]
# symbol = "USDT"
# decimals = 6
# contractAddress = "0x..."
# minWalletBalance = 10_000_000
# maxWalletBalance = 100_000_000

# =============================================================================
# EVM Chain Configuration (Ethereum)
# =============================================================================

# [ethereum]
# networkName = "Ethereum Mainnet"
# providerEndpoint = "https://eth.llamarpc.com"
# derivationPath = "m/44'/60'/0'/0/0"
#
# Suggested timelock for commitment swaps in minutes (should be safely above the highest swap timeout)
# commitmentTimelock = 20160
#
# Number of confirmations required for lockup transactions (default: 1)
# requiredConfirmations = 1
#
# Multiple providers for failover:
# [[ethereum.providers]]
# name = "Infura"
# endpoint = "https://mainnet.infura.io/v3/YOUR-KEY"
#
# [[ethereum.providers]]
# name = "Alchemy"
# endpoint = "https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY"
#
# [[ethereum.contracts]]
# etherSwap = "0x..."
# erc20Swap = "0x..."
#
# [[ethereum.tokens]]
# symbol = "USDT"
# decimals = 6
# contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"

# =============================================================================
# EVM Chain Configuration (Arbitrum)
# =============================================================================

# [arbitrum]
# networkName = "Arbitrum One"
# providerEndpoint = "https://arb1.arbitrum.io/rpc"
# derivationPath = "m/44'/60'/0'/0/0"
#
# Suggested timelock for commitment swaps in minutes (should be safely above the highest swap timeout)
# commitmentTimelock = 20160
#
# Number of confirmations required for lockup transactions (default: 1)
# requiredConfirmations = 1
#
# L1 providers for gas estimation:
# [[arbitrum.l1Providers]]
# name = "Ethereum Mainnet"
# endpoint = "https://eth.llamarpc.com"
#
# [[arbitrum.contracts]]
# etherSwap = "0x..."
# erc20Swap = "0x..."
#
# [[arbitrum.tokens]]
# symbol = "TBTC"
# decimals = 18
# contractAddress = "0x.."
#
# [arbitrum.quoters]
# weth = "0x.."
#
# [arbitrum.quoters.uniswapV3]
# factory = "0x.."
# quoter = "0x.."
# router = "0x.."
# multicall = "0x.."  # Optional; only override for custom deployments
# liquidTokens = ["0x..", "0x.."] # Optional; tokens to route through for better quotes

# =============================================================================
# ARK Pool Configuration (optional)
# =============================================================================

# [ark]
# symbol = "ARK"
# network = "bitcoinMainnet"
#
# host = "127.0.0.1"
# port = 9_100
#
# minWalletBalance = 10_000_000
# maxZeroConfAmount = 100_000
#
# useLocktimeSeconds = true  # Use seconds for locktimes instead of blocks
#
# [ark.unilateralDelays]
# claim = 16  # In blocks
# refund = 32
# refundWithoutReceiver = 64

# =============================================================================
# Swap Behavior
# =============================================================================

[swap]
# Currency symbols for which claims should be deferred
deferredClaimSymbols = ["BTC", "L-BTC"]

# Batch claim interval (cron format: */15 * * * * = every 15 minutes)
batchClaimInterval = "*/15 * * * *"

expiryTolerance = 120  # Minutes before expiry to trigger claim

cltvDelta = 20  # CLTV delta for Lightning invoices (blocks)

# sweepAmountTrigger = 1_000_000

# Scheduled sweep trigger (optional)
# [swap.scheduleAmountTrigger]
# interval = "0 */4 * * *"  # Cron format
# threshold = 500_000

# Multipliers applied to base fees to calculate minimum swap amounts (default: 6 for all)
# Higher values ensure swaps are economically viable relative to on-chain costs
# [swap.minSwapSizeMultipliers]
# submarine = 6
# reverse = 6
# chain = 6

# [swap.overpayment]
# exemptAmount = 10_000  # Amount below which overpayment is accepted
# maxPercentage = 2

# paymentTimeoutMinutes = 60

# =============================================================================
# Routing Fee Configuration
# =============================================================================

[routing]
# Default routing fee ratio (0.0035 = 0.35%)
default = 0.0035

# [routing.overrides]
# "nodepubkey1" = 0.0050
# "nodepubkey2" = 0.0025

# =============================================================================
# Node Switching (optional)
# =============================================================================

# [nodeSwitch]
# swapNode = "<lnd-node-pubkey>"  # Default node pubkey
#
# Amount threshold for switching to CLN:
# Option A - Single threshold for all swap types:
# clnAmountThreshold = 1_000_000
#
# Option B - Per-swap-type thresholds (use instead of Option A):
# [nodeSwitch.clnAmountThreshold]
# submarine = 1_000_000
# reverse = 5_000_000
#
# [nodeSwitch.referralsIds]
# "referral-1" = "<lnd-node-pubkey>"
# "referral-2" = "<cln-node-pubkey>"
#
# [nodeSwitch.preferredForNode]
# "destination-node-pubkey" = "<cln-node-pubkey>"

# =============================================================================
# Referral Defaults (optional)
# =============================================================================
#
# These defaults are merged with referral config from the database.
# Database config overrides duplicate keys from this file.
#
# [referrals.pro.pairs."BTC/BTC"]
# showHidden = true
# maxRoutingFee = 0.001

# =============================================================================
# Exchange Rates
# =============================================================================

[rates]
# Update interval in minutes (default: 1)
interval = 1

# =============================================================================
# Sidecar (Rust Service) Configuration
# =============================================================================

[sidecar]
# dataDir = "/var/lib/boltz/sidecar"
# path = "/usr/local/bin/boltzr"
# logFile = "/var/lib/boltz/sidecar/sidecar.log"

[sidecar.grpc]
host = "127.0.0.1"
port = 9003
# disableSsl = false
# certificates = "/var/lib/boltz/certificates"

[sidecar.ws]
host = "127.0.0.1"
port = 9004

[sidecar.api]
host = "127.0.0.1"
port = 9005

# [sidecar.webhook]
# retryInterval = 60
# requestTimeout = 15
# maxRetries = 5
# blockList = ["blocked.domain.com"]

# [sidecar.metrics]
# host = "127.0.0.1"
# port = 9_093

boltzr-cli

The boltzr-cli tool allows you to interact with a running backend over gRPC. It can be useful for backend development and to perform maintenance tasks. It is available on the bin folder after compiling the backend.