NAV Navbar
shell python javascript

Lightning Loop gRPC API Reference

Welcome to the gRPC 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:

Future iterations of the Loop software will also allow on-chain to off-chain swaps. These swaps can be useful for additional use-cases:

This site features the documentation for loop (CLI), and the API documentation for 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.

Alternatively, the REST documentation can be found here.

LoopOut

Simple RPC

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().


# Attempts loop out the target amount into either the backing lnd's
# wallet, or a targeted address.
# The amount is to be specified in satoshis.
# Optionally a BASE58/bech32 encoded bitcoin destination address may be
# specified. If not specified, a new wallet address will be generated.

$ loop out [command options] amt [addr]

# --channel value      the 8-byte compact channel ID of the channel to loop out (default: 0)
# --addr value         the optional address that the looped out funds should be sent to, if let blank the funds will go to lnd's wallet
# --amt value          the amount in satoshis to loop out (default: 0)
# --conf_target value  the number of blocks from the swap initiation height that the on-chain HTLC should be swept within (default: 6)
>>> import grpc
>>> import client_pb2 as loop
>>> import client_pb2_grpc as looprpc
>>> channel = grpc.insecure_channel('localhost:11010')
>>> stub = looprpc.SwapClientStub(channel)
>>> request = loop.LoopOutRequest(
        amt=<int64>,
        dest=<string>,
        max_swap_routing_fee=<int64>,
        max_prepay_routing_fee=<int64>,
        max_swap_fee=<int64>,
        max_prepay_amt=<int64>,
        max_miner_fee=<int64>,
        loop_out_channel=<uint64>,
        sweep_conf_target=<int32>,
    )
>>> response = stub.LoopOut(request)
>>> print(response)
{ 
    "id": <string>,
    "htlc_address": <string>,
}
> var grpc = require('grpc');
> var protoLoader = require('@grpc/proto-loader');
> var packageDefinition = protoLoader.loadSync(
    './client.proto',
    {
       keepCase: true,
       longs: String,
       enums: String,
       defaults: true,
       oneofs: true
    },
  );
> var looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
> var swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
> var request = { 
    amt: <int64>, 
    dest: <string>, 
    max_swap_routing_fee: <int64>, 
    max_prepay_routing_fee: <int64>, 
    max_swap_fee: <int64>, 
    max_prepay_amt: <int64>, 
    max_miner_fee: <int64>, 
    loop_out_channel: <uint64>, 
    sweep_conf_target: <int32>, 
  }
> swapClient.loopOut(request, function(err, response) {
    console.log(response);
  })
{ 
    "id": <string>,
    "htlc_address": <string>,
}

gRPC Request: LoopOutRequest

Parameter Type Description
amt int64 Requested swap amount in sat. This does not include the swap and miner fee.
dest string Base58 encoded destination address for the swap.
max_swap_routing_fee int64 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_prepay_routing_fee int64 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 int64 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 int64 Maximum amount of the swap fee that may be charged as a prepayment.
max_miner_fee int64 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.
loop_out_channel uint64 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.
sweep_conf_target int32 The number of blocks from the on-chain HTLC's confirmation height that it should be swept within.

gRPC Response: SwapResponse

Parameter 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.

LoopIn

Simple RPC

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().


>>> import grpc
>>> import client_pb2 as loop
>>> import client_pb2_grpc as looprpc
>>> channel = grpc.insecure_channel('localhost:11010')
>>> stub = looprpc.SwapClientStub(channel)
>>> request = loop.LoopInRequest(
        amt=<int64>,
        max_swap_fee=<int64>,
        max_miner_fee=<int64>,
        loop_in_channel=<uint64>,
        external_htlc=<bool>,
    )
>>> response = stub.LoopIn(request)
>>> print(response)
{ 
    "id": <string>,
    "htlc_address": <string>,
}
> var grpc = require('grpc');
> var protoLoader = require('@grpc/proto-loader');
> var packageDefinition = protoLoader.loadSync(
    './client.proto',
    {
       keepCase: true,
       longs: String,
       enums: String,
       defaults: true,
       oneofs: true
    },
  );
> var looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
> var swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
> var request = { 
    amt: <int64>, 
    max_swap_fee: <int64>, 
    max_miner_fee: <int64>, 
    loop_in_channel: <uint64>, 
    external_htlc: <bool>, 
  }
> swapClient.loopIn(request, function(err, response) {
    console.log(response);
  })
{ 
    "id": <string>,
    "htlc_address": <string>,
}

