Skip to content

Commit

Permalink
Cleanup zaps a bit.
Browse files Browse the repository at this point in the history
  • Loading branch information
unclebob committed Jul 4, 2023
1 parent f0ed4e9 commit 13445a9
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 130 deletions.
127 changes: 54 additions & 73 deletions spec/more_speech/nostr/zaps_spec.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
(ns more-speech.nostr.zaps-spec
(:require
[clj-http.client :as client]
[clojure.core.async :as async]
[clojure.data.json :as json]
[clojure.string :as string]
[more-speech.bech32 :as bech32]
[more-speech.config :as config]
[more-speech.db.gateway :as gateway]
Expand All @@ -16,7 +19,7 @@
[speclj.core :refer :all])
(:import (ecdhJava SECP256K1)))

(declare db)
(declare db wallet-response zap-descriptor)
(describe "zaps"
(with-stubs)
(setup-db-mem)
Expand Down Expand Up @@ -87,22 +90,24 @@
)

(context "zap request"
(with wallet-response {"status" "OK"
"reason" "reason"
"callback" "invoice-url"
"maxSendable" 1000000
"minSendable" 1000
"metadata" "metadata"
"tag" "payRequest"
"commentAllowed" 20
"allowsNostr" true
"nostrPubkey" "deadbeef"})
(with zap-descriptor {:zap-amount 1000
:zap-comment "12345678901234567890"
:id 1
:pubkey 99})

(it "makes a zap request if all is valid"
(with-redefs [util/get-now (stub :get-now {:return 11111})]
(let [wallet-response {"callback" "callback"
"maxSendable" 100
"minSendable" 1
"metadata" "metadata"
"tag" "payRequest"
"commentAllowed" 20
"allowsNostr" true
"nostrPubkey" "deadbeef"}
recipient-id 99
event-id 1
amount 100
comment "comment"
lnurl "lnurl"
b32-lnurl (bech32/encode-str "lnurl" lnurl)
(let [b32-lnurl (bech32/encode-str "lnurl" "lnurl")
my-privkey 0xb0b
my-pubkey (util/bytes->num (es/get-pub-key (util/num->bytes 32 my-privkey)))
_ (set-mem :pubkey my-pubkey)
Expand All @@ -111,84 +116,60 @@
_ (set-mem :relays {"relay-r1" {:read :read-all}
"relay-nr" {:read :read-none}
"relay-r2" {:read :read-all}})
body (zaps/make-zap-request
wallet-response recipient-id event-id amount comment lnurl)
body (zaps/make-zap-request-event
@wallet-response "lnurl" @zap-descriptor)
{:keys [kind content tags pubkey created_at]} body
tags (set tags)]

(should= 9734 kind)
(should= "comment" content)
(should= (:zap-comment @zap-descriptor) content)
(should= my-pubkey (util/unhexify pubkey))
(should= (util/get-now) created_at)
(should (contains? tags ["relays" "relay-r1" "relay-r2"]))
(should (contains? tags ["amount" "100"]))
(should (contains? tags ["amount" "1000"]))
(should (contains? tags ["lnurl" b32-lnurl]))
(should (contains? tags ["p" (util/hexify recipient-id)]))
(should (contains? tags ["e" (util/hexify event-id)])))))
(should (contains? tags ["p" (util/hexify (:pubkey @zap-descriptor))]))
(should (contains? tags ["e" (util/hexify (:id @zap-descriptor))])))))

(it "rejects if nostr is not allowed"
(let [wallet-response {"callback" "callback"
"maxSendable" 100
"minSendable" 1
"metadata" "metadata"
"tag" "payRequest"
"commentAllowed" 20
"allowsNostr" false
"nostrPubkey" "deadbeef"}
amount 100
comment "12345678901234567890"
lnurl "lnurl"]
(let [wallet-response (assoc @wallet-response "allowsNostr" false)]
(should-throw Exception "Recipient does not accept Nostr zaps."
(zaps/make-zap-request wallet-response
0 0 amount comment lnurl))))
(zaps/make-zap-request-event wallet-response "lnurl" @zap-descriptor))))

(it "rejects if amount too small"
(let [wallet-response {"callback" "callback"
"maxSendable" 1000000
"minSendable" 1000
"metadata" "metadata"
"tag" "payRequest"
"commentAllowed" 20
"allowsNostr" true
"nostrPubkey" "deadbeef"}
amount 100
comment "12345678901234567890"
lnurl "lnurl"]
(let [zap-descriptor (assoc @zap-descriptor :zap-amount 100)]
(should-throw Exception "Amount 0 is below minimum of 1"
(zaps/make-zap-request wallet-response
0 0 amount comment lnurl))))
(zaps/make-zap-request-event @wallet-response "lnurl" zap-descriptor))))

