A lightweight Web UI for PeerSwap, which allows trustless p2p submarine swaps Lightning⇔BTC and Lightning⇔Liquid. Also facilitates BTC⇒Liquid peg-ins and automatic channel fee management. PeerSwap with Liquid is a great cost efficient way to rebalance lightning channels.
This source code is free speech. The contributors do not solicit its use for any purpose, do not control, are not responsible for and gain nothing from such use.
PeerSwap requires Bitcoin Core installed and synced, Elements Core installed and synced, LND or Core Lightning installed.
mkdir -p ~/.peerswap && \
docker run --net=host \
--user 1000:1000 \
-v ~/.lnd:/home/peerswap/.lnd:ro \
-v ~/.elements:/home/peerswap/.elements:ro \
-v ~/.peerswap:/home/peerswap/.peerswap \
-e ELEMENTS_FOLDER="/home/$(whoami)/.elements" \
-e ELEMENTS_FOLDER_MAPPED="/home/peerswap/.elements" \
-e HOSTNAME="$(hostname)" \
-e NETWORK="testnet" \
ghcr.io/impa10r/peerswap-web:latest
This example assumes .lnd and .elements folders in the host user's home directory, and connects to LND via host network. Change "testnet" to "mainnet" for production.
Config files should exist or wiil be created with default values. Depending on how your LND and Elements Core are actually installed, may require different parameters (-e). If -e NETWORK="testnet" is ommitted, mainnet assumed. See Umbrel integration for all supported env variables.
If you need to run pscli in the docker container, first lookup container id with docker ps
. Then run docker exec "container id" pscli
.
If Elements is also run in a Docker container, it should be started by the same user as the PeerSwap one (user id 1000:1000).
Please note that configuration files of the Docker version are not compatible with the manual build.
Install golang from https://go.dev/doc/install
Install and configure PeerSwap. Please consult these instructions for LND and these for CLN.
Clone the repository and build PeerSwap Web UI:
git clone https://github.com/Impa10r/peerswap-web && \
cd peerswap-web && \
make -j$(nproc) install-cln
PeerSwap Web UI is a now in your GOPATH (~/go/bin). To launch it as a CLN plugin, add plugin=/home/USER/go/bin/psweb
to your ~/.lightning/config
file and restart lightningd
(replace USER with your username).
git clone https://github.com/Impa10r/peerswap-web && \
cd peerswap-web && \
make -j$(nproc) install-lnd
This will install psweb
to your GOPATH (~/go/bin).
To start psweb as a daemon, create a systemd service file as follows (replace USER with your username):
sudo nano /etc/systemd/system/psweb.service
[Unit]
Description=PeerSwap Web UI
[Service]
ExecStart=/home/USER/go/bin/psweb
User=USER
Type=simple
KillMode=process
TimeoutSec=180
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Save with ctrl-S, exit with ctrl-X
Now start the service, check that it runs, then enable it on startup:
sudo systemctl start psweb
sudo systemctl status psweb
sudo systemctl enable psweb
The log and the config file will be saved to peerswap folder (~/.peerswap
for LND and ~/.lightning/bitcoin/peerswap
for CLN).
By default, PeerSwap Web UI will listen on localhost:1984. This port can be changed in pswebconfig.json
.
Once opened the UI, set the Links on the Config page for testnet or mainnet. If an environment variable NETWORK is present and equals "testnet", the links will be configured automatically for testnet on the first run.
To enable downloading of a backup file of the Elements wallet it is necessary to have access to .elements folder where this backup is saved by elementsd. If Elements is run in a Docker container, both the internal folder (usually /home/elements/.elements) and the mapped external folder (for Umbrel it is /home/umbrel/umbrel/app-data/elements/data) must be provided in the Configuration panel.
Warning If you tried PS Web's Docker version first and then switched to the one built from source, the configuration files will be incorrect. The easiest way to fix this is to delete peerswap.conf
and pswebconfig.json
.
PeerSwap Web UI can be initialized in HTTPS mode with a pre-set password using -password key. CA and server certificates will be generated and saved in the data folder. See more here.
When a new version comes out, just build the app again and restart:
rm -rf peerswap-web && \
git clone https://github.com/Impa10r/peerswap-web && \
cd peerswap-web && \
make -j$(nproc) install-lnd && \
sudo systemctl restart psweb
rm -rf peerswap-web && \
git clone https://github.com/Impa10r/peerswap-web && \
cd peerswap-web && \
make -j$(nproc) install-cln && \
lightning-cli -k plugin subcommand=stop plugin=${HOME}/go/bin/psweb && \
lightning-cli -k plugin subcommand=start plugin=${HOME}/go/bin/psweb
Liquid BTC is more custodial than Bitcoin and Lightning. We do not advise accumulating large balances for long-term holding. Once you gained Liquid in a peer swap-in or a peg-in process, it is better to initiate own swap in to rebalance a channel of your choice.
Currently, it is not possible to prevent swap outs by other peers while allowing receipt of swap ins. You don't want your Liquid balance taken, because such a rebalancing may not be optimal for you (but optimal for your peer).
You may enable automatic deployment of L-BTC deposits, as soon as they arrive, to rebalance your most profitable channels. This option is available from Liquid page.
Elements Core wallet has no seed phrase recovery
Take care to backup PeerSwap wallet after each Liquid transaction. In case of a catastrophic failure of your SSD all L-BTC funds may be lost. Always run your node with a UPS.
Backup is done from the Liquid page. The file name <hexkey>.bak
is your master blinding key hex. The contents is the backup of wallet.dat. For safety, this .bak will be zipped with the same password as the Elements RPC. For Umbrel, this is the password displayed in the Elements Core App. For the rest, this is the .elements/elements.conf rpcpassword parameter.
Make sure you keep this password safe in a separate location!
Restoring the wallet will require elements-cli command line skills:
elements-cli restorewallet "wallet_name" "backup_file"
Default wallet_name is "peerswap", the same value as in peerswap.conf elementsd.rpcwallet parameter.
To restore your master blinding key use:
elements-cli importmasterblindingkey "hexkey"
For Umbrel and other Docker installs it will be necessary first to copy the backup file inside the container:
docker cp /home/umbrel/peerswap.bak elements_node_1:/home/elements/peerswap.bak
docker exec -it elements_node_1 elements-cli -rpcuser=elements -rpcpassword=49...d1e restorewallet "peerswap" "/home/elements/peerswap.bak"
docker exec -it elements_node_1 elements-cli -rpcuser=elements -rpcpassword=49...d1e importmasterblindingkey "hexkey"
DO NOT uninstall Elements Core unless all liquid funds are spent or you have backed up your wallet.
Create a new telegram bot with BotFather and copy API Token to PS Web Configuration page. Type /start. The backup file will be sent upon every change of the Liquid balance. To re-use an existing bot make sure to revoke old API Token.
Stop and disable the service:
sudo systemctl stop psweb
sudo systemctl disable psweb
Update: Since v1.2.0 this is handled via UI on the Bitcoin page.
To convert some BTC on your node into L-BTC you don't need any third party (but must run a full Bitcon node with txindex=1, or manually provide block hash from mempool.space for getrawtransaction
and gettxoutproof
):
- Generate a special BTC address:
elements-cli getpeginaddress
. Save claim_script for later. - Send BTC onchain:
lncli sendcoins --amt <sats to peg in> -addr <mainchain_address from step 1> --sat_per_vbyte <from mempool>
- Wait for 102 confirmations (about 17 hours).
- Run
bitcoin-cli getrawtransaction <txid from step 2>
- Run
bitcoin-cli gettxoutproof '["<txid from step 2>"]'
- Run
elements-cli claimpegin <raw from step 4> <proof from step 5> <claim_script from step 1>
- Your Liquid balance should update once the tx confirms (1-2 minutes)
Taken from here.
Hint for Umbrel: To save keystrokes, add these aliases to ~/.profile, then source .profile
alias lncli="docker exec -it lightning_lnd_1 lncli --lnddir /home/umbrel/umbrel/app-data/lightning/data/lnd"
alias bcli="docker exec -it bitcoin_bitcoind_1 bitcoin-cli -rpcuser=umbrel -rpcpassword=<your bitcoin password>"
alias ecli="docker exec -it elements_node_1 elements-cli -rpcuser=elements -rpcpassword=<your elements password>"
(lookup Elements and Bitcoin rpc passwords in pswebconfig.com)
Elements Core v23.2.2 introduced vsize discount for confidential transactions. Now sending a Liquid payment with a blinded amount costs the same or cheaper than a publicly visible (explicit) one. For example, claiming a peg-in with elements-cli claimpegin
costs about 45 sats, but it is possible to manually construct the same transaction (elements-cli createrawtransaction
) with confidential destination address, blind and sign it, then post and pay a lower fee. However, from privacy perspective, blinding a single peg-in claim makes little sense. The linked Bitcoin UTXO will still show the explicit amount, so it is easily traceable to your new Liquid address. To achieve a truly confidential peg-in, it is necessary to mix two or more independent claims into one single transaction, a-la CoinJoin.
In v1.7.0 PSWeb implemented such "ClaimJoin". If you opt in when starting your peg-in, your node will send invitations to all other PSWeb nodes to join in while you wait for your 102 confirmations. To join your claim, a peer should opt in while starting his own peg-in. A node responds to an invitation by anonymously sending details of its peg-in funding transaction, once it confirms, to the initiator. Peers don't know which specific node initiated the ClaimJoin and who else will be joining. The initiator also doesn't know public Ids of the nodes that responded. All communication happens blindly via single use public/private key pairs (secp256k1). Nodes who do not directly participate act as p2p relays for the encrypted messages, not being able to read them and not knowing the sources and the final destinations. This way our ClaimJoin coordination is fully confidential and not limited to direct peers.
When all N peg-ins mature, the initiator node prepares one large PSET with N peg-in inputs and N CT outputs, shuffled randomly, and sends it sequentially to all participants: first to blind Liquid outputs and then to sign peg-in inputs. Before blinding/signing and returning the PSET, each joiner verifies that his output address is there for the correct amount (allowing for a small fee haircut). Upto 10 claims can be joined this way, to fit into one custom message (64kb). The price for such privacy is time. For the initiator, the wait can take upto 34 hours if the final peer joins at block 101. For that last joiner the wait will be the same 17 hours as for a standard peg-in. If the total fee cannot be divided equally, the last joiner pays slightnly more as an incentive to join earlier next time. In practice, the blinding and signing round may need to be done twice: first to find out the exact discounted vsize of the final transaction, then to set the exact total fee at 0.1 sat/vb.
The process bears no risk to the participants. If any joiner becomes unresponsive during the blinding/signing round, he is automatically kicked out. If the initiator fails to complete the process, each joiner reverts to a standard single peg-in claim 10 blocks after the final maturity. As the last resort, if your PSWeb dies completely, you can always claim your peg-in manually with elements-cli
. All the necessary details will be in your PSWeb log. Your claim script and peg-in txid can only be used with your own Liquid wallet's private key. Blinding and signing your part happens locally on your node, no sensitive info is transmitted outside at any point.
Join PeerSwap Discord channel at PeerSwap.dev.