Lightning Loop REST API Reference
Welcome to the REST API reference documentation for Lightning Loop.
Lightning Loop is a non-custodial service offered by Lightning Labs to bridge
on-chain and off-chain Bitcoin using submarine swaps. This repository is home to
the Loop client and depends on the Lightning Network daemon lnd. All of lnd’s
supported chain backends are fully supported when using the Loop client:
Neutrino, Bitcoin Core, and btcd.
In the current iteration of the Loop software, only off-chain to on-chain
exchanges are supported, where the Loop client sends funds off-chain in exchange
for the funds back on-chain.
The service can be used in various situations:
- Acquiring inbound channel liquidity from arbitrary nodes on the Lightning
network
- Depositing funds to a Bitcoin on-chain address without closing active
channels
- Paying to on-chain fallback addresses in the case of insufficient route
liquidity
Future iterations of the Loop software will also allow on-chain to
off-chain swaps. These swaps can be useful for additional use-cases:
- Refilling depleted channels with funds from cold-wallets or exchange
withdrawals
- Servicing off-chain Lightning withdrawals using on-chain payments, with no
funds in channels required
- As a failsafe payment method that can be used when channel liquidity along a
route is insufficient
This site features the API documentation for shell script (CLI), Python and
JavaScript clients in order to communicate with a local loopd
instance through
gRPC. Currently, this communication is unauthenticated, so exposing this service
to the internet is not recommended.
The original client.proto
file from which the gRPC documentation was generated
can be found here.
NOTE: The byte
field type must be set as the base64 encoded string
representation of a raw byte array.
Alternatively, the gRPC documentation can be found here.
/v1/loop/out
$ curl -X POST http://localhost:8080/v1/loop/out -d '{ \
"max_swap_routing_fee":<string>, \
"sweep_conf_target":<int32>, \
"max_miner_fee":<string>, \
"max_prepay_routing_fee":<string>, \
"max_swap_fee":<string>, \
"max_prepay_amt":<string>, \
"dest":<string>, \
"amt":<string>, \
"loop_out_channel":<string>, \
}'
{
"id": <string>,
"htlc_address": <string>,
}
>>> import base64, json, requests
>>> url = 'http://localhost:8080/v1/loop/out'
>>> data = {
'max_swap_routing_fee': <string>,
'sweep_conf_target': <int32>,
'max_miner_fee': <string>,
'max_prepay_routing_fee': <string>,
'max_swap_fee': <string>,
'max_prepay_amt': <string>,
'dest': <string>,
'amt': <string>,
'loop_out_channel': <string>,
}
>>> r = requests.post(url, verify=cert_path, data=json.dumps(data))
>>> print(r.json())
{
"id": <string>,
"htlc_address": <string>,
}
> var request = require('request');
> var requestBody = {
max_swap_routing_fee: <string>,
sweep_conf_target: <int32>,
max_miner_fee: <string>,
max_prepay_routing_fee: <string>,
max_swap_fee: <string>,
max_prepay_amt: <string>,
dest: <string>,
amt: <string>,
loop_out_channel: <string>,
};
> var options = {
url: 'http://localhost:8080/v1/loop/out',
json: true,
form: JSON.stringify(requestBody)
};
> request.post(options, function(error, response, body) {
console.log(body);
});
{
"id": <string>,
"htlc_address": <string>,
}
POST /v1/loop/out
loop: out
LoopOut initiates an loop out swap with the given parameters. The call returns after the swap has been set up with the swap server. From that point onwards, progress can be tracked via the SwapStatus stream that is returned from Monitor().
Field |
Type |
Placement |
Description |
max_swap_routing_fee |
string |
body |
Maximum off-chain fee in msat that may be paid for payment to the server. This limit is applied during path finding. Typically this value is taken from the response of the GetQuote call. |
sweep_conf_target |
int32 |
body |
The number of blocks from the on-chain HTLC's confirmation height that it should be swept within. |
max_miner_fee |
string |
body |
Maximum in on-chain fees that we are willing to spent. If we want to sweep the on-chain htlc and the fee estimate turns out higher than this value, we cancel the swap. If the fee estimate is lower, we publish the sweep tx. If the sweep tx is not confirmed, we are forced to ratchet up fees until it is swept. Possibly even exceeding max_miner_fee if we get close to the htlc timeout. Because the initial publication revealed the preimage, we have no other choice. The server may already have pulled the off-chain htlc. Only when the fee becomes higher than the swap amount, we can only wait for fees to come down and hope - if we are past the timeout - that the server is not publishing the revocation. max_miner_fee is typically taken from the response of the GetQuote call. |
max_prepay_routing_fee |
string |
body |
Maximum off-chain fee in msat that may be paid for payment to the server. This limit is applied during path finding. Typically this value is taken from the response of the GetQuote call. |
max_swap_fee |
string |
body |
Maximum we are willing to pay the server for the swap. This value is not disclosed in the swap initiation call, but if the server asks for a higher fee, we abort the swap. Typically this value is taken from the response of the GetQuote call. It includes the prepay amount. |
max_prepay_amt |
string |
body |
Maximum amount of the swap fee that may be charged as a prepayment. |
dest |
string |
body |
Base58 encoded destination address for the swap. |
amt |
string |
body |
Requested swap amount in sat. This does not include the swap and miner fee. |
loop_out_channel |
string |
body |
The channel to loop out, the channel to loop out is selected based on the lowest routing fee for the swap payment to the server. |
Response
Field |
Type |
Description |
id |
string |
Swap identifier to track status in the update stream that is returned from the Start() call. Currently this is the hash that locks the htlcs. |
htlc_address |
string |
The address of the on-chain htlc. |
/v1/loop/in/quote
$ curl -X GET http://localhost:8080/v1/loop/in/quote/{amt}
{
"swap_fee": <string>,
"miner_fee": <string>,
"prepay_amt": <string>,
"cltv_delta": <int32>,
"swap_payment_dest": <byte>,
}
>>> import base64, json, requests
>>> url = 'http://localhost:8080/v1/loop/in/quote/{amt}'
>>> r = requests.get(url, verify=cert_path)
>>> print(r.json())
{
"swap_fee": <string>,
"miner_fee": <string>,
"prepay_amt": <string>,
"cltv_delta": <int32>,
"swap_payment_dest": <byte>,
}
> var request = require('request');
> var options = {
url: 'http://localhost:8080/v1/loop/in/quote/{amt}',
json: true
};
> request.get(options, function(error, response, body) {
console.log(body);
});
{
"swap_fee": <string>,
"miner_fee": <string>,
"prepay_amt": <string>,
"cltv_delta": <int32>,
"swap_payment_dest": <byte>,
}
GET /v1/loop/in/quote/{amt}
GetQuote returns a quote for a swap with the provided parameters.
Field |
Type |
Placement |
Description |
amt |
string |
path |
None |
conf_target |
int32 |
query |
The confirmation target that should be used either for the sweep of the on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for the confirmation of the on-chain HTLC broadcast by the swap client in the case of a Loop In. |
external_htlc |
boolean |
query |
If external_htlc is true, we expect the htlc to be published by an external actor. |
Response
Field |
Type |
Description |
swap_fee |
string |
The fee that the swap server is charging for the swap. |
miner_fee |
string |
An estimate of the on-chain fee that needs to be paid to sweep the HTLC. |
prepay_amt |
string |
The part of the swap fee that is requested as a prepayment. |
cltv_delta |
int32 |
On-chain cltv expiry delta |
swap_payment_dest |
byte |
The node pubkey where the swap payment needs to be paid to. This can be used to test connectivity before initiating the swap. |
/v1/loop/out/terms
$ curl -X GET http://localhost:8080/v1/loop/out/terms
{
"min_swap_amount": <string>,
"max_swap_amount": <string>,
}
>>> import base64, json, requests
>>> url = 'http://localhost:8080/v1/loop/out/terms'
>>> r = requests.get(url, verify=cert_path)
>>> print(r.json())
{
"min_swap_amount": <string>,
"max_swap_amount": <string>,
}
> var request = require('request');
> var options = {
url: 'http://localhost:8080/v1/loop/out/terms',
json: true
};
> request.get(options, function(error, response, body) {
console.log(body);
});
{
"min_swap_amount": <string>,
"max_swap_amount": <string>,
}
GET /v1/loop/out/terms
loop: terms
LoopOutTerms returns the terms that the server enforces for a loop out swap.
This request has no parameters.
Response
Field |
Type |
Description |
min_swap_amount |
string |
Minimum swap amount (sat) |
max_swap_amount |
string |
Maximum swap amount (sat) |
/v1/loop/in
$ curl -X POST http://localhost:8080/v1/loop/in -d '{ \
"max_swap_fee":<string>, \
"loop_in_channel":<string>, \
"max_miner_fee":<string>, \
"amt":<string>, \
"external_htlc":<boolean>, \
}'
{
"id": <string>,
"htlc_address": <string>,
}
>>> import base64, json, requests
>>> url = 'http://localhost:8080/v1/loop/in'
>>> data = {
'max_swap_fee': <string>,
'loop_in_channel': <string>,
'max_miner_fee': <string>,
'amt': <string>,
'external_htlc': <boolean>,
}
>>> r = requests.post(url, verify=cert_path, data=json.dumps(data))
>>> print(r.json())
{
"id": <string>,
"htlc_address": <string>,
}
> var request = require('request');
> var requestBody = {
max_swap_fee: <string>,
loop_in_channel: <string>,
max_miner_fee: <string>,
amt: <string>,
external_htlc: <boolean>,
};
> var options = {
url: 'http://localhost:8080/v1/loop/in',
json: true,
form: JSON.stringify(requestBody)
};
> request.post(options, function(error, response, body) {
console.log(body);
});
{
"id": <string>,
"htlc_address": <string>,
}
POST /v1/loop/in
LoopIn initiates a loop in swap with the given parameters. The call returns after the swap has been set up with the swap server. From that point onwards, progress can be tracked via the SwapStatus stream that is returned from Monitor().
Field |
Type |
Placement |
Description |
max_swap_fee |
string |
body |
Maximum we are willing to pay the server for the swap. This value is not disclosed in the swap initiation call, but if the server asks for a higher fee, we abort the swap. Typically this value is taken from the response of the GetQuote call. |
loop_in_channel |
string |
body |
The channel to loop in. If zero, the channel to loop in is selected based on the lowest routing fee for the swap payment from the server. Note: NOT YET IMPLEMENTED |
max_miner_fee |
string |
body |
Maximum in on-chain fees that we are willing to spent. If we want to publish the on-chain htlc and the fee estimate turns out higher than this value, we cancel the swap. max_miner_fee is typically taken from the response of the GetQuote call. |
amt |
string |
body |
Requested swap amount in sat. This does not include the swap and miner fee. |
external_htlc |
boolean |
body |
If external_htlc is true, we expect the htlc to be published by an external actor. |
Response
Field |
Type |
Description |
id |
string |
Swap identifier to track status in the update stream that is returned from the Start() call. Currently this is the hash that locks the htlcs. |
htlc_address |
string |
The address of the on-chain htlc. |
/v1/loop/out/quote
$ curl -X GET http://localhost:8080/v1/loop/out/quote/{amt}
{
"swap_fee": <string>,
"miner_fee": <string>,
"prepay_amt": <string>,
"cltv_delta": <int32>,
"swap_payment_dest": <byte>,
}
>>> import base64, json, requests
>>> url = 'http://localhost:8080/v1/loop/out/quote/{amt}'
>>> r = requests.get(url, verify=cert_path)
>>> print(r.json())
{
"swap_fee": <string>,
"miner_fee": <string>,
"prepay_amt": <string>,
"cltv_delta": <int32>,
"swap_payment_dest": <byte>,
}
> var request = require('request');
> var options = {
url: 'http://localhost:8080/v1/loop/out/quote/{amt}',
json: true
};
> request.get(options, function(error, response, body) {
console.log(body);
});
{
"swap_fee": <string>,
"miner_fee": <string>,
"prepay_amt": <string>,
"cltv_delta": <int32>,
"swap_payment_dest": <byte>,
}
GET /v1/loop/out/quote/{amt}
loop: quote
LoopOutQuote returns a quote for a loop out swap with the provided parameters.
Field |
Type |
Placement |
Description |
amt |
string |
path |
None |
conf_target |
int32 |
query |
The confirmation target that should be used either for the sweep of the on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for the confirmation of the on-chain HTLC broadcast by the swap client in the case of a Loop In. |
external_htlc |
boolean |
query |
If external_htlc is true, we expect the htlc to be published by an external actor. |
Response
Field |
Type |
Description |
swap_fee |
string |
The fee that the swap server is charging for the swap. |
miner_fee |
string |
An estimate of the on-chain fee that needs to be paid to sweep the HTLC. |
prepay_amt |
string |
The part of the swap fee that is requested as a prepayment. |
cltv_delta |
int32 |
On-chain cltv expiry delta |
swap_payment_dest |
byte |
The node pubkey where the swap payment needs to be paid to. This can be used to test connectivity before initiating the swap. |
/v1/loop/in/terms
$ curl -X GET http://localhost:8080/v1/loop/in/terms
{
"min_swap_amount": <string>,
"max_swap_amount": <string>,
}
>>> import base64, json, requests
>>> url = 'http://localhost:8080/v1/loop/in/terms'
>>> r = requests.get(url, verify=cert_path)
>>> print(r.json())
{
"min_swap_amount": <string>,
"max_swap_amount": <string>,
}
> var request = require('request');
> var options = {
url: 'http://localhost:8080/v1/loop/in/terms',
json: true
};
> request.get(options, function(error, response, body) {
console.log(body);
});
{
"min_swap_amount": <string>,
"max_swap_amount": <string>,
}
GET /v1/loop/in/terms
GetTerms returns the terms that the server enforces for swaps.
This request has no parameters.
Response
Field |
Type |
Description |
min_swap_amount |
string |
Minimum swap amount (sat) |
max_swap_amount |
string |
Maximum swap amount (sat) |
Properties
SwapResponse
Field |
Type |
Description |
id |
string |
Swap identifier to track status in the update stream that is returned from the Start() call. Currently this is the hash that locks the htlcs. |
htlc_address |
string |
The address of the on-chain htlc. |
SwapStatus
Field |
Type |
Description |
cost_server |
string |
Swap server cost |
htlc_address |
string |
Htlc address. |
initiation_time |
string |
Initiation time of the swap. |
last_update_time |
string |
Initiation time of the swap. |
cost_offchain |
string |
Off-chain routing fees |
type |
SwapType |
Swap type |
id |
string |
Swap identifier to track status in the update stream that is returned from the Start() call. Currently this is the hash that locks the htlcs. |
amt |
string |
Requested swap amount in sat. This does not include the swap and miner fee. |
cost_onchain |
string |
On-chain transaction cost |
state |
SwapState |
State the swap is currently in, see State enum. |
LoopInRequest
Field |
Type |
Description |
max_swap_fee |
string |
Maximum we are willing to pay the server for the swap. This value is not disclosed in the swap initiation call, but if the server asks for a higher fee, we abort the swap. Typically this value is taken from the response of the GetQuote call. |
loop_in_channel |
string |
The channel to loop in. If zero, the channel to loop in is selected based on the lowest routing fee for the swap payment from the server. Note: NOT YET IMPLEMENTED |
max_miner_fee |
string |
Maximum in on-chain fees that we are willing to spent. If we want to publish the on-chain htlc and the fee estimate turns out higher than this value, we cancel the swap. max_miner_fee is typically taken from the response of the GetQuote call. |
amt |
string |
Requested swap amount in sat. This does not include the swap and miner fee. |
external_htlc |
boolean |
If external_htlc is true, we expect the htlc to be published by an external actor. |
LoopOutRequest
Field |
Type |
Description |
max_swap_routing_fee |
string |
Maximum off-chain fee in msat that may be paid for payment to the server. This limit is applied during path finding. Typically this value is taken from the response of the GetQuote call. |
sweep_conf_target |
int32 |
The number of blocks from the on-chain HTLC's confirmation height that it should be swept within. |
max_miner_fee |
string |
Maximum in on-chain fees that we are willing to spent. If we want to sweep the on-chain htlc and the fee estimate turns out higher than this value, we cancel the swap. If the fee estimate is lower, we publish the sweep tx. If the sweep tx is not confirmed, we are forced to ratchet up fees until it is swept. Possibly even exceeding max_miner_fee if we get close to the htlc timeout. Because the initial publication revealed the preimage, we have no other choice. The server may already have pulled the off-chain htlc. Only when the fee becomes higher than the swap amount, we can only wait for fees to come down and hope - if we are past the timeout - that the server is not publishing the revocation. max_miner_fee is typically taken from the response of the GetQuote call. |
max_prepay_routing_fee |
string |
Maximum off-chain fee in msat that may be paid for payment to the server. This limit is applied during path finding. Typically this value is taken from the response of the GetQuote call. |
max_swap_fee |
string |
Maximum we are willing to pay the server for the swap. This value is not disclosed in the swap initiation call, but if the server asks for a higher fee, we abort the swap. Typically this value is taken from the response of the GetQuote call. It includes the prepay amount. |
max_prepay_amt |
string |
Maximum amount of the swap fee that may be charged as a prepayment. |
dest |
string |
Base58 encoded destination address for the swap. |
amt |
string |
Requested swap amount in sat. This does not include the swap and miner fee. |
loop_out_channel |
string |
The channel to loop out, the channel to loop out is selected based on the lowest routing fee for the swap payment to the server. |
TermsResponse
Field |
Type |
Description |
min_swap_amount |
string |
Minimum swap amount (sat) |
max_swap_amount |
string |
Maximum swap amount (sat) |
QuoteResponse
Field |
Type |
Description |
swap_fee |
string |
The fee that the swap server is charging for the swap. |
miner_fee |
string |
An estimate of the on-chain fee that needs to be paid to sweep the HTLC. |
prepay_amt |
string |
The part of the swap fee that is requested as a prepayment. |
cltv_delta |
int32 |
On-chain cltv expiry delta |
swap_payment_dest |
byte |
The node pubkey where the swap payment needs to be paid to. This can be used to test connectivity before initiating the swap. |
Enums
SwapState
Name |
Value |
INITIATED |
0 |
PREIMAGE_REVEALED |
1 |
HTLC_PUBLISHED |
2 |
SUCCESS |
3 |
FAILED |
4 |
INVOICE_SETTLED |
5 |
SwapType
Name |
Value |
LOOP_OUT |
0 |
LOOP_IN |
1 |