(it "rejects if amount too large"
(let [wallet-response {"callback" "callback"
"maxSendable" 1000000
"minSendable" 1000
"metadata" "metadata"
"tag" "payRequest"
"commentAllowed" 20
"allowsNostr" true
"nostrPubkey" "deadbeef"}
comment "12345678901234567890"
lnurl "lnurl"
amount 2000000]
(let [zap-descriptor (assoc @zap-descriptor :zap-amount 2000000)]
(should-throw Exception "Amount 2000 is larger than maximum of 1000"
(zaps/make-zap-request wallet-response
0 0 amount comment lnurl))))
(zaps/make-zap-request-event @wallet-response "lnurl" zap-descriptor))))

(it "rejects if comment too long"
(let [wallet-response {"callback" "callback"
"maxSendable" 1000000
"minSendable" 1000
"metadata" "metadata"
"tag" "payRequest"
"commentAllowed" 20
"allowsNostr" true
"nostrPubkey" "deadbeef"}
comment "123456789012345678901"
amount 1000
lnurl "lnurl"]
(let [zap-descriptor (assoc @zap-descriptor :zap-comment "123456789012345678901")]
(should-throw Exception "This wallet restricts comments to 20 characters"
(zaps/make-zap-request wallet-response
0 0 amount comment lnurl))))
(zaps/make-zap-request-event @wallet-response "lnurl" zap-descriptor))))

(it "gets the zap invoice"
(let [ln-response {:body (json/write-str @wallet-response)}
invoice-response {:status 200
:body (json/write-str {:status "OK"
:pr "invoice"})}
zap-descriptor {:zap-amount 1000
:zap-comment "comment"
:id 1
:pubkey 2
:auto-zap? true}
messages {"lnurl" ln-response
"invoice-url" invoice-response}
client-get (fn [url] (get messages (first (string/split url #"\?"))))]
(with-redefs [zaps/get-lnurl (stub :get-lnurl {:return "lnurl"})
client/get (stub :client-get {:invoke client-get})
es/do-sign (stub :do-sign {:return (util/num->bytes 64 99)})]
(should= "invoice" (zaps/get-zap-invoice zap-descriptor))
(should= {"invoice" {:id 1, :amount 1000, :comment "comment"}}
(get-mem :pending-zaps)))))
)
)

Expand Down
127 changes: 71 additions & 56 deletions src/more_speech/nostr/zaps.clj
Original file line number Diff line number Diff line change
Expand Up @@ -81,35 +81,40 @@
zap-address
(get-lnurl-from-profile (:pubkey zap-descriptor)))))

(defn make-zap-request [wallet-response recipient event-id amount comment lnurl]
(let [{:strs [maxSendable minSendable
commentAllowed allowsNostr]} wallet-response
_ (when-not allowsNostr
(throw (Exception. (str "Recipient does not accept Nostr zaps."))))
_ (when (< amount minSendable)
(throw (Exception. (str "Amount "
(quot amount 1000)
" is below minimum of "
(quot minSendable 1000)))))
_ (when (> amount maxSendable)
(throw (Exception. (str "Amount "
(quot amount 1000)
" is larger than maximum of "
(quot maxSendable 1000)))))
_ (when (and (some? comment)
(some? commentAllowed)
(> (count comment) commentAllowed))
(throw (Exception. (str "This wallet restricts comments to " commentAllowed " characters"))))
body {:kind 9734
:content comment
:tags [(concat ["relays"] (relays/relays-for-reading))
["amount" (str amount)]
["lnurl" (bech32/encode-str "lnurl" lnurl)]
["p" (util/hexify recipient)]
["e" (util/hexify event-id)]
]}
[_ request] (composers/body->event body)]
request))
(defn make-zap-request-event
([wallet-response lnurl zap-descriptor]
(let [{:keys [zap-amount zap-comment id pubkey]} zap-descriptor]
(make-zap-request-event wallet-response pubkey id zap-amount zap-comment lnurl)))

([wallet-response recipient event-id amount comment lnurl]
(let [{:strs [maxSendable minSendable
commentAllowed allowsNostr]} wallet-response
_ (when-not allowsNostr
(throw (Exception. (str "Recipient does not accept Nostr zaps."))))
_ (when (< amount minSendable)
(throw (Exception. (str "Amount "
(quot amount 1000)
" is below minimum of "
(quot minSendable 1000)))))
_ (when (> amount maxSendable)
(throw (Exception. (str "Amount "
(quot amount 1000)
" is larger than maximum of "
(quot maxSendable 1000)))))
_ (when (and (some? comment)
(some? commentAllowed)
(> (count comment) commentAllowed))
(throw (Exception. (str "This wallet restricts comments to " commentAllowed " characters"))))
body {:kind 9734
:content comment
:tags [(concat ["relays"] (relays/relays-for-reading))
["amount" (str amount)]
["lnurl" (bech32/encode-str "lnurl" lnurl)]
["p" (util/hexify recipient)]
["e" (util/hexify event-id)]
]}
[_ request] (composers/body->event body)]
request)))

