From a2ca5ade685e9a7dc57c9fa226858769cacca728 Mon Sep 17 00:00:00 2001 From: michael1011 Date: Wed, 25 Sep 2024 14:21:54 +0900 Subject: [PATCH] feat: BIP-353 resolver --- src/components/CreateButton.tsx | 49 +++++++++++++++++++++++++-------- src/config.ts | 1 + src/utils/invoice.ts | 28 +++++++++++++++++++ 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/components/CreateButton.tsx b/src/components/CreateButton.tsx index ad784ca8..1664bf66 100644 --- a/src/components/CreateButton.tsx +++ b/src/components/CreateButton.tsx @@ -14,7 +14,7 @@ import { fetchBolt12Invoice, getPairs } from "../utils/boltzClient"; import { formatAmount } from "../utils/denomination"; import { formatError } from "../utils/errors"; import { coalesceLn } from "../utils/helper"; -import { fetchLnurl } from "../utils/invoice"; +import { fetchBip353, fetchLnurl } from "../utils/invoice"; import { SomeSwap, createChain, @@ -153,17 +153,42 @@ export const CreateButton = () => { const create = async () => { if (validWayToFetchInvoice()) { if (lnurl() !== undefined && lnurl() !== "") { - log.info("Fetching invoice from lnurl", lnurl()); - try { - const inv = await fetchLnurl( - lnurl(), - Number(receiveAmount()), - ); - setInvoice(inv); + log.info("Fetching invoice from LNURL or BIP-353", lnurl()); + + const fetchResults = await Promise.allSettled([ + (() => { + try { + return fetchLnurl(lnurl(), Number(receiveAmount())); + } catch (e) { + log.warn("Fetching invoice from LNURL failed", e); + throw e; + } + })(), + (() => { + try { + return fetchBip353( + lnurl(), + Number(receiveAmount()), + ); + } catch (e) { + log.warn("Fetching invoice from BIP-353 failed", e); + throw e; + } + })(), + ]); + + const fetched = fetchResults.find( + (res) => res.status === "fulfilled", + ); + if (fetched !== undefined) { + setInvoice(fetched.value); setLnurl(""); - } catch (e) { - notify("error", e.message); - log.warn("fetch lnurl failed", e); + } else { + // All failed, so we can safely cast the first one + notify( + "error", + (fetchResults[0] as PromiseRejectedResult).reason, + ); return; } } else { @@ -177,7 +202,7 @@ export const CreateButton = () => { setBolt12Offer(undefined); } catch (e) { notify("error", formatError(e)); - log.warn("Fetching invoice from bol12 failed", e); + log.warn("Fetching invoice from bolt12 failed", e); return; } } diff --git a/src/config.ts b/src/config.ts index 1ce8410e..b88832cc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -23,6 +23,7 @@ const defaults = { testnetUrl: "https://testnet.boltz.exchange", telegramUrl: "https://t.me/boltzhq", email: "hi@bol.tz", + dnsOverHttps: "https://1.1.1.1/dns-query", }; type Asset = { diff --git a/src/utils/invoice.ts b/src/utils/invoice.ts index c918a161..a4e71e7b 100644 --- a/src/utils/invoice.ts +++ b/src/utils/invoice.ts @@ -5,6 +5,7 @@ import { Invoice, Offer } from "boltz-bolt12"; import log from "loglevel"; import { config } from "../config"; +import { fetchBolt12Invoice } from "./boltzClient"; import { checkResponse } from "./http"; type LnurlResponse = { @@ -104,6 +105,33 @@ export const fetchLnurl = ( }); }; +export const fetchBip353 = async ( + hrn: string, + amountSat: number, +): Promise => { + const split = hrn.split("@"); + if (split.length !== 2) { + throw "invalid BIP-353"; + } + + log.debug(`Fetching BIP-353: ${hrn}`); + + const params = new URLSearchParams({ + type: "TXT", + name: `${split[0]}.user._bitcoin-payment.${split[1]}`, + }); + const res = await fetch(`${config.dnsOverHttps}?${params.toString()}`, { + headers: { + Accept: "application/dns-json", + }, + }); + const resBody = await res.json(); + const paymentRequest = resBody.Answer[0].data; + const offer = new URLSearchParams(paymentRequest.split("?")[1]).get("lno"); + return (await fetchBolt12Invoice(offer.replaceAll('"', ""), amountSat)) + .invoice; +}; + const checkLnurlResponse = (amount: number, data: LnurlResponse) => { log.debug( "amount check: (x, min, max)",