aboutsummaryrefslogtreecommitdiffstats
path: root/lib/payments.ts
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-03 21:26:57 -0800
committerAdam Soltys <[email protected]>2026-03-03 21:26:57 -0800
commit9507e040a03834f7d603c5c13a1f69ae6ef48b3a (patch)
treec82658ef1110105f1ac3f056a462b7e251db657d /lib/payments.ts
parent82f7ae5f84da70e8c5692ff4fffa7263784a7800 (diff)
downloadcoinos-server-9507e040a03834f7d603c5c13a1f69ae6ef48b3a.tar.gz
coinos-server-9507e040a03834f7d603c5c13a1f69ae6ef48b3a.zip
bip353
Diffstat (limited to 'lib/payments.ts')
-rw-r--r--lib/payments.ts35
1 files changed, 34 insertions, 1 deletions
diff --git a/lib/payments.ts b/lib/payments.ts
index 47c82d9d..b3c63cfa 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -63,6 +63,22 @@ const { URL } = process.env;
const dust = 547;
+const resolveBip353 = async (name: string, domain: string) => {
+ const qname = `${name}.user._bitcoin-payment.${domain}`;
+ const res = await fetch(
+ `https://1.1.1.1/dns-query?name=${qname}&type=TXT`,
+ { headers: { accept: "application/dns-json" } },
+ );
+ const data = await res.json();
+ if (!data.AD) return null; // DNSSEC validation failed
+ if (!data.Answer?.length) return null;
+ for (const ans of data.Answer) {
+ const txt = ans.data?.replace(/^"|"$/g, "").replaceAll('" "', "");
+ if (txt?.startsWith("bitcoin:")) return txt;
+ }
+ return null;
+};
+
export const getUserRate = async (user) => {
const rates = await g("rates");
const { currency } = user;
@@ -478,7 +494,24 @@ const pay = async ({ aid = undefined, amount, to, user }) => {
if (to.includes("@") && to.includes(".")) {
const [name, domain] = to.split("@");
if (URL.includes(domain)) to = name;
- lnurl = `https://${domain}/.well-known/lnurlp/${name}`;
+ else {
+ try {
+ const uri = await resolveBip353(name, domain);
+ if (uri) {
+ const params = new URLSearchParams(uri.split("?")[1]);
+ if (params.has("lno")) to = params.get("lno");
+ else if (params.has("sp")) to = params.get("sp");
+ else if (params.has("lightning")) to = params.get("lightning");
+ else {
+ const addr = uri.replace("bitcoin:", "").split("?")[0];
+ if (addr) to = addr;
+ }
+ }
+ } catch (e) {
+ warn("BIP 353 resolution failed, falling back to LNURL", e.message);
+ }
+ if (to.includes("@")) lnurl = `https://${domain}/.well-known/lnurlp/${name}`;
+ }
} else if (to.startsWith("lnurl")) {
lnurl = Buffer.from(bech32.fromWords(bech32.decode(to, 20000).words)).toString();
}