gRPC Request: LoopInRequest

Parameter Type Description
amt int64 Requested swap amount in sat. This does not include the swap and miner fee.
max_swap_fee int64 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.
max_miner_fee int64 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.
loop_in_channel uint64 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
external_htlc bool If external_htlc is true, we expect the htlc to be published by an external actor.

gRPC Response: SwapResponse

Parameter 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.

Monitor

Response-streaming RPC

Monitor will return a stream of swap updates for currently active swaps. TODO: add MonitorSync version for REST clients.


# Allows the user to monitor progress of any active swaps

$ loop monitor [arguments...]

>>> import grpc
>>> import client_pb2 as loop
>>> import client_pb2_grpc as looprpc
>>> channel = grpc.insecure_channel('localhost:11010')
>>> stub = looprpc.SwapClientStub(channel)
>>> request = loop.MonitorRequest()
>>> for response in stub.Monitor(request):
        print(response)
{ 
    "amt": <int64>,
    "id": <string>,
    "type": <SwapType>,
    "state": <SwapState>,
    "initiation_time": <int64>,
    "last_update_time": <int64>,
    "htlc_address": <string>,
    "cost_server": <int64>,
    "cost_onchain": <int64>,
    "cost_offchain": <int64>,
}
> var grpc = require('grpc');
> var protoLoader = require('@grpc/proto-loader');
> var packageDefinition = protoLoader.loadSync(
    './client.proto',
    {
       keepCase: true,
       longs: String,
       enums: String,
       defaults: true,
       oneofs: true
    },
  );
> var looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
> var swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
> var request = {}
> var call = swapClient.monitor(request)
> call.on('data', function(response) {
    // A response was received from the server.
    console.log(response);
  });
> call.on('status', function(status) {
    // The current status of the stream.
  });
> call.on('end', function() {
    // The server has closed the stream.
  });
{ 
    "amt": <int64>,
    "id": <string>,
    "type": <SwapType>,
    "state": <SwapState>,
    "initiation_time": <int64>,
    "last_update_time": <int64>,
    "htlc_address": <string>,
    "cost_server": <int64>,
    "cost_onchain": <int64>,
    "cost_offchain": <int64>,
}

gRPC Request: MonitorRequest

This request has no parameters.

gRPC Response: SwapStatus (Streaming)

Parameter Type Description
amt int64 Requested swap amount in sat. This does not include the swap and miner fee.
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.
type SwapType Swap type
state SwapState State the swap is currently in, see State enum.
initiation_time int64 Initiation time of the swap.
last_update_time int64 Initiation time of the swap.
htlc_address string Htlc address.
cost_server int64 Swap server cost
cost_onchain int64 On-chain transaction cost
cost_offchain int64 Off-chain routing fees

LoopOutTerms

Simple RPC

LoopOutTerms returns the terms that the server enforces for a loop out swap.


# Display the current swap terms imposed by the server.

$ loop terms [arguments...]

>>> import grpc
>>> import client_pb2 as loop
>>> import client_pb2_grpc as looprpc
>>> channel = grpc.insecure_channel('localhost:11010')
>>> stub = looprpc.SwapClientStub(channel)
>>> request = loop.TermsRequest()
>>> response = stub.LoopOutTerms(request)
>>> print(response)
{ 
    "min_swap_amount": <int64>,
    "max_swap_amount": <int64>,
}
> var grpc = require('grpc');
> var protoLoader = require('@grpc/proto-loader');
> var packageDefinition = protoLoader.loadSync(
    './client.proto',
    {
       keepCase: true,
       longs: String,
       enums: String,
       defaults: true,
       oneofs: true
    },
  );
> var looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
> var swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
> var request = {}
> swapClient.loopOutTerms(request, function(err, response) {
    console.log(response);
  })
{ 
    "min_swap_amount": <int64>,
    "max_swap_amount": <int64>,
}

gRPC Request: TermsRequest

This request has no parameters.

gRPC Response: TermsResponse

Parameter Type Description
min_swap_amount int64 Minimum swap amount (sat)
max_swap_amount int64 Maximum swap amount (sat)

LoopOutQuote

Simple RPC

LoopOutQuote returns a quote for a loop out swap with the provided parameters.


# Allows to determine the cost of a swap up front

$ loop quote [command options] amt

