alby-hub/nip47/controllers/receive_controller.go
Roland Bewick c2ae53dd07 feat: add NWC-321 pay and receive methods with BOLT-11 support
Implements the NWC-321 (BIP-321 Lightning Payments) pay and receive
methods, limited to BOLT-11 instructions:

- pay parses the BIP-321 URI, selects the lightning (BOLT-11)
  instruction and rejects URIs without one
  (UNSUPPORTED_PAYMENT_INSTRUCTION), validates the invoice network
  against the node network (UNSUPPORTED_NETWORK), rejects conflicting
  or invalid amounts, unknown req- parameters and payer_note
  (undeliverable over BOLT-11)
- receive returns a BIP-321 URI containing a single BOLT-11 invoice;
  a variable amount is rejected as zero-amount invoices are not
  supported
- both methods reuse the existing pay_invoice / make_invoice scopes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 21:24:01 +07:00

76 lines
2.3 KiB
Go

package controllers
import (
"context"
"github.com/getAlby/go-nostr"
"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/logger"
"github.com/getAlby/hub/nip47/models"
"github.com/sirupsen/logrus"
)
type receiveParams struct {
Amount *uint64 `json:"amount"`
Description string `json:"description"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type receiveResult struct {
Bip321 string `json:"bip321"`
TransactionId string `json:"transaction_id,omitempty"`
}
// HandleReceiveEvent handles the NWC-321 receive method. Currently only
// BOLT-11 instructions (the "lightning" URI parameter) are returned.
func (controller *nip47Controller) HandleReceiveEvent(ctx context.Context, nip47Request *models.Request, requestEventId uint, appId uint, publishResponse publishFunc) {
receiveParams := &receiveParams{}
resp := decodeRequest(nip47Request, receiveParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
return
}
if receiveParams.Amount == nil {
// variable-amount (zero-amount) invoices are not supported
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Error: &models.Error{
Code: constants.ERROR_BAD_REQUEST,
Message: "amount is required",
},
}, nostr.Tags{})
return
}
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
"app_id": appId,
"amount": *receiveParams.Amount,
"description": receiveParams.Description,
}).Debug("Handling receive request")
transaction, err := controller.transactionsService.MakeInvoice(ctx, *receiveParams.Amount, receiveParams.Description, "", 0, receiveParams.Metadata, controller.lnClient, &appId, &requestEventId, nil)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
"app_id": appId,
"amount": *receiveParams.Amount,
"description": receiveParams.Description,
}).Infof("Failed to make invoice: %v", err)
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Error: mapNip47Error(err),
}, nostr.Tags{})
return
}
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Result: receiveResult{
Bip321: "bitcoin:?lightning=" + transaction.PaymentRequest,
TransactionId: transaction.PaymentHash,
},
}, nostr.Tags{})
}