Main Menu
Examples
Onchain / Lightning swaps on Voltage using Loop
8 min
lightning loop on voltage — full rest examples what you need (one time) your node’s public api host (from the voltage dashboard) your super admin macaroon (from the voltage dashboard) requests authenticate with header grpc metadata macaroon carrying the hex of your macaroon gui alternative you can also do all of this from terminal web (lit bundles loop) pair via lnc from your voltage dashboard quick env setup \# required set your host and macaroon (hex) export host="\<your node hostname> 443" \# if you have the file locally \# export macaroon hex=$(xxd ps u c 1000 /path/to/loop macaroon) \# sanity check curl s "https //$host/v1/loop/info" \\ h "grpc metadata macaroon $macaroon hex" | jq /v1/loop/info returns daemon details when auth is good (lightning engineering) drop in script with both swaps and full lifecycle save as loop examples sh , run chmod +x loop examples sh to make it executable requires jq and xxd \#!/usr/bin/env bash set euo pipefail \# config you can tweak "${host ?set host (e g node 123 voltageapp io or node 123 voltageapp io 443)}" "${macaroon hex ?set macaroon hex (hex of super admin macaroon)}" \# fee policy defaults (simple + safe starting points) \# for server fee 1% of amt, hard capped swap fee percent default=1 # 1% swap fee hardcap sats default=3000 \# miner fee cap (sweep/publish) start with quote's miner fee 2 as a buffer miner fee multiplier default=2 \# off chain routing fee cap (loop out) route fee msat default=50000 # 50k msat \# sweep/publish conf target conf target default=6 \# poll interval poll secs=15 poll timeout secs=$((60 60)) # 1 hour hdr() { echo n "grpc metadata macaroon $macaroon hex"; } get json() { local path="$1" curl ss "https //$host$path" h "$(hdr)" } post json() { local path="$1" body="$2" curl ss "https //$host$path" h "$(hdr)" h 'content type application/json' d "$body" } ceil div() { awk v n="$1" v d="$2" 'begin{printf "%d\n", (n + d 1)/d}'; } budget caps from amt() { local amt sats="$1" local fee pct="${2 $swap fee percent default}" local hardcap="${3 $swap fee hardcap sats default}" local pct fee=$(( amt sats fee pct / 100 )) if (( pct fee > hardcap )); then echo "$hardcap"; else echo "$pct fee"; fi } poll swap() { local id="$1" start ts start ts="$(date +%s)" echo "polling /v1/loop/swap/{id} every ${poll secs}s… (timeout ${poll timeout secs}s)" while ; do local now js state now="$(date +%s)" if (( now start ts > poll timeout secs )); then echo "timeout reached; last known state " >&2 get json "/v1/loop/swap/${id}" | jq exit 1 fi js="$(get json "/v1/loop/swap/${id}")" state="$(jq r ' state' <<<"$js")" echo "$(date is) state=$state" case "$state" in success|succeeded) echo "swap succeeded ✅"; jq <<<"$js"; break;; failed) echo "swap failed ❌"; jq <<<"$js"; exit 2;; ) sleep "$poll secs";; esac done } \# ============= loop out (lightning > on chain) ============= loop out() { local amt sats="$1" dest addr="$2" local conf target="${3 $conf target default}" echo "== loop out terms ==" get json "/v1/loop/out/terms" | jq # min/max, cltv ranges echo "== loop out quote for ${amt sats} sats (conf target=$conf target) ==" local quote quote="$(get json "/v1/loop/out/quote/${amt sats}?conf target=${conf target}")" echo "$quote" | jq local swap fee prepay amt sweep fee swap fee="$(jq r ' swap fee sat|tonumber' <<<"$quote")" prepay amt="$(jq r ' prepay amt sat|tonumber' <<<"$quote")" sweep fee="$(jq r ' htlc sweep fee sat|tonumber' <<<"$quote")" \# compute budget caps local max swap fee max miner fee max route msat max swap fee="$(budget caps from amt "$amt sats")" # includes prepay by contract max miner fee=$(( sweep fee miner fee multiplier default )) max route msat="${route fee msat default}" echo "== budget check ==" echo "server fee quote ${swap fee} sats (prepay ${prepay amt} sats is part of server fee)" echo "miner (sweep) fee est ${sweep fee} sats; cap we will set ${max miner fee} sats" echo "server fee cap we will set ${max swap fee} sats (policy 1% up to ${swap fee hardcap sats default})" if (( swap fee > max swap fee )); then echo "✋ quote swap fee (${swap fee}) exceeds cap (${max swap fee}) tune caps or amount " >&2 exit 3 fi echo "== loop out create swap ==" local body created id body="$(jq n argjson amt "$amt sats" \\ \ arg dest "$dest addr" \\ \ argjson max swap fee "$max swap fee" \\ \ argjson max miner fee "$max miner fee" \\ \ argjson max prepay amt "$prepay amt" \\ \ argjson max swap routing fee "$max route msat" \\ \ argjson sweep conf target "$conf target" \\ '{amt, dest, max swap fee, max miner fee, max prepay amt, max swap routing fee, sweep conf target}')" created="$(post json "/v1/loop/out" "$body")" echo "$created" | jq id="$(jq r ' id' <<<"$created")" echo "== loop out monitor swap id ==" poll swap "$id" } \# ============= loop in (on chain > lightning) ============= loop in() { local amt sats="$1" local conf target="${2 $conf target default}" echo "== loop in terms ==" get json "/v1/loop/in/terms" | jq echo "== (optional) probe routeability for ${amt sats} sats ==" get json "/v1/loop/in/probe/${amt sats}" | jq echo "== loop in quote for ${amt sats} sats (htlc conf target=$conf target) ==" local quote quote="$(get json "/v1/loop/in/quote/${amt sats}?conf target=${conf target}")" echo "$quote" | jq local swap fee publish fee swap fee="$(jq r ' swap fee sat|tonumber' <<<"$quote")" publish fee="$(jq r ' htlc publish fee sat|tonumber' <<<"$quote")" \# compute budget caps local max swap fee max miner fee max swap fee="$(budget caps from amt "$amt sats")" max miner fee=$(( publish fee miner fee multiplier default )) echo "== budget check ==" echo "server fee quote ${swap fee} sats" echo "publish fee est ${publish fee} sats; cap we will set ${max miner fee} sats" echo "server fee cap we will set ${max swap fee} sats (policy 1% up to ${swap fee hardcap sats default})" if (( swap fee > max swap fee )); then echo "✋ quote swap fee (${swap fee}) exceeds cap (${max swap fee}) tune caps or amount " >&2 exit 3 fi echo "== loop in create swap ==" local body created id body="$(jq n argjson amt "$amt sats" \\ \ argjson max swap fee "$max swap fee" \\ \ argjson max miner fee "$max miner fee" \\ \ argjson htlc conf target "$conf target" \\ '{amt, max swap fee, max miner fee, htlc conf target}')" created="$(post json "/v1/loop/in" "$body")" echo "$created" | jq id="$(jq r ' id' <<<"$created")" echo "== loop in monitor swap id ==" poll swap "$id" } \# cl interface usage() { cat <\<usage usage $0 out \<amt sats> \<onchain dest> \[conf target] $0 in \<amt sats> \[conf target] examples $0 out 250000 bc1qyourdest 6 $0 in 250000 6 usage } cmd="${1 }"; shift || true case "${cmd }" in out) "${1 ?amt sats}"; "${2 ?dest addr}"; loop out "$1" "$2" "${3 }";; in) "${1 ?amt sats}"; loop in "$1" "${2 }";; ) usage; exit 1;; esac what this script demonstrates (mapping to loop api) terms get /v1/loop/out/terms and get /v1/loop/in/terms for bounds (min/max, cltv ranges) (lightning engineering) quote get /v1/loop/out/quote/{amt} (returns swap fee sat, prepay amt sat, htlc sweep fee sat) and get /v1/loop/in/quote/{amt} (returns swap fee sat, htlc publish fee sat) (lightning engineering) budget check we compare the quote vs caps you’ll actually send in the create swap call loop out caps max swap fee ( includes prepay ), max miner fee, max swap routing fee, sweep conf target (lightning engineering) loop in caps max swap fee, max miner fee, htlc conf target (lightning engineering) create swap post /v1/loop/out and post /v1/loop/in; response includes the id you’ll use to monitor (lightning engineering) poll get /v1/loop/swap/{id} the state eventually becomes success or failed; we poll until a terminal state (these states are enumerated in monitor/listswaps docs ) (lightning engineering) fee & reliability playbook (simple policy you can adjust) always fetch terms → quote first; populate your caps from the quote (lightning engineering) start with ≈1% of amount for max swap fee (cap with a hard ceiling you’re comfortable with, e g , 100–3000 sats) keep max miner fee ≈ 2× the miner fee estimate from the quote to absorb fee volatility; tune conf target to trade speed vs cost (loop out uses sweep conf target; loop in uses htlc conf target ) (lightning engineering) if a loop out fails due to routing, retry once with a higher max swap routing fee (keep server fee cap the same) for loop in, you can probe with /v1/loop/in/probe/{amt} to sanity check routeability before creating the swap (lightning engineering) voltage specific notes tls your node’s api host uses a trusted ca signed certificate ; you don’t need to pass a local tls cert to curl macaroons loop requires super admin macaroon to use it throught the api on voltage access control if you’ve enabled ip whitelisting on your node, be sure your client ip is allowed for both rest and grpc apis (voltage documentation) ui prefer a gui? use terminal web (lit bundles loop/pool/faraday) via an lnc pairing phrase from your voltage dashboard endpoint reference (official) rest index (all loop endpoints) https //lightning engineering/api docs/api/loop/rest endpoints/ https //lightning engineering/api docs/api/loop/rest endpoints/ loop out /v1/loop/out https //lightning engineering/api docs/api/loop/swap client/loop out/ https //lightning engineering/api docs/api/loop/swap client/loop out/ loop in /v1/loop/in https //lightning engineering/api docs/api/loop/swap client/loop in/ https //lightning engineering/api docs/api/loop/swap client/loop in/ monitro single swap /v1/loop/swap/{id} https //lightning engineering/api docs/api/loop/swap client/swap info/ https //lightning engineering/api docs/api/loop/swap client/swap info/ monitor multiple swaps /v1/loop/swaps https //lightning engineering/api docs/api/loop/swap client/list swaps/ https //lightning engineering/api docs/api/loop/swap client/list swaps/