🐳 Backend Development
This document describes how to set up a Docker regtest environment for Boltz Backend development.
Getting Started
- The latest Node.js LTS and npm installed. We recommend using nvm to manage npm installs:
nvm install --lts - Docker
The regtest environment of the Boltz Backend is based on boltz-regtest. 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"]
# 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"
# 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"
#
# 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"
#
# 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>"
# =============================================================================
# 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_093boltzr-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.