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:
- 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 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) |