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