WIP Persist zaps, create and send zap receipts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
2024-05-09 14:31:37 +02:00
parent c0f4e7925e
commit c6c5d80fb4
17 changed files with 242 additions and 37 deletions

View File

@@ -1,7 +1,20 @@
FactoryBot.define do
factory :zap do
user { nil }
request { "" }
receipt { "" }
request {
Nostr::Event.new(
id: "3cf02d7f0ccd9711c25098fc50b3a7ab880326e4e51cc8c7a7b59f147cff4fff",
pubkey: "730b43e6f62c2ab22710b046e481802c8ac1108ed2cb9c21dff808d57ba24b6c",
created_at: 1712487443,
kind: 9734,
tags: [
["relays", "wss://nostr.kosmos.org", "wss://relay.example.com"],
["p", "07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3"]],
content: "",
sig: "e9e9bb2bac4267a107ab5c3368f504b4f11b8e3b5ae875a1d63c74d6934138d2521dc35815b6f534fc5d803cbf633736d871886368bb8f92c4ad3837a68a06f2"
).to_h
}
payment_request { "lnbc1u1p3mull3pp5qw4x46ew6kjknudypyjsg8maw935tr5kkuz7t6h7pugp3pt4msyqhp5zp40yd97a028sgr9x4yxq5d57gft6vwja58e8cl0eea4uasr6apscqzpgxqyz5vqsp53m2n8h6yeflgukv5fhwm802kur6un9w8nvycl7auk67w5g2u008q9qyyssqml8rfmxyvp32qd5939qx7uu0w6ppjuujlpwsrz28m9u0dzp799hz5j72w0xm8pg97hd4hdvwh9zxaw2hewnnmzewvc550f9y3qsfaegphmk0mu" }
receipt { nil }
end
end

View File

@@ -4,7 +4,7 @@
"user_login": "123456abcdef",
"amount": 21000,
"fee": 0,
"memo": "{\"id\":\"3cf02d7f0ccd9711c25098fc50b3a7ab880326e4e51cc8c7a7b59f147cff4fff\",\"sig\":\"e9e9bb2bac4267a107ab5c3368f504b4f11b8e3b5ae875a1d63c74d6934138d2521dc35815b6f534fc5d803cbf633736d871886368bb8f92c4ad3837a68a06f2\",\"pubkey\":\"730b43e6f62c2ab22710b046e481802c8ac1108ed2cb9c21dff808d57ba24b6c\",\"created_at\":1712487443,\"kind\":9734,\"tags\":[[\"relays\",\"wss://nostr.kosmos.org\",\"wss://relay.example.com\"],[\"p\",\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\"]],\"content\":\"\"}",
"memo": "Zap for satoshi@kosmos.org",
"description_hash": "b1a9910724bc9c1b03b4eba2e2d78ac69a8ac7b244e6ff6d4e7391bf6893f26a",
"payment_request": "lnbc1u1p3mull3pp5qw4x46ew6kjknudypyjsg8maw935tr5kkuz7t6h7pugp3pt4msyqhp5zp40yd97a028sgr9x4yxq5d57gft6vwja58e8cl0eea4uasr6apscqzpgxqyz5vqsp53m2n8h6yeflgukv5fhwm802kur6un9w8nvycl7auk67w5g2u008q9qyyssqml8rfmxyvp32qd5939qx7uu0w6ppjuujlpwsrz28m9u0dzp799hz5j72w0xm8pg97hd4hdvwh9zxaw2hewnnmzewvc550f9y3qsfaegphmk0mu",
"destination_pubkey_hex": "024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946",

12
spec/models/zap_spec.rb Normal file
View File

@@ -0,0 +1,12 @@
require 'rails_helper'
RSpec.describe Zap, type: :model do
describe "#request_event" do
let(:user) { create :user, cn: 'satoshi', ou: 'kosmos.org', ln_account: 'abcdefg123456' }
let(:zap) { create :zap, user: user }
it "returns the stored request as a Nostr::Event" do
expect(zap.request_event).to be_a(Nostr::Event)
end
end
end

View File

@@ -143,7 +143,7 @@ RSpec.describe "/lnurlpay", type: :request do
expect(LndhubManager::CreateUserInvoice).to receive(:call)
.with(user: user, payload: {
amount: 21,
description: event.to_json,
description: "Zap for satoshi@kosmos.org",
description_hash: "b1a9910724bc9c1b03b4eba2e2d78ac69a8ac7b244e6ff6d4e7391bf6893f26a"
})
.and_return(invoice)
@@ -156,7 +156,17 @@ RSpec.describe "/lnurlpay", type: :request do
res = JSON.parse(response.body)
expect(res["status"]).to eq('OK')
expect(res["pr"]).to eq("lnbc210n1pnzzyvjpp56map6jmxtpaty37hk8mkpre2a4uq5rx2qgwsngcz08pam8lcp7zshp5xs7v3qlx0j0gyu9grrzx9xgews3t9vq64v30579le9z9wqr6fc5scqzzsxqyz5vqsp5kmltj5eayh47c6trwj8wdrz5nxymqp0eqwtk7k5nk6ytyz522nvs9qyyssqvkluufkp34gtzxdg0uyqcsdum2n34xz94tqr4jfwwx53czteutvj7eptz4lm5vcu0m8jqzxck484ycxzcqgqlqmpj2r3jxjlj4x6nygp8fvnag")
expect(res["pr"]).to eq(invoice["payment_request"])
end
it "creates a zap record" do
get lnurlpay_invoice_path(username: "satoshi", params: {
amount: 21000, nostr: event.to_json
})
zap = user.zaps.find_by payment_request: invoice["payment_request"]
expect(zap.request_event.id).to eq(event.id)
expect(zap.receipt).to be_nil
end
end
end