# --conf_target value  the number of blocks from the swap initiation height that the on-chain HTLC should be swept within in a Loop Out (default: 6)
>>> import grpc
>>> import client_pb2 as loop
>>> import client_pb2_grpc as looprpc
>>> channel = grpc.insecure_channel('localhost:11010')
>>> stub = looprpc.SwapClientStub(channel)
>>> request = loop.QuoteRequest(
        amt=<int64>,
        conf_target=<int32>,
        external_htlc=<bool>,
    )
>>> response = stub.LoopOutQuote(request)
>>> print(response)
{ 
    "swap_fee": <int64>,
    "prepay_amt": <int64>,
    "miner_fee": <int64>,
    "swap_payment_dest": <bytes>,
    "cltv_delta": <int32>,
}
> var grpc = require('grpc');
> var protoLoader = require('@grpc/proto-loader');
> var packageDefinition = protoLoader.loadSync(
    './client.proto',
    {
       keepCase: true,
       longs: String,
       enums: String,
       defaults: true,
       oneofs: true
    },
  );
> var looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
> var swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
> var request = { 
    amt: <int64>, 
    conf_target: <int32>, 
    external_htlc: <bool>, 
  }
> swapClient.loopOutQuote(request, function(err, response) {
    console.log(response);
  })
{ 
    "swap_fee": <int64>,
    "prepay_amt": <int64>,
    "miner_fee": <int64>,
    "swap_payment_dest": <bytes>,
    "cltv_delta": <int32>,
}

gRPC Request: QuoteRequest

Parameter Type Description
amt int64 The amount to swap in satoshis.
conf_target int32 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 bool If external_htlc is true, we expect the htlc to be published by an external actor.

gRPC Response: QuoteResponse

Parameter Type Description
swap_fee int64 The fee that the swap server is charging for the swap.
prepay_amt int64 The part of the swap fee that is requested as a prepayment.
miner_fee int64 An estimate of the on-chain fee that needs to be paid to sweep the HTLC.
swap_payment_dest bytes The node pubkey where the swap payment needs to be paid to. This can be used to test connectivity before initiating the swap.
cltv_delta int32 On-chain cltv expiry delta

GetLoopInTerms

Simple RPC

GetTerms returns the terms that the server enforces for swaps.


>>> import grpc
>>> import client_pb2 as loop
>>> import client_pb2_grpc as looprpc
>>> channel = grpc.insecure_channel('localhost:11010')
>>> stub = looprpc.SwapClientStub(channel)
>>> request = loop.TermsRequest()
>>> response = stub.GetLoopInTerms(request)
>>> print(response)
{ 
    "min_swap_amount": <int64>,
    "max_swap_amount": <int64>,
}
> var grpc = require('grpc');
> var protoLoader = require('@grpc/proto-loader');
> var packageDefinition = protoLoader.loadSync(
    './client.proto',
    {
       keepCase: true,
       longs: String,
       enums: String,
       defaults: true,
       oneofs: true
    },
  );
> var looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
> var swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
> var request = {}
> swapClient.getLoopInTerms(request, function(err, response) {
    console.log(response);
  })
{ 
    "min_swap_amount": <int64>,
    "max_swap_amount": <int64>,
}

gRPC Request: TermsRequest

This request has no parameters.

gRPC Response: TermsResponse

Parameter Type Description
min_swap_amount int64 Minimum swap amount (sat)
max_swap_amount int64 Maximum swap amount (sat)

GetLoopInQuote

Simple RPC

GetQuote returns a quote for a swap with the provided parameters.


>>> import grpc
>>> import client_pb2 as loop
>>> import client_pb2_grpc as looprpc
>>> channel = grpc.insecure_channel('localhost:11010')
>>> stub = looprpc.SwapClientStub(channel)
>>> request = loop.QuoteRequest(
        amt=<int64>,
        conf_target=<int32>,
        external_htlc=<bool>,
    )
>>> response = stub.GetLoopInQuote(request)
>>> print(response)
{ 
    "swap_fee": <int64>,
    "prepay_amt": <int64>,
    "miner_fee": <int64>,
    "swap_payment_dest": <bytes>,
    "cltv_delta": <int32>,
}
> var grpc = require('grpc');
> var protoLoader = require('@grpc/proto-loader');
> var packageDefinition = protoLoader.loadSync(
    './client.proto',
    {
       keepCase: true,
       longs: String,
       enums: String,
       defaults: true,
       oneofs: true
    },
  );
