aboutsummaryrefslogtreecommitdiffstats
path: root/lib/nwc.ts
blob: d8323495b5fc0bfe2801dd90588f1cdcd8c1f434 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import config from "$config";
import { err } from "$lib/logging";
import { g } from "$lib/db";
import { Relay } from "nostr";
import { getInvoice, sleep } from "$lib/utils";
import { sendInternal, sendLightning } from "$lib/payments";
import { serverPubkey } from "$lib/nostr";
import { nip04, nip19, finalizeEvent } from "nostr-tools";
import type { UnsignedEvent } from "nostr-tools";
import ln from "$lib/ln";

let result = (result) => ({ result });
let error = (error) => ({ error });

export default () => {
  let r = new Relay("ws://strfry:7777");

  r.on("open", (_) => {
    r.subscribe("nwc", { kinds: [23194], "#p": [serverPubkey] });
  });

  r.on("event", async (sub, ev) => {
    try {
      if (sub !== "nwc") return;
      let { content, pubkey } = ev;
      let sk = nip19.decode(config.nostrKey).data as Uint8Array;
      let { params, method } = JSON.parse(
        await nip04.decrypt(sk, pubkey, content),
      );

      let uid = await g(pubkey);
      let user = await g(`user:${uid}`);

      let result = await handle(method, params, user);
      let payload = JSON.stringify({ result_type: method, result });
      content = await nip04.encrypt(sk, pubkey, payload);

      let response: UnsignedEvent = {
        created_at: Math.floor(Date.now() / 1000),
        kind: 23195,
        pubkey: serverPubkey,
        tags: [
          ["p", pubkey],
          ["e", ev.id],
        ],
        content,
      };

      response = await finalizeEvent(response, sk);
      r.send(["EVENT", response]);
    } catch (e) {
      err("problem with nwc", e.message);
    }
  });
};

let handle = (method, params, user) =>
  ({
    async pay_invoice() {
      let { invoice: pr } = params;
      let { amount_msat, payee } = await ln.decode(pr);
      let { id } = await ln.getinfo();
      let amount = Math.round(amount_msat / 1000);

      if (payee === id) {
        let invoice = await getInvoice(pr);
        let recipient = await g(`user:${invoice.uid}`);

        let { id: preimage } = await sendInternal({
          amount,
          invoice,
          recipient,
          sender: user,
        });

        return result({ preimage });
      } else {
        await sendLightning({
          amount,
          user,
          pr,
          maxfee: 50,
        });

        for (let i = 0; i < 10; i++) {
          let { pays } = await ln.listpays(pr);
          let p = pays.find((p) => p.status === "complete");
          if (p) {
            let { preimage } = p;
            return result({ preimage });
          }
          await sleep(2000);
        }

        return error({ code: "INTERNAL", message: "Payment timed out" });
      }
    },

    async get_info() {
      let { alias, blockheight, color } = await ln.getinfo();
      return {
        result: {
          alias,
          color,
          pubkey: serverPubkey,
          network: "mainnet",
          block_height: blockheight,
          methods: ["pay_invoice", "get_balance", "get_info"],
        },
      };
    },

    async get_balance() {
      let balance = await g(`balance:${user.id}`);
      return result({ balance });
    },
  })[method](params);