-
Notifications
You must be signed in to change notification settings - Fork 196
/
Copy pathlnurl.ts
144 lines (124 loc) · 4.21 KB
/
lnurl.ts
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import fetchAdapter from "@vespaiach/axios-fetch-adapter";
import axios from "axios";
import lightningPayReq from "bolt11-signet";
import { isLNURLDetailsError } from "~/common/utils/typeHelpers";
import {
LNURLAuthServiceResponse,
LNURLDetails,
LNURLError,
LNURLPaymentInfo,
} from "~/types";
import { bech32Decode } from "../utils/helpers";
const fromInternetIdentifier = (address: string) => {
// email regex: https://emailregex.com/
// modified to allow _ in subdomains
if (
address.match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-_0-9]+\.)+[a-zA-Z]{2,}))$/
)
) {
let [name, host] = address.split("@");
// remove invisible characters %EF%B8%8F
name = name.replace(/[^ -~]+/g, "");
host = host.replace(/[^ -~]+/g, "");
return `https://${host}/.well-known/lnurlp/${name}`;
}
return null;
};
const normalizeLnurl = (lnurlString: string) => {
// maybe it's bech32 encoded?
try {
const url = bech32Decode(lnurlString);
return new URL(url);
} catch (e) {
console.info("ignoring bech32 parsing error", e);
}
// maybe it's a lightning address?
const urlFromAddress = fromInternetIdentifier(lnurlString);
if (urlFromAddress) {
return new URL(urlFromAddress);
}
//maybe it's already a URL?
return new URL(`https://${lnurlString.replace(/^lnurl[pwc]/i, "")}`);
};
const lnurl = {
isLightningAddress(address: string) {
return Boolean(fromInternetIdentifier(address));
},
findLnurl(text: string) {
const stringToText = text.trim();
let match;
// look for a LNURL with protocol scheme
if ((match = stringToText.match(/lnurl[pwc]:(\S+)/i))) {
return match[1];
}
// look for LNURL bech32 in the string
if ((match = stringToText.match(/(lnurl[a-zA-HJ-NP-Z0-9]+)/i))) {
return match[1];
}
return null;
},
async getDetails(lnurlString: string): Promise<LNURLError | LNURLDetails> {
const url = normalizeLnurl(lnurlString);
const searchParamsTag = url.searchParams.get("tag");
const searchParamsK1 = url.searchParams.get("k1");
const searchParamsAction = url.searchParams.get("action");
if (searchParamsTag && searchParamsTag === "login" && searchParamsK1) {
const lnurlAuthDetails: LNURLAuthServiceResponse = {
...(searchParamsAction && { action: searchParamsAction }),
domain: url.hostname,
k1: searchParamsK1,
tag: searchParamsTag,
url: url.toString(),
};
return lnurlAuthDetails;
} else {
try {
const { data }: { data: LNURLDetails | LNURLError } = await axios.get(
url.toString(),
{
adapter: fetchAdapter,
}
);
const lnurlDetails = data;
if (isLNURLDetailsError(lnurlDetails)) {
throw new Error(lnurlDetails.reason);
} else {
lnurlDetails.domain = url.hostname;
lnurlDetails.url = url.toString();
}
return lnurlDetails;
} catch (e) {
let error = "";
if (axios.isAxiosError(e)) {
error =
(e.response?.data as { reason?: string })?.reason || e.message;
if (this.isLightningAddress(lnurlString)) {
error = `This is not a valid lightning address. Either the address is invalid or it is using a different and unsupported protocol: ${error}`;
}
} else if (e instanceof Error) {
error = e.message;
}
throw new Error(error);
}
}
},
verifyInvoice({
paymentInfo,
amount,
}: {
paymentInfo: LNURLPaymentInfo;
amount: number;
}) {
const paymentRequestDetails = lightningPayReq.decode(paymentInfo.pr);
switch (true) {
case paymentRequestDetails.millisatoshis !== String(amount): // LN WALLET Verifies that amount in provided invoice equals an amount previously specified by user
case paymentInfo.successAction &&
!["url", "message", "aes"].includes(paymentInfo.successAction.tag): // If successAction is not null: LN WALLET makes sure that tag value of is of supported type, aborts a payment otherwise
return false;
default:
return true;
}
},
};
export default lnurl;