From a50699322f5329a65428f7f7f3f9af84701026a1 Mon Sep 17 00:00:00 2001 From: Asara Date: Sat, 31 Aug 2024 14:10:32 -0400 Subject: [PATCH] feat: add lnurl with lightning.pub --- go.mod | 1 + lightningpub/well-known.go | 84 ++++++++++++++++++++++++++++++++ main.go | 7 ++- migrations/000002_lnurl.down.sql | 5 ++ migrations/000002_lnurl.up.sql | 29 +++++++++++ nostr/policies.go | 8 +-- 6 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 lightningpub/well-known.go create mode 100644 migrations/000002_lnurl.down.sql create mode 100644 migrations/000002_lnurl.up.sql diff --git a/go.mod b/go.mod index 262488c..6d6f5e9 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.0 require ( git.devvul.com/asara/gologger v0.8.0 + github.com/davecgh/go-spew v1.1.1 github.com/fiatjaf/eventstore v0.8.1 github.com/fiatjaf/khatru v0.8.0 github.com/jmoiron/sqlx v1.4.0 diff --git a/lightningpub/well-known.go b/lightningpub/well-known.go new file mode 100644 index 0000000..6dddaea --- /dev/null +++ b/lightningpub/well-known.go @@ -0,0 +1,84 @@ +package lightningpub + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + + "git.devvul.com/asara/gologger" + "git.devvul.com/asara/well-goknown/config" + "github.com/jmoiron/sqlx" +) + +var ( + DB *sqlx.DB +) + +type lnurlp struct { + Tag string `json:"tag"` + Callback string `json:"callback"` + MaxSendable int64 `json:"maxSendable"` + MinSendable int64 `json:"minSendable"` + Metadata string `json:"metadata"` + AllowsNostr bool `json:"allowsNostr"` + NostrPubkey string `json:"nostrPubkey"` +} + +func GetLnurlp(w http.ResponseWriter, r *http.Request) { + l := gologger.Get(config.GetConfig().LogLevel).With().Str("context", "lightningpub").Logger() + + // normalize domain + domain, _, err := net.SplitHostPort(r.Host) + if err != nil { + domain = r.Host + } + + name := r.PathValue("name") + var lnwallet string + err = DB.QueryRow("SELECT wallet FROM lnwallets WHERE name=$1 AND domain=$2", name, domain).Scan(&lnwallet) + if err != nil { + l.Debug().Msgf("user (%s@%s) doesn't exist: %s", name, domain, err.Error()) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + + //upstreamUrl := fmt.Sprintf("https://%s/api/guest/lnurl_pay/info?k1=%s", domain, lnwallet) + upstreamUrl := fmt.Sprintf("https://%s/api/guest/lnurl_pay/info?k1=%s", domain, lnwallet) + upstreamPayload, err := http.Get(upstreamUrl) + if err != nil { + l.Debug().Msgf("user (%s@%s) doesn't exist: %s", name, domain, err.Error()) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + + defer upstreamPayload.Body.Close() + body, err := ioutil.ReadAll(upstreamPayload.Body) + if err != nil { + l.Debug().Msgf("user (%s@%s) doesn't exist: %s", name, domain, err.Error()) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + + lnurlpReturn := lnurlp{} + err = json.Unmarshal(body, &lnurlpReturn) + if err != nil { + l.Debug().Msgf("user (%s@%s) doesn't exist: %s", name, domain, err.Error()) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + + ret, err := json.Marshal(lnurlpReturn) + if err != nil { + l.Debug().Msgf("user (%s@%s) doesn't exist: %s", name, domain, err.Error()) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + + l.Debug().Msgf("returning lnwallet for %s@%s", name, domain) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(ret) + return +} diff --git a/main.go b/main.go index 159f4ab..0dee870 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "git.devvul.com/asara/gologger" "git.devvul.com/asara/well-goknown/config" "git.devvul.com/asara/well-goknown/db" + "git.devvul.com/asara/well-goknown/lightningpub" "git.devvul.com/asara/well-goknown/matrix" "git.devvul.com/asara/well-goknown/nostr" "github.com/fiatjaf/eventstore/postgresql" @@ -20,12 +21,12 @@ func main() { db, _ := db.NewDB() defer db.Close() + lightningpub.DB = db nostr.DB = db nostr.RelayDb = postgresql.PostgresBackend{DatabaseURL: config.GetConfig().DbUrl} if err := nostr.RelayDb.Init(); err != nil { l.Panic().Msgf("unable to connect to relay db: %s", err.Error()) } - relay := nostr.NewRelay(Version) // matrix endpoints @@ -38,6 +39,10 @@ func main() { http.HandleFunc("/.well-known/nostr.json", nostr.GetNostrAddr) http.Handle("/relay", relay) + // lnurlp endpoint + l.Debug().Msg("enabling lnurlp well-known endpoint") + http.HandleFunc("/.well-known/lnurlp/{name}", lightningpub.GetLnurlp) + // start server port := config.GetConfig().ListenAddr l.Info().Msgf("starting server on %s", port) diff --git a/migrations/000002_lnurl.down.sql b/migrations/000002_lnurl.down.sql new file mode 100644 index 0000000..045d041 --- /dev/null +++ b/migrations/000002_lnurl.down.sql @@ -0,0 +1,5 @@ +BEGIN; +DROP TRIGGER IF EXISTS update_lnwallets_update_ts on lnwallets; +DROP FUNCTION IF EXISTS lnwallets_update_ts(); +DROP TABLE IF EXISTS lnwallets; +COMMIT; diff --git a/migrations/000002_lnurl.up.sql b/migrations/000002_lnurl.up.sql new file mode 100644 index 0000000..2515950 --- /dev/null +++ b/migrations/000002_lnurl.up.sql @@ -0,0 +1,29 @@ +BEGIN; +CREATE TABLE IF NOT EXISTS lnwallets ( + id SERIAL PRIMARY KEY, + owner_id INT NOT NULL, + name TEXT UNIQUE NOT NULL, + domain TEXT NOT NULL, + wallet TEXT NOT NULL, + create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT fk_owner FOREIGN KEY(owner_id) REFERENCES users(id), + CONSTRAINT ck_name CHECK (name ~ '^[a-z0-9]*$') +); + +CREATE FUNCTION lnwallets_update_ts() +RETURNS TRIGGER AS $$ +BEGIN + NEW.update_ts = now(); + RETURN NEW; +END; +$$ language 'plpgsql'; + +CREATE TRIGGER update_lnwallets_update_ts + BEFORE UPDATE + ON + lnwallets + FOR EACH ROW +EXECUTE PROCEDURE lnwallets_update_ts(); + +COMMIT; diff --git a/nostr/policies.go b/nostr/policies.go index a774e7e..bff4319 100644 --- a/nostr/policies.go +++ b/nostr/policies.go @@ -14,8 +14,8 @@ func RejectUnregisteredNpubs(ctx context.Context, event *nostr.Event) (reject bo var err error l := gologger.Get(config.GetConfig().LogLevel).With().Str("context", "nostr-reject-unregistered").Logger() - // always allow seals, lightning ephemeral messages, auth messages - if event.Kind == 13 || event.Kind == 21000 || event.Kind == 22242 || event.Kind == 30078 { + // always allow seals, lightning ephemeral messages, auth messages, addressable events + if event.Kind == 13 || event.Kind == 21000 || event.Kind == 22242 || event.Kind == 30078 || event.Kind == 1059 { return false, "" } @@ -27,8 +27,8 @@ func RejectUnregisteredNpubs(ctx context.Context, event *nostr.Event) (reject bo } npubs := []string{authenticatedUser} - // add recipients of dms (nip04)/private dms (nip17)/gift wraps (nip59) to npubs list - if event.Kind == 4 || event.Kind == 14 || event.Kind == 1059 { + // add recipients of dms/private dms/gift wraps/signature requests to npubs list + if event.Kind == 4 || event.Kind == 14 || event.Kind == 1059 || event.Kind == 24133 { for _, npub := range event.Tags.GetAll([]string{"p"}) { npubs = append(npubs, npub.Value()) }