add multikeysend endpoint

This commit is contained in:
kiwiidb
2022-12-05 11:51:46 +01:00
parent d869cf61d2
commit e97bce1855
5 changed files with 315 additions and 18 deletions

View File

@@ -28,6 +28,18 @@ type KeySendRequestBody struct {
CustomRecords map[string]string `json:"customRecords" validate:"omitempty"` CustomRecords map[string]string `json:"customRecords" validate:"omitempty"`
} }
type MultiKeySendRequestBody struct {
Keysends []KeySendRequestBody `json:"keysends"`
}
type MultiKeySendResponseBody struct {
Keysends []KeySendResult `json:"keysends"`
}
type KeySendResult struct {
Keysend KeySendResponseBody `json:",omitempty"`
Error responses.ErrorResponse `json:",omitempty"`
}
type KeySendResponseBody struct { type KeySendResponseBody struct {
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
Fee int64 `json:"fee"` Fee int64 `json:"fee"`
@@ -38,7 +50,7 @@ type KeySendResponseBody struct {
PaymentHash string `json:"payment_hash,omitempty"` PaymentHash string `json:"payment_hash,omitempty"`
} }
//// KeySend godoc // // KeySend godoc
// @Summary Make a keysend payment // @Summary Make a keysend payment
// @Description Pay a node without an invoice using it's public key // @Description Pay a node without an invoice using it's public key
// @Accept json // @Accept json
@@ -57,10 +69,71 @@ func (controller *KeySendController) KeySend(c echo.Context) error {
c.Logger().Errorf("Failed to load keysend request body: %v", err) c.Logger().Errorf("Failed to load keysend request body: %v", err)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
} }
_, err := controller.SingleKeySend(c, &reqBody, userID)
return err
}
// // MultiKeySend godoc
// @Summary Make multiple keysend payments
// @Description Pay multiple nodes without an invoice using their public key
// @Accept json
// @Produce json
// @Tags Payment
// @Param MultiKeySendRequestBody body MultiKeySendRequestBody True "Invoice to pay"
// @Success 200 {object} MultiKeySendResponseBody
// @Failure 400 {object} responses.ErrorResponse
// @Failure 500 {object} responses.ErrorResponse
// @Router /v2/payments/keysend/multi [post]
// @Security OAuth2Password
func (controller *KeySendController) MultiKeySend(c echo.Context) error {
// TODO
// - V create request and response structs
// - V extract shared code
// - V call shared code in loop
// - V fill and return response
// - test
// - integration tests
// - PR
// - deploy
// - Update API
// - mail sam
userID := c.Get("UserID").(int64)
reqBody := MultiKeySendRequestBody{}
if err := c.Bind(&reqBody); err != nil {
c.Logger().Errorf("Failed to load keysend request body: %v", err)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
}
result := &MultiKeySendResponseBody{
Keysends: []KeySendResult{},
}
for _, keysend := range reqBody.Keysends {
keysend := keysend
res, err := controller.SingleKeySend(c, &keysend, userID)
if err != nil {
controller.svc.Logger.Errorf("Error making keysend split payment %v %s", keysend, err.Error())
result.Keysends = append(result.Keysends, KeySendResult{
Keysend: KeySendResponseBody{
Destination: keysend.Destination,
},
Error: responses.ErrorResponse{Error: true, Code: 500, Message: err.Error()},
})
}
result.Keysends = append(result.Keysends, KeySendResult{
Keysend: *res,
Error: responses.ErrorResponse{
Error: false,
Code: 200,
},
})
}
return c.JSON(http.StatusOK, result)
}
func (controller *KeySendController) SingleKeySend(c echo.Context, reqBody *KeySendRequestBody, userID int64) (result *KeySendResponseBody, err error) {
if err := c.Validate(&reqBody); err != nil { if err := c.Validate(&reqBody); err != nil {
c.Logger().Errorf("Invalid keysend request body: %v", err) c.Logger().Errorf("Invalid keysend request body: %v", err)
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) return nil, c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
} }
lnPayReq := &lnd.LNPayReq{ lnPayReq := &lnd.LNPayReq{
@@ -74,12 +147,12 @@ func (controller *KeySendController) KeySend(c echo.Context) error {
invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, "", lnPayReq) invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, "", lnPayReq)
if err != nil { if err != nil {
return err return nil, err
} }
currentBalance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userID) currentBalance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userID)
if err != nil { if err != nil {
return err return nil, err
} }
minimumBalance := invoice.Amount minimumBalance := invoice.Amount
@@ -88,14 +161,14 @@ func (controller *KeySendController) KeySend(c echo.Context) error {
} }
if currentBalance < minimumBalance { if currentBalance < minimumBalance {
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) return nil, c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError)
} }
invoice.DestinationCustomRecords = map[uint64][]byte{} invoice.DestinationCustomRecords = map[uint64][]byte{}
for key, value := range reqBody.CustomRecords { for key, value := range reqBody.CustomRecords {
intKey, err := strconv.Atoi(key) intKey, err := strconv.Atoi(key)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) return nil, c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
} }
invoice.DestinationCustomRecords[uint64(intKey)] = []byte(value) invoice.DestinationCustomRecords[uint64(intKey)] = []byte(value)
} }
@@ -103,7 +176,7 @@ func (controller *KeySendController) KeySend(c echo.Context) error {
if err != nil { if err != nil {
c.Logger().Errorf("Payment failed: user_id:%v error: %v", userID, err) c.Logger().Errorf("Payment failed: user_id:%v error: %v", userID, err)
sentry.CaptureException(err) sentry.CaptureException(err)
return c.JSON(http.StatusBadRequest, echo.Map{ return nil, c.JSON(http.StatusBadRequest, echo.Map{
"error": true, "error": true,
"code": 10, "code": 10,
"message": err.Error(), "message": err.Error(),
@@ -119,5 +192,5 @@ func (controller *KeySendController) KeySend(c echo.Context) error {
PaymentHash: sendPaymentResponse.PaymentHashStr, PaymentHash: sendPaymentResponse.PaymentHashStr,
} }
return c.JSON(http.StatusOK, responseBody) return responseBody, c.JSON(http.StatusOK, responseBody)
} }

View File

@@ -397,6 +397,57 @@ const docTemplate = `{
} }
} }
}, },
"/v2/payments/keysend/multi": {
"post": {
"security": [
{
"OAuth2Password": []
}
],
"description": "Pay multiple nodes without an invoice using their public key",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Payment"
],
"summary": "Make multiple keysend payments",
"parameters": [
{
"description": "Invoice to pay",
"name": "MultiKeySendRequestBody",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v2controllers.MultiKeySendRequestBody"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v2controllers.MultiKeySendResponseBody"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/responses.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/responses.ErrorResponse"
}
}
}
}
},
"/v2/users": { "/v2/users": {
"post": { "post": {
"description": "Create a new account with a login and password", "description": "Create a new account with a login and password",
@@ -543,6 +594,9 @@ const docTemplate = `{
"v2controllers.CreateUserResponseBody": { "v2controllers.CreateUserResponseBody": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": {
"type": "integer"
},
"login": { "login": {
"type": "string" "type": "string"
}, },
@@ -660,6 +714,39 @@ const docTemplate = `{
} }
} }
}, },
"v2controllers.KeySendResult": {
"type": "object",
"properties": {
"error": {
"$ref": "#/definitions/responses.ErrorResponse"
},
"keysend": {
"$ref": "#/definitions/v2controllers.KeySendResponseBody"
}
}
},
"v2controllers.MultiKeySendRequestBody": {
"type": "object",
"properties": {
"keysends": {
"type": "array",
"items": {
"$ref": "#/definitions/v2controllers.KeySendRequestBody"
}
}
}
},
"v2controllers.MultiKeySendResponseBody": {
"type": "object",
"properties": {
"keysends": {
"type": "array",
"items": {
"$ref": "#/definitions/v2controllers.KeySendResult"
}
}
}
},
"v2controllers.PayInvoiceRequestBody": { "v2controllers.PayInvoiceRequestBody": {
"type": "object", "type": "object",
"required": [ "required": [
@@ -719,7 +806,7 @@ var SwaggerInfo = &swag.Spec{
Version: "0.9.0", Version: "0.9.0",
Host: "", Host: "",
BasePath: "/", BasePath: "/",
Schemes: []string{"https", "http"}, Schemes: []string{},
Title: "LndHub.go", Title: "LndHub.go",
Description: "Accounting wrapper for the Lightning Network providing separate accounts for end-users.", Description: "Accounting wrapper for the Lightning Network providing separate accounts for end-users.",
InfoInstanceName: "swagger", InfoInstanceName: "swagger",

View File

@@ -1,8 +1,4 @@
{ {
"schemes": [
"https",
"http"
],
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "Accounting wrapper for the Lightning Network providing separate accounts for end-users.", "description": "Accounting wrapper for the Lightning Network providing separate accounts for end-users.",
@@ -393,6 +389,57 @@
} }
} }
}, },
"/v2/payments/keysend/multi": {
"post": {
"security": [
{
"OAuth2Password": []
}
],
"description": "Pay multiple nodes without an invoice using their public key",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Payment"
],
"summary": "Make multiple keysend payments",
"parameters": [
{
"description": "Invoice to pay",
"name": "MultiKeySendRequestBody",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v2controllers.MultiKeySendRequestBody"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v2controllers.MultiKeySendResponseBody"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/responses.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/responses.ErrorResponse"
}
}
}
}
},
"/v2/users": { "/v2/users": {
"post": { "post": {
"description": "Create a new account with a login and password", "description": "Create a new account with a login and password",
@@ -539,6 +586,9 @@
"v2controllers.CreateUserResponseBody": { "v2controllers.CreateUserResponseBody": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": {
"type": "integer"
},
"login": { "login": {
"type": "string" "type": "string"
}, },
@@ -656,6 +706,39 @@
} }
} }
}, },
"v2controllers.KeySendResult": {
"type": "object",
"properties": {
"error": {
"$ref": "#/definitions/responses.ErrorResponse"
},
"keysend": {
"$ref": "#/definitions/v2controllers.KeySendResponseBody"
}
}
},
"v2controllers.MultiKeySendRequestBody": {
"type": "object",
"properties": {
"keysends": {
"type": "array",
"items": {
"$ref": "#/definitions/v2controllers.KeySendRequestBody"
}
}
}
},
"v2controllers.MultiKeySendResponseBody": {
"type": "object",
"properties": {
"keysends": {
"type": "array",
"items": {
"$ref": "#/definitions/v2controllers.KeySendResult"
}
}
}
},
"v2controllers.PayInvoiceRequestBody": { "v2controllers.PayInvoiceRequestBody": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@@ -64,6 +64,8 @@ definitions:
type: object type: object
v2controllers.CreateUserResponseBody: v2controllers.CreateUserResponseBody:
properties: properties:
id:
type: integer
login: login:
type: string type: string
password: password:
@@ -141,6 +143,27 @@ definitions:
payment_preimage: payment_preimage:
type: string type: string
type: object type: object
v2controllers.KeySendResult:
properties:
error:
$ref: '#/definitions/responses.ErrorResponse'
keysend:
$ref: '#/definitions/v2controllers.KeySendResponseBody'
type: object
v2controllers.MultiKeySendRequestBody:
properties:
keysends:
items:
$ref: '#/definitions/v2controllers.KeySendRequestBody'
type: array
type: object
v2controllers.MultiKeySendResponseBody:
properties:
keysends:
items:
$ref: '#/definitions/v2controllers.KeySendResult'
type: array
type: object
v2controllers.PayInvoiceRequestBody: v2controllers.PayInvoiceRequestBody:
properties: properties:
amount: amount:
@@ -418,6 +441,38 @@ paths:
summary: Make a keysend payment summary: Make a keysend payment
tags: tags:
- Payment - Payment
/v2/payments/keysend/multi:
post:
consumes:
- application/json
description: Pay multiple nodes without an invoice using their public key
parameters:
- description: Invoice to pay
in: body
name: MultiKeySendRequestBody
required: true
schema:
$ref: '#/definitions/v2controllers.MultiKeySendRequestBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/v2controllers.MultiKeySendResponseBody'
"400":
description: Bad Request
schema:
$ref: '#/definitions/responses.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/responses.ErrorResponse'
security:
- OAuth2Password: []
summary: Make multiple keysend payments
tags:
- Payment
/v2/users: /v2/users:
post: post:
consumes: consumes:
@@ -447,9 +502,6 @@ paths:
summary: Create an account summary: Create an account
tags: tags:
- Account - Account
schemes:
- https
- http
securityDefinitions: securityDefinitions:
OAuth2Password: OAuth2Password:
flow: password flow: password

View File

@@ -13,11 +13,13 @@ func RegisterV2Endpoints(svc *service.LndhubService, e *echo.Echo, secured *echo
e.POST("/v2/users", v2controllers.NewCreateUserController(svc).CreateUser, strictRateLimitMiddleware, adminMw) e.POST("/v2/users", v2controllers.NewCreateUserController(svc).CreateUser, strictRateLimitMiddleware, adminMw)
} }
invoiceCtrl := v2controllers.NewInvoiceController(svc) invoiceCtrl := v2controllers.NewInvoiceController(svc)
keysendCtrl := v2controllers.NewKeySendController(svc)
secured.POST("/v2/invoices", invoiceCtrl.AddInvoice) secured.POST("/v2/invoices", invoiceCtrl.AddInvoice)
secured.GET("/v2/invoices/incoming", invoiceCtrl.GetIncomingInvoices) secured.GET("/v2/invoices/incoming", invoiceCtrl.GetIncomingInvoices)
secured.GET("/v2/invoices/outgoing", invoiceCtrl.GetOutgoingInvoices) secured.GET("/v2/invoices/outgoing", invoiceCtrl.GetOutgoingInvoices)
secured.GET("/v2/invoices/:payment_hash", invoiceCtrl.GetInvoice) secured.GET("/v2/invoices/:payment_hash", invoiceCtrl.GetInvoice)
securedWithStrictRateLimit.POST("/v2/payments/bolt11", v2controllers.NewPayInvoiceController(svc).PayInvoice) securedWithStrictRateLimit.POST("/v2/payments/bolt11", v2controllers.NewPayInvoiceController(svc).PayInvoice)
securedWithStrictRateLimit.POST("/v2/payments/keysend", v2controllers.NewKeySendController(svc).KeySend) securedWithStrictRateLimit.POST("/v2/payments/keysend", keysendCtrl.KeySend)
securedWithStrictRateLimit.POST("/v2/payments/keysend/multi", keysendCtrl.MultiKeySend)
secured.GET("/v2/balance", v2controllers.NewBalanceController(svc).Balance) secured.GET("/v2/balance", v2controllers.NewBalanceController(svc).Balance)
} }