mirror of
https://github.com/getAlby/lndhub.go.git
synced 2026-02-18 11:24:22 +01:00
fix merge conflict
This commit is contained in:
@@ -35,7 +35,8 @@ func (controller *AuthController) Auth(c echo.Context) error {
|
||||
var body AuthRequestBody
|
||||
|
||||
if err := c.Bind(&body); err != nil {
|
||||
return err
|
||||
c.Logger().Errorf("Failed to load auth user request body: %v", err)
|
||||
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
|
||||
}
|
||||
if err := c.Validate(&body); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/getAlby/lndhub.go/lib/responses"
|
||||
"github.com/getAlby/lndhub.go/lib/service"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -33,12 +34,13 @@ func (controller *CreateUserController) CreateUser(c echo.Context) error {
|
||||
var body CreateUserRequestBody
|
||||
|
||||
if err := c.Bind(&body); err != nil {
|
||||
return err
|
||||
c.Logger().Errorf("Failed to load create user request body: %v", err)
|
||||
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
|
||||
}
|
||||
user, err := controller.svc.CreateUser(c.Request().Context(), body.Login, body.Password)
|
||||
//todo json response
|
||||
if err != nil {
|
||||
return err
|
||||
c.Logger().Errorf("Failed to create user: %v", err)
|
||||
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
|
||||
}
|
||||
|
||||
var ResponseBody CreateUserResponseBody
|
||||
|
||||
102
controllers/keysend.ctrl.go
Normal file
102
controllers/keysend.ctrl.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/getAlby/lndhub.go/lib"
|
||||
"github.com/getAlby/lndhub.go/lib/responses"
|
||||
"github.com/getAlby/lndhub.go/lib/service"
|
||||
"github.com/getAlby/lndhub.go/lnd"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
)
|
||||
|
||||
// KeySendController : Key send controller struct
|
||||
type KeySendController struct {
|
||||
svc *service.LndhubService
|
||||
}
|
||||
|
||||
func NewKeySendController(svc *service.LndhubService) *KeySendController {
|
||||
return &KeySendController{svc: svc}
|
||||
}
|
||||
|
||||
type KeySendRequestBody struct {
|
||||
Amount int64 `json:"amount" validate:"required"`
|
||||
Destination string `json:"destination" validate:"required"`
|
||||
Memo string `json:"memo" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type KeySendResponseBody struct {
|
||||
RHash *lib.JavaScriptBuffer `json:"payment_hash,omitempty"`
|
||||
Amount int64 `json:"num_satoshis,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Destination string `json:"destination,omitempty"`
|
||||
DescriptionHashStr string `json:"description_hash,omitempty"`
|
||||
PaymentError string `json:"payment_error,omitempty"`
|
||||
PaymentPreimage *lib.JavaScriptBuffer `json:"payment_preimage,omitempty"`
|
||||
PaymentRoute *service.Route `json:"route,omitempty"`
|
||||
}
|
||||
|
||||
// KeySend : Key send Controller
|
||||
func (controller *KeySendController) KeySend(c echo.Context) error {
|
||||
userID := c.Get("UserID").(int64)
|
||||
reqBody := KeySendRequestBody{}
|
||||
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)
|
||||
}
|
||||
|
||||
if err := c.Validate(&reqBody); err != nil {
|
||||
c.Logger().Errorf("Invalid keysend request body: %v", err)
|
||||
return c.JSON(http.StatusBadRequest, responses.BadArgumentsError)
|
||||
}
|
||||
|
||||
lnPayReq := &lnd.LNPayReq{
|
||||
PayReq: &lnrpc.PayReq{
|
||||
Destination: reqBody.Destination,
|
||||
NumSatoshis: reqBody.Amount,
|
||||
Description: reqBody.Memo,
|
||||
},
|
||||
Keysend: true,
|
||||
}
|
||||
|
||||
invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, "", lnPayReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentBalance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
sentry.CaptureException(err)
|
||||
return c.JSON(http.StatusBadRequest, echo.Map{
|
||||
"error": true,
|
||||
"code": 10,
|
||||
"message": fmt.Sprintf("Payment failed. Does the receiver have enough inbound capacity? (%v)", err),
|
||||
})
|
||||
}
|
||||
|
||||
responseBody := &KeySendResponseBody{}
|
||||
responseBody.RHash = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentHash}
|
||||
responseBody.Amount = invoice.Amount
|
||||
responseBody.Destination = invoice.DestinationPubkeyHex
|
||||
responseBody.Description = invoice.Memo
|
||||
responseBody.DescriptionHashStr = invoice.DescriptionHash
|
||||
responseBody.PaymentError = sendPaymentResponse.PaymentError
|
||||
responseBody.PaymentPreimage = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentPreimage}
|
||||
responseBody.PaymentRoute = sendPaymentResponse.PaymentRoute
|
||||
|
||||
return c.JSON(http.StatusOK, responseBody)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/getAlby/lndhub.go/lib"
|
||||
"github.com/getAlby/lndhub.go/lib/responses"
|
||||
"github.com/getAlby/lndhub.go/lib/service"
|
||||
"github.com/getAlby/lndhub.go/lnd"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -69,7 +70,12 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error {
|
||||
}
|
||||
*/
|
||||
|
||||
invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, paymentRequest, decodedPaymentRequest)
|
||||
lnPayReq := &lnd.LNPayReq{
|
||||
PayReq: decodedPaymentRequest,
|
||||
Keysend: false,
|
||||
}
|
||||
|
||||
invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, paymentRequest, lnPayReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
1
db/migrations/20220304103000_keysend_invoice.up.sql
Normal file
1
db/migrations/20220304103000_keysend_invoice.up.sql
Normal file
@@ -0,0 +1 @@
|
||||
alter table invoices add column keysend boolean;
|
||||
@@ -22,6 +22,7 @@ type Invoice struct {
|
||||
RHash string `json:"r_hash"`
|
||||
Preimage string `json:"preimage" bun:",nullzero"`
|
||||
Internal bool `json:"internal" bun:",nullzero"`
|
||||
Keysend bool `json:"keysend" bun:",nullzero"`
|
||||
State string `json:"state" bun:",default:'initialized'"`
|
||||
ErrorMessage string `json:"error_message" bun:",nullzero"`
|
||||
AddIndex uint64 `json:"add_index" bun:",nullzero"`
|
||||
|
||||
145
go.mod
145
go.mod
@@ -1,10 +1,10 @@
|
||||
module github.com/getAlby/lndhub.go
|
||||
|
||||
go 1.17
|
||||
|
||||
// +heroku goVersion go1.17
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/go-playground/validator/v10 v10.10.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
@@ -27,149 +27,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/aead/siphash v1.0.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||
github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // indirect
|
||||
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 // indirect
|
||||
github.com/btcsuite/btcwallet v0.13.0 // indirect
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 // indirect
|
||||
github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 // indirect
|
||||
github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect
|
||||
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec // indirect
|
||||
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c // indirect
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/lru v1.0.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fergusstrange/embedded-postgres v1.10.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.10.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.8.1 // indirect
|
||||
github.com/jackc/pgx/v4 v4.13.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/jrick/logrotate v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kkdai/bstream v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lib/pq v1.10.3 // indirect
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
||||
github.com/lightninglabs/neutrino v0.13.0 // indirect
|
||||
github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 // indirect
|
||||
github.com/lightningnetwork/lnd/clock v1.1.0 // indirect
|
||||
github.com/lightningnetwork/lnd/healthcheck v1.2.0 // indirect
|
||||
github.com/lightningnetwork/lnd/kvdb v1.2.1 // indirect
|
||||
github.com/lightningnetwork/lnd/queue v1.1.0 // indirect
|
||||
github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.10 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.0 // indirect
|
||||
github.com/miekg/dns v1.1.43 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nwaples/rardecode v1.1.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.26.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||
github.com/rs/zerolog v1.26.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/client/v2 v2.305.0 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/raft/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/server/v3 v3.5.0 // indirect
|
||||
go.opentelemetry.io/contrib v0.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.17.0 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
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
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
mellium.im/sasl v0.2.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.22 // indirect
|
||||
modernc.org/ccgo/v3 v3.14.0 // indirect
|
||||
modernc.org/libc v1.13.2 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.5 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/sqlite v1.14.3 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
122
integration_tests/keysend_test.go
Normal file
122
integration_tests/keysend_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package integration_tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/getAlby/lndhub.go/controllers"
|
||||
"github.com/getAlby/lndhub.go/lib"
|
||||
"github.com/getAlby/lndhub.go/lib/responses"
|
||||
"github.com/getAlby/lndhub.go/lib/service"
|
||||
"github.com/getAlby/lndhub.go/lib/tokens"
|
||||
"github.com/getAlby/lndhub.go/lnd"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type KeySendTestSuite struct {
|
||||
TestSuite
|
||||
fundingClient *lnd.LNDWrapper
|
||||
service *service.LndhubService
|
||||
aliceLogin controllers.CreateUserResponseBody
|
||||
aliceToken string
|
||||
invoiceUpdateSubCancelFn context.CancelFunc
|
||||
}
|
||||
|
||||
func (suite *KeySendTestSuite) SetupSuite() {
|
||||
lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{
|
||||
Address: lnd2RegtestAddress,
|
||||
MacaroonHex: lnd2RegtestMacaroonHex,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error setting up funding client: %v", err)
|
||||
}
|
||||
suite.fundingClient = lndClient
|
||||
|
||||
svc, err := LndHubTestServiceInit(nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing test service: %v", err)
|
||||
}
|
||||
users, userTokens, err := createUsers(svc, 1)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating test users: %v", err)
|
||||
}
|
||||
// Subscribe to LND invoice updates in the background
|
||||
// store cancel func to be called in tear down suite
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
suite.invoiceUpdateSubCancelFn = cancel
|
||||
go svc.InvoiceUpdateSubscription(ctx)
|
||||
suite.service = svc
|
||||
e := echo.New()
|
||||
|
||||
e.HTTPErrorHandler = responses.HTTPErrorHandler
|
||||
e.Validator = &lib.CustomValidator{Validator: validator.New()}
|
||||
suite.echo = e
|
||||
assert.Equal(suite.T(), 1, len(users))
|
||||
assert.Equal(suite.T(), 1, len(userTokens))
|
||||
suite.aliceLogin = users[0]
|
||||
suite.aliceToken = userTokens[0]
|
||||
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.POST("/payinvoice", controllers.NewPayInvoiceController(suite.service).PayInvoice)
|
||||
suite.echo.POST("/keysend", controllers.NewKeySendController(suite.service).KeySend)
|
||||
}
|
||||
|
||||
func (suite *KeySendTestSuite) TearDownSuite() {
|
||||
suite.invoiceUpdateSubCancelFn()
|
||||
}
|
||||
|
||||
func (suite *KeySendTestSuite) TestKeysendPayment() {
|
||||
aliceFundingSats := 1000
|
||||
externalSatRequested := 500
|
||||
//fund alice account
|
||||
invoiceResponse := suite.createAddInvoiceReq(aliceFundingSats, "integration test external 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)
|
||||
|
||||
suite.createKeySendReq(int64(externalSatRequested), "key send test", simnetLnd3PubKey, suite.aliceToken)
|
||||
|
||||
// check that balance was reduced
|
||||
userId := getUserIdFromToken(suite.aliceToken)
|
||||
aliceBalance, err := suite.service.CurrentUserBalance(context.Background(), userId)
|
||||
if err != nil {
|
||||
fmt.Printf("Error when getting balance %v\n", err.Error())
|
||||
}
|
||||
assert.Equal(suite.T(), int64(aliceFundingSats)-int64(externalSatRequested), aliceBalance)
|
||||
}
|
||||
|
||||
func (suite *KeySendTestSuite) TestKeysendPaymentNonExistentDestination() {
|
||||
aliceFundingSats := 1000
|
||||
externalSatRequested := 500
|
||||
//fund alice account
|
||||
invoiceResponse := suite.createAddInvoiceReq(aliceFundingSats, "integration test external 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)
|
||||
|
||||
suite.createKeySendReqError(int64(externalSatRequested), "key send test", "12345", suite.aliceToken)
|
||||
}
|
||||
|
||||
func TestKeySendTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(KeySendTestSuite))
|
||||
}
|
||||
@@ -33,6 +33,9 @@ const (
|
||||
// This will cause payment to be routed through lnd2, which will charge a fee (lnd default fee 1 sat base + 1 ppm).
|
||||
lnd3RegtestAddress = "rpc.lnd3.regtest.getalby.com:443"
|
||||
lnd3RegtestMacaroonHex = "0201036c6e6402f801030a102a5aa69a5efdf4b4a55a5304b164641f1201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620defbb5a809262297fd661a9ab6d3deb4b7acca4f1309c79addb952f0dc2d8c82"
|
||||
simnetLnd1PubKey = "0242898f86064c2fd72de22059c947a83ba23e9d97aedeae7b6dba647123f1d71b"
|
||||
simnetLnd2PubKey = "025c1d5d1b4c983cc6350fc2d756fbb59b4dc365e45e87f8e3afe07e24013e8220"
|
||||
simnetLnd3PubKey = "03c7092d076f799ab18806743634b4c9bb34e351bdebc91d5b35963f3dc63ec5aa"
|
||||
)
|
||||
|
||||
func LndHubTestServiceInit(lndClientMock lnd.LightningClientWrapper) (svc *service.LndhubService, err error) {
|
||||
@@ -154,6 +157,44 @@ func (suite *TestSuite) createAddInvoiceReq(amt int, memo, token string) *contro
|
||||
return invoiceResponse
|
||||
}
|
||||
|
||||
func (suite *TestSuite) createKeySendReq(amount int64, memo, destination, token string) *controllers.KeySendResponseBody {
|
||||
rec := httptest.NewRecorder()
|
||||
var buf bytes.Buffer
|
||||
assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(&controllers.KeySendRequestBody{
|
||||
Amount: amount,
|
||||
Destination: destination,
|
||||
Memo: memo,
|
||||
}))
|
||||
req := httptest.NewRequest(http.MethodPost, "/keysend", &buf)
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
suite.echo.ServeHTTP(rec, req)
|
||||
|
||||
keySendResponse := &controllers.KeySendResponseBody{}
|
||||
assert.Equal(suite.T(), http.StatusOK, rec.Code)
|
||||
assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(keySendResponse))
|
||||
return keySendResponse
|
||||
}
|
||||
|
||||
func (suite *TestSuite) createKeySendReqError(amount int64, memo, destination, token string) *responses.ErrorResponse {
|
||||
rec := httptest.NewRecorder()
|
||||
var buf bytes.Buffer
|
||||
assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(&controllers.KeySendRequestBody{
|
||||
Amount: amount,
|
||||
Destination: destination,
|
||||
Memo: memo,
|
||||
}))
|
||||
req := httptest.NewRequest(http.MethodPost, "/keysend", &buf)
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
suite.echo.ServeHTTP(rec, req)
|
||||
|
||||
errorResponse := &responses.ErrorResponse{}
|
||||
assert.Equal(suite.T(), http.StatusBadRequest, rec.Code)
|
||||
assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(errorResponse))
|
||||
return errorResponse
|
||||
}
|
||||
|
||||
func (suite *TestSuite) createPayInvoiceReq(payReq string, token string) *controllers.PayInvoiceResponseBody {
|
||||
rec := httptest.NewRecorder()
|
||||
var buf bytes.Buffer
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/getAlby/lndhub.go/common"
|
||||
"github.com/getAlby/lndhub.go/db/models"
|
||||
"github.com/getAlby/lndhub.go/lnd"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/labstack/gommon/random"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
@@ -100,25 +102,14 @@ func (svc *LndhubService) SendInternalPayment(ctx context.Context, invoice *mode
|
||||
|
||||
func (svc *LndhubService) SendPaymentSync(ctx context.Context, invoice *models.Invoice) (SendPaymentResponse, error) {
|
||||
sendPaymentResponse := SendPaymentResponse{}
|
||||
// TODO: set dynamic fee limit
|
||||
feeLimit := lnrpc.FeeLimit{
|
||||
//Limit: &lnrpc.FeeLimit_Percent{
|
||||
// Percent: 2,
|
||||
//},
|
||||
Limit: &lnrpc.FeeLimit_Fixed{
|
||||
Fixed: 300,
|
||||
},
|
||||
}
|
||||
|
||||
// Prepare the LNRPC call
|
||||
sendPaymentRequest := lnrpc.SendRequest{
|
||||
PaymentRequest: invoice.PaymentRequest,
|
||||
Amt: invoice.Amount,
|
||||
FeeLimit: &feeLimit,
|
||||
sendPaymentRequest, err := createLnRpcSendRequest(invoice)
|
||||
if err != nil {
|
||||
return sendPaymentResponse, err
|
||||
}
|
||||
|
||||
// Execute the payment
|
||||
sendPaymentResult, err := svc.LndClient.SendPaymentSync(ctx, &sendPaymentRequest)
|
||||
sendPaymentResult, err := svc.LndClient.SendPaymentSync(ctx, sendPaymentRequest)
|
||||
if err != nil {
|
||||
return sendPaymentResponse, err
|
||||
}
|
||||
@@ -138,6 +129,44 @@ func (svc *LndhubService) SendPaymentSync(ctx context.Context, invoice *models.I
|
||||
return sendPaymentResponse, nil
|
||||
}
|
||||
|
||||
func createLnRpcSendRequest(invoice *models.Invoice) (*lnrpc.SendRequest, error) {
|
||||
// TODO: set dynamic fee limit
|
||||
feeLimit := lnrpc.FeeLimit{
|
||||
//Limit: &lnrpc.FeeLimit_Percent{
|
||||
// Percent: 2,
|
||||
//},
|
||||
Limit: &lnrpc.FeeLimit_Fixed{
|
||||
Fixed: 300,
|
||||
},
|
||||
}
|
||||
|
||||
if !invoice.Keysend {
|
||||
return &lnrpc.SendRequest{
|
||||
PaymentRequest: invoice.PaymentRequest,
|
||||
Amt: invoice.Amount,
|
||||
FeeLimit: &feeLimit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
preImage := makePreimageHex()
|
||||
pHash := sha256.New()
|
||||
pHash.Write(preImage)
|
||||
// Prepare the LNRPC call
|
||||
//See: https://github.com/hsjoberg/blixt-wallet/blob/9fcc56a7dc25237bc14b85e6490adb9e044c009c/src/lndmobile/index.ts#L251-L270
|
||||
destBytes, err := hex.DecodeString(invoice.DestinationPubkeyHex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lnrpc.SendRequest{
|
||||
Dest: destBytes,
|
||||
Amt: invoice.Amount,
|
||||
PaymentHash: pHash.Sum(nil),
|
||||
FeeLimit: &feeLimit,
|
||||
DestFeatures: []lnrpc.FeatureBit{lnrpc.FeatureBit_TLV_ONION_REQ},
|
||||
DestCustomRecords: map[uint64][]byte{KEYSEND_CUSTOM_RECORD: preImage, TLV_WHATSAT_MESSAGE: []byte(invoice.Memo)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *LndhubService) PayInvoice(ctx context.Context, invoice *models.Invoice) (*SendPaymentResponse, error) {
|
||||
userId := invoice.UserID
|
||||
|
||||
@@ -275,19 +304,20 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice *
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *LndhubService) AddOutgoingInvoice(ctx context.Context, userID int64, paymentRequest string, decodedInvoice *lnrpc.PayReq) (*models.Invoice, error) {
|
||||
func (svc *LndhubService) AddOutgoingInvoice(ctx context.Context, userID int64, paymentRequest string, lnPayReq *lnd.LNPayReq) (*models.Invoice, error) {
|
||||
// Initialize new DB invoice
|
||||
invoice := models.Invoice{
|
||||
Type: common.InvoiceTypeOutgoing,
|
||||
UserID: userID,
|
||||
PaymentRequest: paymentRequest,
|
||||
RHash: decodedInvoice.PaymentHash,
|
||||
Amount: decodedInvoice.NumSatoshis,
|
||||
RHash: lnPayReq.PayReq.PaymentHash,
|
||||
Amount: lnPayReq.PayReq.NumSatoshis,
|
||||
State: common.InvoiceStateInitialized,
|
||||
DestinationPubkeyHex: decodedInvoice.Destination,
|
||||
DescriptionHash: decodedInvoice.DescriptionHash,
|
||||
Memo: decodedInvoice.Description,
|
||||
ExpiresAt: bun.NullTime{Time: time.Unix(decodedInvoice.Timestamp, 0).Add(time.Duration(decodedInvoice.Expiry) * time.Second)},
|
||||
DestinationPubkeyHex: lnPayReq.PayReq.Destination,
|
||||
DescriptionHash: lnPayReq.PayReq.DescriptionHash,
|
||||
Memo: lnPayReq.PayReq.Description,
|
||||
Keysend: lnPayReq.Keysend,
|
||||
ExpiresAt: bun.NullTime{Time: time.Unix(lnPayReq.PayReq.Timestamp, 0).Add(time.Duration(lnPayReq.PayReq.Expiry) * time.Second)},
|
||||
}
|
||||
|
||||
// Save invoice
|
||||
|
||||
@@ -6,6 +6,13 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
)
|
||||
|
||||
//https://github.com/hsjoberg/blixt-wallet/blob/9fcc56a7dc25237bc14b85e6490adb9e044c009c/src/utils/constants.ts#L5
|
||||
const (
|
||||
KEYSEND_CUSTOM_RECORD = 5482373484
|
||||
TLV_WHATSAT_MESSAGE = 34349334
|
||||
TLV_RECORD_NAME = 128100
|
||||
)
|
||||
|
||||
func (svc *LndhubService) GetInfo(ctx context.Context) (*lnrpc.GetInfoResponse, error) {
|
||||
return svc.LndClient.GetInfo(ctx, &lnrpc.GetInfoRequest{})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ import (
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
type LNPayReq struct {
|
||||
PayReq *lnrpc.PayReq
|
||||
Keysend bool
|
||||
}
|
||||
|
||||
// LNDoptions are the options for the connection to the lnd node.
|
||||
type LNDoptions struct {
|
||||
Address string
|
||||
|
||||
1
main.go
1
main.go
@@ -134,6 +134,7 @@ func main() {
|
||||
secured.GET("/checkpayment/:payment_hash", controllers.NewCheckPaymentController(svc).CheckPayment)
|
||||
secured.GET("/balance", controllers.NewBalanceController(svc).Balance)
|
||||
secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo)
|
||||
secured.POST("/keysend", controllers.NewKeySendController(svc).KeySend)
|
||||
|
||||
// These endpoints are currently not supported and we return a blank response for backwards compatibility
|
||||
blankController := controllers.NewBlankController(svc)
|
||||
|
||||
Reference in New Issue
Block a user