fix merge conflicts

This commit is contained in:
kiwiidb
2022-05-03 09:04:54 +02:00
29 changed files with 3208 additions and 90 deletions

View File

@@ -30,7 +30,18 @@ type AddInvoiceResponseBody struct {
PayReq string `json:"pay_req"`
}
// AddInvoice : Add invoice Controller
// AddInvoice godoc
// @Summary Generate a new invoice
// @Description Returns a new bolt11 invoice
// @Accept json
// @Produce json
// @Tags Invoice
// @Param invoice body AddInvoiceRequestBody True "Add Invoice"
// @Success 200 {object} AddInvoiceResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /addinvoice [post]
// @Security OAuth2Password
func (controller *AddInvoiceController) AddInvoice(c echo.Context) error {
userID := c.Get("UserID").(int64)
return AddInvoice(c, controller.svc, userID)
@@ -53,11 +64,11 @@ func AddInvoice(c echo.Context, svc *service.LndhubService, userID int64) error
if err != nil {
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}
c.Logger().Infof("Adding invoice: user_id=%v memo=%s value=%v description_hash=%s", userID, body.Memo, amount, body.DescriptionHash)
c.Logger().Infof("Adding invoice: user_id:%v memo:%s value:%v description_hash:%s", userID, body.Memo, amount, body.DescriptionHash)
invoice, err := svc.AddIncomingInvoice(c.Request().Context(), userID, amount, body.Memo, body.DescriptionHash)
if err != nil {
c.Logger().Errorf("Error creating invoice: %v", err)
c.Logger().Errorf("Error creating invoice: user_id:%v error: %v", userID, err)
sentry.CaptureException(err)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}

View File

@@ -29,7 +29,17 @@ type AuthResponseBody struct {
AccessToken string `json:"access_token"`
}
// Auth : Auth Controller
// Auth godoc
// @Summary Authenticate
// @Description Exchanges a login + password for a token
// @Accept json
// @Produce json
// @Tags Account
// @Param AuthRequestBody body AuthRequestBody false "Login and password"
// @Success 200 {object} AuthResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /auth [post]
func (controller *AuthController) Auth(c echo.Context) error {
var body AuthRequestBody
@@ -42,6 +52,20 @@ func (controller *AuthController) Auth(c echo.Context) error {
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}
if body.Login == "" || body.Password == "" {
// To support Swagger we also look in the Form data
params, err := c.FormParams()
if err != nil {
return err
}
username := params.Get("username")
password := params.Get("password")
if username != "" && password != "" {
body.Login = username
body.Password = password
}
}
accessToken, refreshToken, err := controller.svc.GenerateToken(c.Request().Context(), body.Login, body.Password, body.RefreshToken)
if err != nil {
return c.JSON(http.StatusUnauthorized, responses.BadAuthError)

View File

@@ -22,7 +22,17 @@ type BalanceResponse struct {
}
}
// Balance : Balance Controller
// Balance godoc
// @Summary Retrieve balance
// @Description Current user's balance in satoshi
// @Accept json
// @Produce json
// @Tags Account
// @Success 200 {object} BalanceResponse
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /balance [get]
// @Security OAuth2Password
func (controller *BalanceController) Balance(c echo.Context) error {
userId := c.Get("UserID").(int64)
balance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userId)

View File

@@ -21,16 +21,27 @@ func NewCheckPaymentController(svc *service.LndhubService) *CheckPaymentControll
return &CheckPaymentController{svc: svc}
}
// CheckPayment : Check Payment Controller
// CheckPayment godoc
// @Summary Check if an invoice is paid
// @Description Checks if an invoice is paid, can be incoming our outgoing
// @Accept json
// @Produce json
// @Tags Invoice
// @Param payment_hash path string true "Payment hash"
// @Success 200 {object} CheckPaymentResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /checkpayment/{payment_hash} [get]
// @Security OAuth2Password
func (controller *CheckPaymentController) CheckPayment(c echo.Context) error {
userId := c.Get("UserID").(int64)
userID := c.Get("UserID").(int64)
rHash := c.Param("payment_hash")
invoice, err := controller.svc.FindInvoiceByPaymentHash(c.Request().Context(), userId, rHash)
invoice, err := controller.svc.FindInvoiceByPaymentHash(c.Request().Context(), userID, rHash)
// Probably we did not find the invoice
if err != nil {
c.Logger().Errorf("Invalid checkpayment request payment_hash=%s", rHash)
c.Logger().Errorf("Invalid checkpayment request user_id:%v payment_hash:%s", userID, rHash)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}

View File

@@ -28,7 +28,17 @@ type CreateUserRequestBody struct {
AccountType string `json:"accounttype"`
}
// CreateUser : Create user Controller
// CreateUser godoc
// @Summary Create an account
// @Description Create a new account with a login and password
// @Accept json
// @Produce json
// @Tags Account
// @Param account body CreateUserRequestBody false "Create User"
// @Success 200 {object} CreateUserResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /create [post]
func (controller *CreateUserController) CreateUser(c echo.Context) error {
var body CreateUserRequestBody

View File

@@ -7,6 +7,63 @@ import (
"github.com/labstack/echo/v4"
)
//Copy over struct for swagger purposes
type GetInfoResponse struct {
// The version of the LND software that the node is running.
Version string `protobuf:"bytes,14,opt,name=version,proto3" json:"version,omitempty"`
// The SHA1 commit hash that the daemon is compiled with.
CommitHash string `protobuf:"bytes,20,opt,name=commit_hash,json=commitHash,proto3" json:"commit_hash,omitempty"`
// The identity pubkey of the current node.
IdentityPubkey string `protobuf:"bytes,1,opt,name=identity_pubkey,json=identityPubkey,proto3" json:"identity_pubkey,omitempty"`
// If applicable, the alias of the current node, e.g. "bob"
Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"`
// The color of the current node in hex code format
Color string `protobuf:"bytes,17,opt,name=color,proto3" json:"color,omitempty"`
// Number of pending channels
NumPendingChannels uint32 `protobuf:"varint,3,opt,name=num_pending_channels,json=numPendingChannels,proto3" json:"num_pending_channels,omitempty"`
// Number of active channels
NumActiveChannels uint32 `protobuf:"varint,4,opt,name=num_active_channels,json=numActiveChannels,proto3" json:"num_active_channels,omitempty"`
// Number of inactive channels
NumInactiveChannels uint32 `protobuf:"varint,15,opt,name=num_inactive_channels,json=numInactiveChannels,proto3" json:"num_inactive_channels,omitempty"`
// Number of peers
NumPeers uint32 `protobuf:"varint,5,opt,name=num_peers,json=numPeers,proto3" json:"num_peers,omitempty"`
// The node's current view of the height of the best block
BlockHeight uint32 `protobuf:"varint,6,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
// The node's current view of the hash of the best block
BlockHash string `protobuf:"bytes,8,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
// Timestamp of the block best known to the wallet
BestHeaderTimestamp int64 `protobuf:"varint,13,opt,name=best_header_timestamp,json=bestHeaderTimestamp,proto3" json:"best_header_timestamp,omitempty"`
// Whether the wallet's view is synced to the main chain
SyncedToChain bool `protobuf:"varint,9,opt,name=synced_to_chain,json=syncedToChain,proto3" json:"synced_to_chain,omitempty"`
// Whether we consider ourselves synced with the public channel graph.
SyncedToGraph bool `protobuf:"varint,18,opt,name=synced_to_graph,json=syncedToGraph,proto3" json:"synced_to_graph,omitempty"`
//
//Whether the current node is connected to testnet. This field is
//deprecated and the network field should be used instead
//
// Deprecated: Do not use.
Testnet bool `protobuf:"varint,10,opt,name=testnet,proto3" json:"testnet,omitempty"`
// A list of active chains the node is connected to
Chains []*Chain `protobuf:"bytes,16,rep,name=chains,proto3" json:"chains,omitempty"`
// The URIs of the current node.
Uris []string `protobuf:"bytes,12,rep,name=uris,proto3" json:"uris,omitempty"`
//
//Features that our node has advertised in our init message, node
//announcements and invoices.
Features map[uint32]*Feature `protobuf:"bytes,19,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
type Chain struct {
// The blockchain the node is on (eg bitcoin, litecoin)
Chain string `protobuf:"bytes,1,opt,name=chain,proto3" json:"chain,omitempty"`
// The network the node is on (eg regtest, testnet, mainnet)
Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
}
type Feature struct {
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
IsRequired bool `protobuf:"varint,3,opt,name=is_required,json=isRequired,proto3" json:"is_required,omitempty"`
IsKnown bool `protobuf:"varint,4,opt,name=is_known,json=isKnown,proto3" json:"is_known,omitempty"`
}
// GetInfoController : GetInfoController struct
type GetInfoController struct {
svc *service.LndhubService
@@ -16,10 +73,19 @@ func NewGetInfoController(svc *service.LndhubService) *GetInfoController {
return &GetInfoController{svc: svc}
}
// GetInfo : GetInfo handler
// GetInfo godoc
// @Summary Get info about the Lightning node
// @Description Returns info about the backend node powering this LNDhub instance
// @Accept json
// @Produce json
// @Tags Info
// @Success 200 {object} GetInfoResponse
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /getinfo [get]
// @Security OAuth2Password
func (controller *GetInfoController) GetInfo(c echo.Context) error {
// TODO: add some caching for this GetInfo call. No need to always hit the node
info, err := controller.svc.GetInfo(c.Request().Context())
if err != nil {
return err

View File

@@ -42,7 +42,17 @@ type IncomingInvoice struct {
IsPaid bool `json:"ispaid"`
}
// GetTXS : Get TXS Controller
// GetTXS godoc
// @Summary Retrieve outgoing payments
// @Description Returns a list of outgoing payments for a user
// @Accept json
// @Produce json
// @Tags Account
// @Success 200 {object} []OutgoingInvoice
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /gettxs [get]
// @Security OAuth2Password
func (controller *GetTXSController) GetTXS(c echo.Context) error {
userId := c.Get("UserID").(int64)
@@ -68,6 +78,17 @@ func (controller *GetTXSController) GetTXS(c echo.Context) error {
return c.JSON(http.StatusOK, &response)
}
// GetUserInvoices godoc
// @Summary Retrieve incoming invoices
// @Description Returns a list of incoming invoices for a user
// @Accept json
// @Produce json
// @Tags Account
// @Success 200 {object} []IncomingInvoice
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /getuserinvoices [get]
// @Security OAuth2Password
func (controller *GetTXSController) GetUserInvoices(c echo.Context) error {
userId := c.Get("UserID").(int64)

View File

@@ -17,7 +17,18 @@ func NewInvoiceController(svc *service.LndhubService) *InvoiceController {
return &InvoiceController{svc: svc}
}
// Invoice : Invoice Controller
// Invoice godoc
// @Summary Generate a new invoice
// @Description Returns a new bolt11 invoice for a user with given login, without an Authorization Header
// @Accept json
// @Produce json
// @Tags Invoice
// @Param user_login path string true "User Login"
// @Param invoice body AddInvoiceRequestBody True "Add Invoice"
// @Success 200 {object} AddInvoiceResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /invoice/{user_login} [post]
func (controller *InvoiceController) Invoice(c echo.Context) error {
user, err := controller.svc.FindUserByLogin(c.Request().Context(), c.Param("user_login"))
if err != nil {

View File

@@ -27,7 +27,20 @@ func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamContro
return &InvoiceStreamController{svc: svc}
}
// Stream invoices streams incoming payments to the client
// StreamInvoices godoc
// @Summary Websocket for incoming payments
// @Description Websocket: won't work with Swagger web UI. Returns a stream of settled incoming payments.
// @Description A keep-alive message is sent on startup and every 30s.
// @Accept json
// @Produce json
// @Tags Invoice
// @Param token query string true "Auth token, retrieved from /auth endpoint"
// @Param since_payment_hash query string false "Payment hash of earliest invoice. If specified, missing updates starting from this payment will be sent."
// @Success 200 {object} []InvoiceEventWrapper
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /invoices/stream [get]
// @Security OAuth2Password
func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error {
userId, err := tokens.ParseToken(controller.svc.Config.JWTSecret, (c.QueryParam("token")), false)
if err != nil {
@@ -41,8 +54,11 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error
return err
}
//start subscription
subId := controller.svc.InvoicePubSub.Subscribe(strconv.FormatInt(userId, 10), invoiceChan)
subId, err := controller.svc.InvoicePubSub.Subscribe(strconv.FormatInt(userId, 10), invoiceChan)
if err != nil {
controller.svc.Logger.Error(err)
return err
}
//start with keepalive message
err = ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"})
if err != nil {

View File

@@ -41,7 +41,18 @@ type KeySendResponseBody struct {
PaymentRoute *service.Route `json:"payment_route,omitempty"`
}
// KeySend : Key send Controller
//// PayInvoice godoc
// @Summary Make a keysend payment
// @Description Pay a node without an invoice using it's public key
// @Accept json
// @Produce json
// @Tags Payment
// @Param KeySendRequestBody body KeySendRequestBody True "Invoice to pay"
// @Success 200 {object} KeySendResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /keysend [post]
// @Security OAuth2Password
func (controller *KeySendController) KeySend(c echo.Context) error {
userID := c.Get("UserID").(int64)
reqBody := KeySendRequestBody{}
@@ -75,7 +86,7 @@ func (controller *KeySendController) KeySend(c echo.Context) error {
}
if currentBalance < invoice.Amount {
c.Logger().Errorf("User does not have enough balance invoice_id=%v user_id=%v balance=%v amount=%v", invoice.ID, userID, currentBalance, invoice.Amount)
c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount)
return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError)
}
@@ -89,7 +100,7 @@ func (controller *KeySendController) KeySend(c echo.Context) error {
}
sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice)
if err != nil {
c.Logger().Errorf("Payment failed: %v", err)
c.Logger().Errorf("Payment failed: user_id:%v error: %v", userID, err)
sentry.CaptureException(err)
return c.JSON(http.StatusBadRequest, echo.Map{
"error": true,

View File

@@ -38,17 +38,28 @@ type PayInvoiceResponseBody struct {
PaymentRoute *service.Route `json:"payment_route,omitempty"`
}
// PayInvoice : Pay invoice Controller
// PayInvoice godoc
// @Summary Pay an invoice
// @Description Pay a bolt11 invoice
// @Accept json
// @Produce json
// @Tags Payment
// @Param PayInvoiceRequest body PayInvoiceRequestBody True "Invoice to pay"
// @Success 200 {object} PayInvoiceResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /payinvoice [post]
// @Security OAuth2Password
func (controller *PayInvoiceController) PayInvoice(c echo.Context) error {
userID := c.Get("UserID").(int64)
reqBody := PayInvoiceRequestBody{}
if err := c.Bind(&reqBody); err != nil {
c.Logger().Errorf("Failed to load payinvoice request body: %v", err)
c.Logger().Errorf("Failed to load payinvoice request body: user_id:%v error: %v", userID, err)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}
if err := c.Validate(&reqBody); err != nil {
c.Logger().Errorf("Invalid payinvoice request body: %v", err)
c.Logger().Errorf("Invalid payinvoice request body user_id:%v error: %v", userID, err)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}
@@ -56,26 +67,22 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error {
paymentRequest = strings.ToLower(paymentRequest)
decodedPaymentRequest, err := controller.svc.DecodePaymentRequest(c.Request().Context(), paymentRequest)
if err != nil {
c.Logger().Errorf("Invalid payment request: %v", err)
c.Logger().Errorf("Invalid payment request user_id:%v error: %v", userID, err)
sentry.CaptureException(err)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}
// TODO: zero amount invoices
/*
_, err = controller.svc.ParseInt(reqBody.Amount)
if err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{
"error": true,
"code": 8,
"message": "Bad arguments",
})
}
*/
lnPayReq := &lnd.LNPayReq{
PayReq: decodedPaymentRequest,
Keysend: false,
}
if decodedPaymentRequest.NumSatoshis == 0 {
amt, err := controller.svc.ParseInt(reqBody.Amount)
if err != nil || amt <= 0 {
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}
lnPayReq.PayReq.NumSatoshis = amt
}
invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, paymentRequest, lnPayReq)
if err != nil {
@@ -88,14 +95,14 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error {
}
if currentBalance < invoice.Amount {
c.Logger().Errorf("User does not have enough balance invoice_id=%v user_id=%v balance=%v amount=%v", invoice.ID, userID, currentBalance, invoice.Amount)
c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount)
return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError)
}
sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice)
if err != nil {
c.Logger().Errorf("Payment failed: %v", err)
c.Logger().Errorf("Payment failed invoice_id:%v user_id:%v error: %v", invoice.ID, userID, err)
sentry.CaptureException(err)
return c.JSON(http.StatusBadRequest, echo.Map{
"error": true,

1037
docs/docs.go Normal file

File diff suppressed because it is too large Load Diff

1017
docs/swagger.json Normal file

File diff suppressed because it is too large Load Diff

674
docs/swagger.yaml Normal file
View File

@@ -0,0 +1,674 @@
basePath: /
definitions:
controllers.AddInvoiceRequestBody:
properties:
amt:
description: amount in Satoshi
description_hash:
type: string
memo:
type: string
type: object
controllers.AddInvoiceResponseBody:
properties:
pay_req:
type: string
payment_request:
type: string
r_hash:
type: string
type: object
controllers.AuthRequestBody:
properties:
login:
type: string
password:
type: string
refresh_token:
type: string
type: object
controllers.AuthResponseBody:
properties:
access_token:
type: string
refresh_token:
type: string
type: object
controllers.BalanceResponse:
properties:
btc:
properties:
availableBalance:
type: integer
type: object
type: object
controllers.Chain:
properties:
chain:
description: The blockchain the node is on (eg bitcoin, litecoin)
type: string
network:
description: The network the node is on (eg regtest, testnet, mainnet)
type: string
type: object
controllers.CheckPaymentResponseBody:
properties:
paid:
type: boolean
type: object
controllers.CreateUserRequestBody:
properties:
accounttype:
type: string
login:
type: string
partnerid:
type: string
password:
type: string
type: object
controllers.CreateUserResponseBody:
properties:
login:
type: string
password:
type: string
type: object
controllers.Feature:
properties:
is_known:
type: boolean
is_required:
type: boolean
name:
type: string
type: object
controllers.GetInfoResponse:
properties:
alias:
description: If applicable, the alias of the current node, e.g. "bob"
type: string
best_header_timestamp:
description: Timestamp of the block best known to the wallet
type: integer
block_hash:
description: The node's current view of the hash of the best block
type: string
block_height:
description: The node's current view of the height of the best block
type: integer
chains:
description: A list of active chains the node is connected to
items:
$ref: '#/definitions/controllers.Chain'
type: array
color:
description: The color of the current node in hex code format
type: string
commit_hash:
description: The SHA1 commit hash that the daemon is compiled with.
type: string
features:
additionalProperties:
$ref: '#/definitions/controllers.Feature'
description: |-
Features that our node has advertised in our init message, node
announcements and invoices.
type: object
identity_pubkey:
description: The identity pubkey of the current node.
type: string
num_active_channels:
description: Number of active channels
type: integer
num_inactive_channels:
description: Number of inactive channels
type: integer
num_peers:
description: Number of peers
type: integer
num_pending_channels:
description: Number of pending channels
type: integer
synced_to_chain:
description: Whether the wallet's view is synced to the main chain
type: boolean
synced_to_graph:
description: Whether we consider ourselves synced with the public channel
graph.
type: boolean
testnet:
description: |-
Whether the current node is connected to testnet. This field is
deprecated and the network field should be used instead
Deprecated: Do not use.
type: boolean
uris:
description: The URIs of the current node.
items:
type: string
type: array
version:
description: The version of the LND software that the node is running.
type: string
type: object
controllers.IncomingInvoice:
properties:
amt:
type: integer
description:
type: string
expire_time:
type: integer
ispaid:
type: boolean
pay_req:
type: string
payment_hash: {}
payment_request:
type: string
r_hash: {}
timestamp:
type: integer
type:
type: string
type: object
controllers.InvoiceEventWrapper:
properties:
invoice:
$ref: '#/definitions/controllers.IncomingInvoice'
type:
type: string
type: object
controllers.KeySendRequestBody:
properties:
amount:
type: integer
customRecords:
additionalProperties:
type: string
type: object
destination:
type: string
memo:
type: string
required:
- amount
- destination
type: object
controllers.KeySendResponseBody:
properties:
description:
type: string
description_hash:
type: string
destination:
type: string
num_satoshis:
type: integer
payment_error:
type: string
payment_hash:
$ref: '#/definitions/lib.JavaScriptBuffer'
payment_preimage:
$ref: '#/definitions/lib.JavaScriptBuffer'
payment_route:
$ref: '#/definitions/service.Route'
type: object
controllers.OutgoingInvoice:
properties:
fee:
type: integer
memo:
type: string
payment_hash: {}
payment_preimage:
type: string
r_hash: {}
timestamp:
type: integer
type:
type: string
value:
type: integer
type: object
controllers.PayInvoiceRequestBody:
properties:
amount: {}
invoice:
type: string
required:
- invoice
type: object
controllers.PayInvoiceResponseBody:
properties:
description:
type: string
description_hash:
type: string
num_satoshis:
type: integer
pay_req:
type: string
payment_error:
type: string
payment_hash:
$ref: '#/definitions/lib.JavaScriptBuffer'
payment_preimage:
$ref: '#/definitions/lib.JavaScriptBuffer'
payment_request:
type: string
payment_route:
$ref: '#/definitions/service.Route'
type: object
lib.JavaScriptBuffer:
properties:
data:
items:
type: integer
type: array
type: object
responses.ErrorResponse:
properties:
code:
type: integer
error:
type: boolean
message:
type: string
type: object
service.Route:
properties:
total_amt:
type: integer
total_fees:
type: integer
type: object
info:
contact:
email: hello@getalby.com
name: Alby
url: https://getalby.com
description: Accounting wrapper for the Lightning Network providing separate accounts
for end-users.
license:
name: GNU GPL
url: https://www.gnu.org/licenses/gpl-3.0.en.html
title: LNDhub.go
version: 0.6.1
paths:
/addinvoice:
post:
consumes:
- application/json
description: Returns a new bolt11 invoice
parameters:
- description: Add Invoice
in: body
name: invoice
required: true
schema:
$ref: '#/definitions/controllers.AddInvoiceRequestBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.AddInvoiceResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Generate a new invoice
tags:
- Invoice
/auth:
post:
consumes:
- application/json
description: Exchanges a login + password for a token
parameters:
- description: Login and password
in: body
name: AuthRequestBody
schema:
$ref: '#/definitions/controllers.AuthRequestBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.AuthResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
summary: Authenticate
tags:
- Account
/balance:
get:
consumes:
- application/json
description: Current user's balance in satoshi
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.BalanceResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Retrieve balance
tags:
- Account
/checkpayment/{payment_hash}:
get:
consumes:
- application/json
description: Checks if an invoice is paid, can be incoming our outgoing
parameters:
- description: Payment hash
in: path
name: payment_hash
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.CheckPaymentResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Check if an invoice is paid
tags:
- Invoice
/create:
post:
consumes:
- application/json
description: Create a new account with a login and password
parameters:
- description: Create User
in: body
name: account
schema:
$ref: '#/definitions/controllers.CreateUserRequestBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.CreateUserResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
summary: Create an account
tags:
- Account
/getinfo:
get:
consumes:
- application/json
description: Returns info about the backend node powering this LNDhub instance
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.GetInfoResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Get info about the Lightning node
tags:
- Info
/gettxs:
get:
consumes:
- application/json
description: Returns a list of outgoing payments for a user
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/controllers.OutgoingInvoice'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Retrieve outgoing payments
tags:
- Account
/getuserinvoices:
get:
consumes:
- application/json
description: Returns a list of incoming invoices for a user
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/controllers.IncomingInvoice'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Retrieve incoming invoices
tags:
- Account
/invoice/{user_login}:
post:
consumes:
- application/json
description: Returns a new bolt11 invoice for a user with given login, without
an Authorization Header
parameters:
- description: User Login
in: path
name: user_login
required: true
type: string
- description: Add Invoice
in: body
name: invoice
required: true
schema:
$ref: '#/definitions/controllers.AddInvoiceRequestBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.AddInvoiceResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
summary: Generate a new invoice
tags:
- Invoice
/invoices/stream:
get:
consumes:
- application/json
description: |-
Websocket: won't work with Swagger web UI. Returns a stream of settled incoming payments.
A keep-alive message is sent on startup and every 30s.
parameters:
- description: Auth token, retrieved from /auth endpoint
in: query
name: token
required: true
type: string
- description: Payment hash of earliest invoice. If specified, missing updates
starting from this payment will be sent.
in: query
name: since_payment_hash
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/controllers.InvoiceEventWrapper'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Websocket for incoming payments
tags:
- Invoice
/keysend:
post:
consumes:
- application/json
description: Pay a node without an invoice using it's public key
parameters:
- description: Invoice to pay
in: body
name: KeySendRequestBody
required: true
schema:
$ref: '#/definitions/controllers.KeySendRequestBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.KeySendResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Make a keysend payment
tags:
- Payment
/payinvoice:
post:
consumes:
- application/json
description: Pay a bolt11 invoice
parameters:
- description: Invoice to pay
in: body
name: PayInvoiceRequest
required: true
schema:
$ref: '#/definitions/controllers.PayInvoiceRequestBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.PayInvoiceResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Pay an invoice
tags:
- Payment
schemes:
- http
- https
securityDefinitions:
OAuth2Password:
flow: password
tokenUrl: /auth
type: oauth2
swagger: "2.0"

14
go.mod
View File

@@ -10,7 +10,7 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/joho/godotenv v1.4.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/labstack/echo/v4 v4.6.1
github.com/labstack/echo/v4 v4.7.2
github.com/labstack/gommon v0.3.1
github.com/lightningnetwork/lnd v0.14.1-beta
github.com/stretchr/testify v1.7.0
@@ -21,18 +21,24 @@ require (
github.com/uptrace/bun/driver/sqliteshim v1.0.21
github.com/uptrace/bun/extra/bundebug v1.0.21
github.com/ziflex/lecho/v3 v3.1.0
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
google.golang.org/grpc v1.43.0
gopkg.in/macaroon.v2 v2.1.0
)
require (
github.com/BurntSushi/toml v1.1.0 // indirect
github.com/SporkHubr/echo-http-cache v0.0.0-20200706100054-1d7ae9f38029
github.com/go-openapi/spec v0.20.5 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/gorilla/websocket v1.5.0
github.com/labstack/echo-contrib v0.12.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
github.com/swaggo/echo-swagger v1.3.0
github.com/swaggo/swag v1.8.1
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/tools v0.1.10 // indirect
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 // indirect
)

84
go.sum
View File

@@ -35,8 +35,9 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
@@ -44,11 +45,15 @@ github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5Db
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
@@ -60,6 +65,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -72,6 +79,7 @@ github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -225,8 +233,8 @@ github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNy
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -252,7 +260,20 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE=
github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o=
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
@@ -468,6 +489,8 @@ github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
@@ -541,10 +564,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo-contrib v0.12.0 h1:NPr1ez+XUa5s/4LujEon+32Bxg5DO6EKSW/va06pmLc=
github.com/labstack/echo-contrib v0.12.0/go.mod h1:kR62TbwsBgmpV2HVab5iQRsQtLuhPyGqCBee88XRc4M=
github.com/labstack/echo/v4 v4.1.14/go.mod h1:Q5KZ1vD3V5FEzjM79hjwVrC3ABr7F5IdM23bXQMRDGg=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M=
github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k=
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
@@ -587,9 +612,15 @@ github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1s
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -602,6 +633,7 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -665,8 +697,15 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4=
@@ -696,7 +735,6 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
@@ -740,8 +778,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
@@ -773,6 +811,13 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/swaggo/echo-swagger v1.3.0 h1:xxL/4jbCY4Z3udUvqOas+IpTMKbxrKdEKwtS7He0Qhg=
github.com/swaggo/echo-swagger v1.3.0/go.mod h1:snY6MlGK+pQAfJNEfX5qaOzt/QuM/WINVxGgQaZVJgg=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA=
@@ -902,8 +947,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
@@ -947,8 +992,10 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -987,8 +1034,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1033,6 +1081,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -1041,8 +1090,9 @@ golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1119,6 +1169,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1136,11 +1187,13 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1221,8 +1274,10 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1371,6 +1426,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -109,7 +109,9 @@ func (suite *CheckPaymentTestSuite) TestCheckPaymentProperIsPaidResponse() {
assert.False(suite.T(), checkPaymentResponse.IsPaid)
// pay external from user
payResponse := suite.createPayInvoiceReq(invoice.PaymentRequest, suite.userToken)
payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{
Invoice: invoice.PayReq,
}, suite.userToken)
assert.NotEmpty(suite.T(), payResponse.PaymentPreimage)
// check payment is paid

View File

@@ -102,7 +102,9 @@ func (suite *GetTxTestSuite) TestGetOutgoingInvoices() {
// create invoice
invoice = suite.createAddInvoiceReq(500, "integration test internal payment alice", suite.userToken)
// pay invoice, this will create outgoing invoice and settle it
suite.createPayInvoiceReq(invoice.PayReq, suite.userToken)
suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{
Invoice: invoice.PayReq,
}, suite.userToken)
// check invoices again
req = httptest.NewRequest(http.MethodGet, "/gettxs", nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.userToken))

View File

@@ -110,6 +110,45 @@ func (suite *IncomingPaymentTestSuite) TestIncomingPayment() {
assert.Equal(suite.T(), int64(fundingSatAmt), balance.BTC.AvailableBalance)
}
func (suite *IncomingPaymentTestSuite) TestIncomingPaymentZeroAmt() {
var buf bytes.Buffer
req := httptest.NewRequest(http.MethodGet, "/balance", &buf)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.userToken))
rec := httptest.NewRecorder()
suite.echo.Use(tokens.Middleware([]byte(suite.service.Config.JWTSecret)))
suite.echo.GET("/balance", controllers.NewBalanceController(suite.service).Balance)
suite.echo.POST("/addinvoice", controllers.NewAddInvoiceController(suite.service).AddInvoice)
suite.echo.ServeHTTP(rec, req)
//lookup balance before
balance := &ExpectedBalanceResponse{}
assert.Equal(suite.T(), http.StatusOK, rec.Code)
assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(&balance))
initialBalance := balance.BTC.AvailableBalance
fundingSatAmt := 0
sendSatAmt := 10
invoiceResponse := suite.createAddInvoiceReq(fundingSatAmt, "integration test IncomingPaymentTestSuite", suite.userToken)
//try to pay invoice with external node
// Prepare the LNRPC call
sendPaymentRequest := lnrpc.SendRequest{
PaymentRequest: invoiceResponse.PayReq,
Amt: int64(sendSatAmt),
FeeLimit: nil,
}
_, err := suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest)
assert.NoError(suite.T(), err)
//wait a bit for the callback event to hit
time.Sleep(100 * time.Millisecond)
//check balance again
req = httptest.NewRequest(http.MethodGet, "/balance", &buf)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.userToken))
suite.echo.ServeHTTP(rec, req)
balance = &ExpectedBalanceResponse{}
assert.Equal(suite.T(), http.StatusOK, rec.Code)
assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(&balance))
//assert the payment value was added to the user's account
assert.Equal(suite.T(), initialBalance+int64(sendSatAmt), balance.BTC.AvailableBalance)
}
func TestIncomingPaymentTestSuite(t *testing.T) {
suite.Run(t, new(IncomingPaymentTestSuite))

View File

@@ -103,7 +103,9 @@ func (suite *PaymentTestSuite) TestInternalPayment() {
//create invoice for bob
bobInvoice := suite.createAddInvoiceReq(bobSatRequested, "integration test internal payment bob", suite.bobToken)
//pay bob from alice
payResponse := suite.createPayInvoiceReq(bobInvoice.PayReq, suite.aliceToken)
payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{
Invoice: bobInvoice.PayReq,
}, suite.aliceToken)
assert.NotEmpty(suite.T(), payResponse.PaymentPreimage)
aliceId := getUserIdFromToken(suite.aliceToken)
@@ -152,7 +154,9 @@ func (suite *PaymentTestSuite) TestInternalPaymentFail() {
//create invoice for bob
bobInvoice := suite.createAddInvoiceReq(bobSatRequested, "integration test internal payment bob", suite.bobToken)
//pay bob from alice
payResponse := suite.createPayInvoiceReq(bobInvoice.PayReq, suite.aliceToken)
payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{
Invoice: bobInvoice.PayReq,
}, suite.aliceToken)
assert.NotEmpty(suite.T(), payResponse.PaymentPreimage)
//try to pay same invoice again for make it fail
_ = suite.createPayInvoiceReqError(bobInvoice.PayReq, suite.aliceToken)

View File

@@ -38,7 +38,9 @@ func (suite *PaymentTestSuite) TestOutGoingPayment() {
invoice, err := suite.fundingClient.AddInvoice(context.Background(), &externalInvoice)
assert.NoError(suite.T(), err)
//pay external from alice
payResponse := suite.createPayInvoiceReq(invoice.PaymentRequest, suite.aliceToken)
payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{
Invoice: invoice.PaymentRequest,
}, suite.aliceToken)
assert.NotEmpty(suite.T(), payResponse.PaymentPreimage)
// check that balance was reduced
@@ -125,7 +127,9 @@ func (suite *PaymentTestSuite) TestOutGoingPaymentWithNegativeBalance() {
invoice, err := suite.fundingClient.AddInvoice(context.Background(), &externalInvoice)
assert.NoError(suite.T(), err)
//pay external from alice
payResponse := suite.createPayInvoiceReq(invoice.PaymentRequest, suite.aliceToken)
payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{
Invoice: invoice.PaymentRequest,
}, suite.aliceToken)
assert.NotEmpty(suite.T(), payResponse.PaymentPreimage)
// check that balance was reduced
@@ -176,3 +180,34 @@ func (suite *PaymentTestSuite) TestOutGoingPaymentWithNegativeBalance() {
// make sure fee entry parent id is previous entry
assert.Equal(suite.T(), transactonEntries[1].ID, transactonEntries[2].ParentID)
}
func (suite *PaymentTestSuite) TestZeroAmountInvoice() {
aliceFundingSats := 1000
amtToPay := 1000
//fund alice account
invoiceResponse := suite.createAddInvoiceReq(aliceFundingSats, "integration test zero amount payment alice", suite.aliceToken)
sendPaymentRequest := lnrpc.SendRequest{
PaymentRequest: invoiceResponse.PayReq,
FeeLimit: nil,
}
_, err := suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest)
assert.NoError(suite.T(), err)
//wait a bit for the callback event to hit
time.Sleep(100 * time.Millisecond)
//create external invoice
externalInvoice := lnrpc.Invoice{
Memo: "integration tests: zero amount pay from alice",
Value: 0,
}
invoice, err := suite.fundingClient.AddInvoice(context.Background(), &externalInvoice)
assert.NoError(suite.T(), err)
//pay external from alice
payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{
Invoice: invoice.PaymentRequest,
Amount: amtToPay,
}, suite.aliceToken)
assert.NotEmpty(suite.T(), payResponse.PaymentPreimage)
assert.Equal(suite.T(), int64(amtToPay), payResponse.Amount)
}

View File

@@ -229,12 +229,10 @@ func (suite *TestSuite) createKeySendReqError(amount int64, memo, destination, t
return checkErrResponse(suite, rec)
}
func (suite *TestSuite) createPayInvoiceReq(payReq string, token string) *ExpectedPayInvoiceResponseBody {
func (suite *TestSuite) createPayInvoiceReq(payReq *ExpectedPayInvoiceRequestBody, token string) *ExpectedPayInvoiceResponseBody {
rec := httptest.NewRecorder()
var buf bytes.Buffer
assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(&ExpectedPayInvoiceRequestBody{
Invoice: payReq,
}))
assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(payReq))
req := httptest.NewRequest(http.MethodPost, "/payinvoice", &buf)
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))

View File

@@ -11,6 +11,7 @@ type Config struct {
LNDMacaroonHex string `envconfig:"LND_MACAROON_HEX" required:"true"`
LNDCertHex string `envconfig:"LND_CERT_HEX"`
CustomName string `envconfig:"CUSTOM_NAME"`
Host string `envconfig:"HOST" default:"localhost:3000"`
Port int `envconfig:"PORT" default:"3000"`
DefaultRateLimit int `envconfig:"DEFAULT_RATE_LIMIT" default:"10"`
StrictRateLimit int `envconfig:"STRICT_RATE_LIMIT" default:"10"`

View File

@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"math"
"math/rand"
"strconv"
"time"
@@ -144,7 +143,10 @@ func createLnRpcSendRequest(invoice *models.Invoice) (*lnrpc.SendRequest, error)
}, nil
}
preImage := makePreimageHex()
preImage, err := makePreimageHex()
if err != nil {
return nil, err
}
pHash := sha256.New()
pHash.Write(preImage)
// Prepare the LNRPC call
@@ -340,7 +342,10 @@ func (svc *LndhubService) AddOutgoingInvoice(ctx context.Context, userID int64,
}
func (svc *LndhubService) AddIncomingInvoice(ctx context.Context, userID int64, amount int64, memo, descriptionHashStr string) (*models.Invoice, error) {
preimage := makePreimageHex()
preimage, err := makePreimageHex()
if err != nil {
return nil, err
}
expiry := time.Hour * 24 // invoice expires in 24h
// Initialize new DB invoice
invoice := models.Invoice{
@@ -354,7 +359,7 @@ func (svc *LndhubService) AddIncomingInvoice(ctx context.Context, userID int64,
}
// Save invoice - we save the invoice early to have a record in case the LN call fails
_, err := svc.DB.NewInsert().Model(&invoice).Exec(ctx)
_, err = svc.DB.NewInsert().Model(&invoice).Exec(ctx)
if err != nil {
return nil, err
}
@@ -399,10 +404,6 @@ func (svc *LndhubService) DecodePaymentRequest(ctx context.Context, bolt11 strin
const hexBytes = random.Hex
func makePreimageHex() []byte {
b := make([]byte, 32)
for i := range b {
b[i] = hexBytes[rand.Intn(len(hexBytes))]
}
return b
func makePreimageHex() ([]byte, error) {
return randBytesFromStr(32, hexBytes)
}

View File

@@ -82,7 +82,11 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice *
InvoiceID: invoice.ID,
CreditAccountID: creditAccount.ID,
DebitAccountID: debitAccount.ID,
Amount: invoice.Amount,
Amount: rawInvoice.AmtPaidSat,
}
if rawInvoice.AmtPaidSat != invoice.Amount {
svc.Logger.Infof("Incoming invoice amount mismatch. user_id:%v invoice_id:%v, amt:%d, amt_paid:%d.", invoice.UserID, invoice.ID, invoice.Amount, rawInvoice.AmtPaidSat)
}
// Save the transaction entry

View File

@@ -17,16 +17,20 @@ func NewPubsub() *Pubsub {
return ps
}
func (ps *Pubsub) Subscribe(topic string, ch chan models.Invoice) (subId string) {
func (ps *Pubsub) Subscribe(topic string, ch chan models.Invoice) (subId string, err error) {
ps.mu.Lock()
defer ps.mu.Unlock()
if ps.subs[topic] == nil {
ps.subs[topic] = make(map[string]chan models.Invoice)
}
//re-use preimage code for a uuid
subId = string(makePreimageHex())
preImageHex, err := makePreimageHex()
if err != nil {
return "", err
}
subId = string(preImageHex)
ps.subs[topic][subId] = ch
return subId
return subId, nil
}
func (ps *Pubsub) Unsubscribe(id string, topic string) {

View File

@@ -3,7 +3,6 @@ package service
import (
"context"
"database/sql"
"math/rand"
"github.com/getAlby/lndhub.go/common"
"github.com/getAlby/lndhub.go/db/models"
@@ -18,11 +17,19 @@ func (svc *LndhubService) CreateUser(ctx context.Context, login string, password
// generate user login/password if not provided
user.Login = login
if login == "" {
user.Login = randStringBytes(20)
randLoginBytes, err := randBytesFromStr(20, alphaNumBytes)
if err != nil {
return nil, err
}
user.Login = string(randLoginBytes)
}
if password == "" {
password = randStringBytes(20)
randPasswordBytes, err := randBytesFromStr(20, alphaNumBytes)
if err != nil {
return nil, err
}
password = string(randPasswordBytes)
}
// we only store the hashed password but return the initial plain text password in the HTTP response
@@ -112,11 +119,3 @@ func (svc *LndhubService) InvoicesFor(ctx context.Context, userId int64, invoice
}
return invoices, nil
}
func randStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = alphaNumBytes[rand.Intn(len(alphaNumBytes))]
}
return string(b)
}

19
lib/service/util.go Normal file
View File

@@ -0,0 +1,19 @@
package service
import (
"crypto/rand"
"math/big"
)
func randBytesFromStr(length int, from string) ([]byte, error) {
b := make([]byte, length)
fromLenBigInt := big.NewInt(int64(len(from)))
for i := range b {
r, err := rand.Int(rand.Reader, fromLenBigInt)
if err != nil {
return nil, err
}
b[i] = from[r.Int64()]
}
return b, nil
}

22
main.go
View File

@@ -15,6 +15,7 @@ import (
"github.com/getAlby/lndhub.go/controllers"
"github.com/getAlby/lndhub.go/db"
"github.com/getAlby/lndhub.go/db/migrations"
"github.com/getAlby/lndhub.go/docs"
"github.com/getAlby/lndhub.go/lib"
"github.com/getAlby/lndhub.go/lib/responses"
"github.com/getAlby/lndhub.go/lib/service"
@@ -29,6 +30,7 @@ import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/lightningnetwork/lnd/lnrpc"
echoSwagger "github.com/swaggo/echo-swagger"
"github.com/uptrace/bun/migrate"
"github.com/ziflex/lecho/v3"
"golang.org/x/time/rate"
@@ -40,6 +42,22 @@ var indexHtml string
//go:embed static/*
var staticContent embed.FS
// @title LNDhub.go
// @version 0.6.1
// @description Accounting wrapper for the Lightning Network providing separate accounts for end-users.
// @contact.name Alby
// @contact.url https://getalby.com
// @contact.email hello@getalby.com
// @license.name GNU GPL
// @license.url https://www.gnu.org/licenses/gpl-3.0.en.html
// @BasePath /
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl /auth
// @schemes https http
func main() {
c := &service.Config{}
@@ -165,6 +183,10 @@ func main() {
//Authentication should be done through the query param because this is a websocket
e.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices)
//Swagger API spec
docs.SwaggerInfo.Host = c.Host
e.GET("/swagger/*", echoSwagger.WrapHandler)
// Subscribe to LND invoice updates in the background
go svc.InvoiceUpdateSubscription(context.Background())