aboutsummaryrefslogtreecommitdiffstats
path: root/routes/lnurl.js
blob: 5660fa1b05d63b3238da6114f09dbed0a39f36d9 (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
import migrate from "$lib/migrate";
import { g, s } from "$lib/db";
import { l, warn } from "$lib/logging";
import { bail, getUser, fail } from "$lib/utils";
import { v4 } from "uuid";
import got from "got";
import { generate } from "$lib/invoices";
import { bech32 } from "bech32";
import { fields, pick } from "$lib/utils";
import { types } from "$lib/payments";
import crypto from "crypto";

import config from "$config";
let { admin, classic } = config;

let { URL } = process.env;
let host = URL.split("/").at(-1);

export default {
  async encode({ query: { address } }, res) {
    let [name, domain] = address.split("@");
    let url = `https://${domain}/.well-known/lnurlp/${name}`;

    try {
      let r = await got(url).json();
      if (r.tag !== "payRequest") fail("not an ln address");
    } catch (e) {
      let m = `failed to lookup lightning address ${address}`;
      warn(m);
      return bail(res, m);
    }

    let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000);
    res.send(enc);
  },

  async decode({ query: { text } }, res) {
    try {
      let url = Buffer.from(
        bech32.fromWords(bech32.decode(text, 20000).words),
      ).toString();

      res.send(await got(url).json());
    } catch (e) {
      bail(res, e.message);
    }
  },

  async lnurlp({ params: { username } }, res) {
    try {
      let { id: uid, pubkey: nostrPubkey } = await getUser(username);

      let metadata = JSON.stringify([
        ["text/plain", `Paying ${username}@${host}`],
        ["text/identifier", `${username}@${host}`],
      ]);

      let id = v4();
      await s(`lnurl:${id}`, uid);

      res.send({
        allowsNostr: true,
        minSendable: 1000,
        maxSendable: 100000000000,
        metadata,
        nostrPubkey,
        callback: `${URL}/api/lnurl/${id}`,
        tag: "payRequest",
      });
    } catch (e) {
      warn("problem generating lnurlp request", e.message);
      bail(res, e.message);
    }
  },

  async lnurlw({ params: { k1, pr } }, res) {},

  async lnurl({ params: { id }, query: { amount, nostr } }, res) {
    let uid = await g(`lnurl:${id}`);
    let user = await g(`user:${uid}`);
    if (!user) user = await migrate(uid);
    if (!(user && user.username)) return bail(res, "user not found");
    let { username } = user;
    username = username.replace(/\s/g, "").toLowerCase();

    let metadata = JSON.stringify([
      ["text/plain", `Paying ${username}@${host}`],
      ["text/identifier", `${username}@${host}`],
    ]);

    if (nostr) {
      try {
        let event = JSON.parse(decodeURIComponent(nostr));
        // TODO: validate the event
        await s(`zap:${id}`, event);
        metadata = nostr;
      } catch (e) {
        err("problem handling zap", e.message);
      }
    }

    let { text: pr } = await generate({
      invoice: {
        amount: Math.round(amount / 1000),
        type: types.lightning,
      },
      memo: metadata,
      user,
    });

    res.send({ pr, routes: [] });
  },
};