(defn- ask-for-zap []
(let [zap-dialog
Expand All @@ -130,36 +135,46 @@
:cancel-fn (fn [_p] nil))]
(show! (pack! zap-dialog))))

(defn open-wallet [lnurl]
(let [ln-response (client/get lnurl)
wallet-response (json/read-str (:body ln-response))
{:strs [status reason]} wallet-response]
(when (and (some? status) (not= status "OK"))
(throw (Exception. (str "Wallet error: " status reason))))
wallet-response))

(defn request-invoice [wallet-response lnurl zap-descriptor]
(let [zap-request-event (make-zap-request-event wallet-response lnurl zap-descriptor)
json-request (events/to-json zap-request-event)
encoded-request (URLEncoder/encode ^String json-request "UTF-8")
callback (get wallet-response "callback")
invoice-request (str callback
"?amount=" (:zap-amount zap-descriptor)
"&nostr=" encoded-request
"&lnurl=" lnurl)]
(client/get invoice-request)))

(defn validate-invoice [invoice-response]
(let [invoice-response-status (:status invoice-response)
_ (when (not= 200 invoice-response-status)
(throw (Exception. (str "Invoice request failed:" invoice-response-status))))
invoice-json (json/read-str (:body invoice-response))
json-status (get invoice-json "status")
_ (when (and (some? json-status) (= "ERROR" json-status))
(throw (Exception. (str "Invoice request error: " (get invoice-json "reason")))))]
(get invoice-json "pr")))

(defn get-zap-invoice [zap-descriptor]
(try
(let [lnurl (get-lnurl zap-descriptor)
_ ('get-zap-invoice 'lnurl lnurl)
ln-response (client/get lnurl)
wallet-response (json/read-str (:body ln-response))
{:strs [callback status reason]} wallet-response]
(when (and (some? status) (not= status "OK"))
(throw (Exception. (str "Wallet error: " status reason))))
(let [{:keys [zap-amount zap-comment id pubkey]} zap-descriptor
zap-request (make-zap-request wallet-response pubkey id zap-amount zap-comment lnurl)
json-request (events/to-json zap-request)
encoded-request (URLEncoder/encode ^String json-request "UTF-8")
invoice-request (str callback
"?amount=" zap-amount
"&nostr=" encoded-request
"&lnurl=" lnurl)
invoice-response (client/get invoice-request)
invoice-response-status (:status invoice-response)
_ (when (not= 200 invoice-response-status)
(throw (Exception. (str "Invoice request failed:" invoice-response-status))))
invoice-json (json/read-str (:body invoice-response))
json-status (get invoice-json "status")
_ (when (and (some? json-status) (= "ERROR" json-status))
(throw (Exception. (str "Invoice request error: " (get invoice-json "reason")))))
invoice (get invoice-json "pr")]
(update-mem :pending-zaps assoc invoice {:id (:id zap-descriptor)
:amount zap-amount
:comment zap-comment})
invoice))
wallet-response (open-wallet lnurl)
invoice-response (request-invoice wallet-response lnurl zap-descriptor)
invoice (validate-invoice invoice-response)]
(update-mem :pending-zaps
assoc invoice {:id (:id zap-descriptor)
:amount (:zap-amount zap-descriptor)
:comment (:zap-comment zap-descriptor)})
invoice)
(catch Exception e
(when (not= "cancel" (.getMessage e))
(log-pr 2 'zap-author (.getMessage e))
Expand Down
2 changes: 1 addition & 1 deletion src/more_speech/types/profile.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
(s/def ::display-name string?)
(s/def ::website string?)
(s/def ::created-at number?)
(s/def ::wallet-connect string?)
(s/def ::wallet-connect (s/or :nil nil? :string string?))
(s/def ::password string?)
(s/def ::profile (s/keys :req-un [::name
::about
Expand Down

0 comments on commit 13445a9

Please sign in to comment.