View File

@@ -110,17 +110,48 @@ RSpec.describe "Webhooks", type: :request do
describe "Valid payload for zap transaction" do
let(:user) { create :user, ln_account: "123456abcdef" }
let(:zap) { create :zap, user: user }
let(:payload) { JSON.parse(File.read(File.expand_path("../fixtures/lndhub/incoming-zap.json", File.dirname(__FILE__)))) }
let(:zap_receipt) {
Nostr::Event.new(
id: "cb66278a9add37a2f1e018826327ff15304e8055ff7b910100225baf83a9d691",
sig: "a808d6792e21824bfddc98742b6831b1070e8b21e12aa424d2bb168a09f3a95a217d4513e803f2acb6e38404f763eb09fa07a341ee9c8c4c7d18bbe3d381eb6f",
pubkey: "bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf",
created_at: 1673428978,
kind: 9735,
tags: [
["p", "07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3"],
["bolt11", "lnbc1u1p3mull3pp5qw4x46ew6kjknudypyjsg8maw935tr5kkuz7t6h7pugp3pt4msyqhp5zp40yd97a028sgr9x4yxq5d57gft6vwja58e8cl0eea4uasr6apscqzpgxqyz5vqsp53m2n8h6yeflgukv5fhwm802kur6un9w8nvycl7auk67w5g2u008q9qyyssqml8rfmxyvp32qd5939qx7uu0w6ppjuujlpwsrz28m9u0dzp799hz5j72w0xm8pg97hd4hdvwh9zxaw2hewnnmzewvc550f9y3qsfaegphmk0mu"],
["preimage", "3539663535656537343331663432653165396430623966633664656664646563"],
["description", "{\"id\":\"3cf02d7f0ccd9711c25098fc50b3a7ab880326e4e51cc8c7a7b59f147cff4fff\",\"sig\":\"e9e9bb2bac4267a107ab5c3368f504b4f11b8e3b5ae875a1d63c74d6934138d2521dc35815b6f534fc5d803cbf633736d871886368bb8f92c4ad3837a68a06f2\",\"pubkey\":\"730b43e6f62c2ab22710b046e481802c8ac1108ed2cb9c21dff808d57ba24b6c\",\"created_at\":1712487443,\"kind\":9734,\"tags\":[[\"relays\",\"wss://nostr.kosmos.org\",\"wss://relay.example.com\"],[\"p\",\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\"]],\"content\":\"\"}"]
],
content: ""
)
}
before { user.save! } #FIXME this should not be necessary
before do
user.save!
zap.save!
allow(NostrManager::CreateZapReceipt).to receive(:call)
.and_return(zap_receipt)
allow(NostrManager::PublishZapReceipt).to receive(:call)
.and_return(true)
end
it "returns a 200 status" do
post "/webhooks/lndhub", params: payload.to_json
expect(response).to have_http_status(:ok)
end
it "sends a zap receipt" do
expect(NostrManager::PublishZapReceipt).to receive(:call)
it "creates and adds a zap receipt to the zap record" do
post "/webhooks/lndhub", params: payload.to_json
zap = user.zaps.first
expect(zap.receipt).not_to be_nil
end
it "publishes the zap receipt" do
expect(NostrManager::PublishZapReceipt).to receive(:call).with(zap: zap)
post "/webhooks/lndhub", params: payload.to_json
end
end

View File

@@ -0,0 +1,45 @@
require 'rails_helper'
RSpec.describe NostrManager::CreateZapReceipt, type: :model do
let(:user) { create :user, ln_account: "123456abcdef" }
let(:zap) { create :zap, user: user }
# before do
# user.save!
# zap.save!
# end
subject {
described_class.call(
zap: zap, paid_at: 1673428978,
preimage: "3539663535656537343331663432653165396430623966633664656664646563"
)
}
describe "Zap receipt" do
it "is a kind:9735 note" do
expect(subject).to be_a(Nostr::Event)
expect(subject.kind).to eq(9735)
end
it "sets created_at to when the invoice was paid" do
expect(subject.created_at).to eq(1673428978)
end
it "contains the zap recipient" do
expect(subject.tags.find{|t| t[0] == "p"}[1]).to eq("07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3")
end
it "contains the bolt11 invoice" do
expect(subject.tags.find{|t| t[0] == "bolt11"}[1]).to eq(zap.payment_request)
end
it "contains the invoice preimage" do
expect(subject.tags.find{|t| t[0] == "preimage"}[1]).to eq("3539663535656537343331663432653165396430623966633664656664646563")
end
it "contains the serialized zap request event as description" do
expect(subject.tags.find{|t| t[0] == "description"}[1]).to eq(zap.request_event.to_json)
end
end
end

View File

@@ -0,0 +1,16 @@
require 'rails_helper'
RSpec.describe NostrManager::PublishZapReceipt, type: :model do
let(:user) { create :user, ln_account: "123456abcdef" }
let(:zap) { create :zap, user: user }
describe "Default/delayed execution" do
it "publishes zap receipts to all requested relays" do
2.times do
expect(NostrPublishEventJob).to receive(:perform_later).and_return(true)
end
described_class.call(zap: zap)
end
end
end