> var looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
> var swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
> var request = { 
    amt: <int64>, 
    conf_target: <int32>, 
    external_htlc: <bool>, 
  }
> swapClient.getLoopInQuote(request, function(err, response) {
    console.log(response);
  })
{ 
    "swap_fee": <int64>,
    "prepay_amt": <int64>,
    "miner_fee": <int64>,
    "swap_payment_dest": <bytes>,
    "cltv_delta": <int32>,
}

gRPC Request: QuoteRequest

Parameter Type Description
amt int64 The amount to swap in satoshis.
conf_target int32 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 bool If external_htlc is true, we expect the htlc to be published by an external actor.

gRPC Response: QuoteResponse

Parameter Type Description
swap_fee int64 The fee that the swap server is charging for the swap.
prepay_amt int64 The part of the swap fee that is requested as a prepayment.
miner_fee int64 An estimate of the on-chain fee that needs to be paid to sweep the HTLC.
swap_payment_dest bytes The node pubkey where the swap payment needs to be paid to. This can be used to test connectivity before initiating the swap.
cltv_delta int32 On-chain cltv expiry delta

Messages

TermsRequest

This message has no parameters.

QuoteRequest

Parameter Type Description
amt int64 The amount to swap in satoshis.
conf_target int32 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 bool If external_htlc is true, we expect the htlc to be published by an external actor.

TermsResponse

Parameter Type Description
min_swap_amount int64 Minimum swap amount (sat)
max_swap_amount int64 Maximum swap amount (sat)

MonitorRequest

This message has no parameters.

LoopInRequest

Parameter Type Description
amt int64 Requested swap amount in sat. This does not include the swap and miner fee.
max_swap_fee int64 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.
max_miner_fee int64 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.
loop_in_channel uint64 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
external_htlc bool If external_htlc is true, we expect the htlc to be published by an external actor.

LoopOutRequest

Parameter Type Description
amt int64 Requested swap amount in sat. This does not include the swap and miner fee.
dest string Base58 encoded destination address for the swap.
max_swap_routing_fee int64 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_prepay_routing_fee int64 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 int64 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 int64 Maximum amount of the swap fee that may be charged as a prepayment.
max_miner_fee int64 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.
loop_out_channel uint64 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.
sweep_conf_target int32 The number of blocks from the on-chain HTLC's confirmation height that it should be swept within.

SwapResponse

Parameter 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

Parameter Type Description
amt int64 Requested swap amount in sat. This does not include the swap and miner fee.
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.
type SwapType Swap type
state SwapState State the swap is currently in, see State enum.
initiation_time int64 Initiation time of the swap.
last_update_time int64 Initiation time of the swap.
htlc_address string Htlc address.
cost_server int64 Swap server cost
cost_onchain int64 On-chain transaction cost
cost_offchain int64 Off-chain routing fees

QuoteResponse

Parameter Type Description
swap_fee int64 The fee that the swap server is charging for the swap.
prepay_amt int64 The part of the swap fee that is requested as a prepayment.
miner_fee int64 An estimate of the on-chain fee that needs to be paid to sweep the HTLC.
swap_payment_dest bytes The node pubkey where the swap payment needs to be paid to. This can be used to test connectivity before initiating the swap.
cltv_delta int32 On-chain cltv expiry delta

Enums

SwapState

Name Value Description
INITIATED 0 INITIATED is the initial state of a swap. At that point, the initiation call to the server has been made and the payment process has been started for the swap and prepayment invoices.
PREIMAGE_REVEALED 1 PREIMAGE_REVEALED is reached when the sweep tx publication is first attempted. From that point on, we should consider the preimage to no longer be secret and we need to do all we can to get the sweep confirmed. This state will mostly coalesce with StateHtlcConfirmed, except in the case where we wait for fees to come down before we sweep.
HTLC_PUBLISHED 2 HTLC_PUBLISHED is reached when the htlc tx has been published in a loop in swap.
SUCCESS 3 SUCCESS is the final swap state that is reached when the sweep tx has the required confirmation depth.
FAILED 4 FAILED is the final swap state for a failed swap with or without loss of the swap amount.
INVOICE_SETTLED 5 INVOICE_SETTLED is reached when the swap invoice in a loop in swap has been paid, but we are still waiting for the htlc spend to confirm.

SwapType

Name Value Description
LOOP_OUT 0 LOOP_OUT indicates an loop out swap (off-chain to on-chain)
LOOP_IN 1 LOOP_IN indicates a loop in swap (on-chain to off-chain)