autopay_nwc.go raw

   1  package app
   2  
   3  import (
   4  	"context"
   5  	"crypto/rand"
   6  	"encoding/hex"
   7  	"encoding/json"
   8  	"fmt"
   9  	"sync"
  10  )
  11  
  12  // autoPayNWC implements bridge.NWCRequester with automatic invoice payment.
  13  // All invoices are marked as paid immediately on creation.
  14  type autoPayNWC struct {
  15  	mu       sync.Mutex
  16  	invoices map[string]*nwcInvoice
  17  	counter  int
  18  }
  19  
  20  type nwcInvoice struct {
  21  	bolt11      string
  22  	paymentHash string
  23  	amount      int64
  24  	preimage    string
  25  }
  26  
  27  func newAutoPayNWC() *autoPayNWC {
  28  	return &autoPayNWC{invoices: make(map[string]*nwcInvoice)}
  29  }
  30  
  31  func (a *autoPayNWC) Request(ctx context.Context, method string, params, result any) error {
  32  	switch method {
  33  	case "make_invoice":
  34  		return a.makeInvoice(params, result)
  35  	case "lookup_invoice":
  36  		return a.lookupInvoice(params, result)
  37  	case "get_balance":
  38  		return marshalInto(result, map[string]any{"balance": int64(1000000000)})
  39  	default:
  40  		return fmt.Errorf("unsupported NWC method: %s", method)
  41  	}
  42  }
  43  
  44  func (a *autoPayNWC) makeInvoice(params, result any) error {
  45  	a.mu.Lock()
  46  	defer a.mu.Unlock()
  47  
  48  	a.counter++
  49  	var hashBytes, preBytes [32]byte
  50  	rand.Read(hashBytes[:])
  51  	rand.Read(preBytes[:])
  52  	paymentHash := hex.EncodeToString(hashBytes[:])
  53  	preimage := hex.EncodeToString(preBytes[:])
  54  
  55  	var amount int64
  56  	if m, ok := params.(map[string]any); ok {
  57  		if v, ok := m["amount"]; ok {
  58  			switch n := v.(type) {
  59  			case int64:
  60  				amount = n
  61  			case float64:
  62  				amount = int64(n)
  63  			case int:
  64  				amount = int64(n)
  65  			}
  66  		}
  67  	}
  68  
  69  	bolt11 := fmt.Sprintf("lnbc%du1autopay%s", amount/1000, paymentHash[:16])
  70  	a.invoices[paymentHash] = &nwcInvoice{
  71  		bolt11:      bolt11,
  72  		paymentHash: paymentHash,
  73  		amount:      amount,
  74  		preimage:    preimage,
  75  	}
  76  
  77  	return marshalInto(result, map[string]any{
  78  		"invoice":      bolt11,
  79  		"payment_hash": paymentHash,
  80  		"amount":       amount,
  81  	})
  82  }
  83  
  84  func (a *autoPayNWC) lookupInvoice(params, result any) error {
  85  	a.mu.Lock()
  86  	defer a.mu.Unlock()
  87  
  88  	var paymentHash string
  89  	if m, ok := params.(map[string]any); ok {
  90  		if v, ok := m["payment_hash"].(string); ok {
  91  			paymentHash = v
  92  		}
  93  	}
  94  	inv, ok := a.invoices[paymentHash]
  95  	if !ok {
  96  		return fmt.Errorf("invoice not found: %s", paymentHash)
  97  	}
  98  	return marshalInto(result, map[string]any{
  99  		"invoice":      inv.bolt11,
 100  		"payment_hash": inv.paymentHash,
 101  		"amount":       inv.amount,
 102  		"preimage":     inv.preimage,
 103  		"settled_at":   1,
 104  	})
 105  }
 106  
 107  func marshalInto(dst any, src map[string]any) error {
 108  	b, err := json.Marshal(src)
 109  	if err != nil {
 110  		return err
 111  	}
 112  	return json.Unmarshal(b, dst)
 113  }
 114