From c7fe1bc3bca5cb3776cb7b30291ab0f28608d308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sun, 19 Feb 2023 17:45:27 +0800 Subject: [PATCH 1/6] Add keysend support for Lightning Address Allow keysend payments to user addresses. Useful for Podcasting 2.0/v4v. --- .env.example | 1 + app/controllers/lnurlpay_controller.rb | 20 ++++++++++++++++++++ app/models/setting.rb | 21 +++++++++++++++++++-- config/routes.rb | 1 + 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index c63d0bb..48b0fec 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,7 @@ BTCPAY_API_URL='http://localhost:23001/api/v1' LNDHUB_API_URL='http://localhost:3023' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' +LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' LNDHUB_ADMIN_UI=true LNDHUB_PG_HOST=localhost LNDHUB_PG_PORT=5432 diff --git a/app/controllers/lnurlpay_controller.rb b/app/controllers/lnurlpay_controller.rb index b1326b1..f880151 100644 --- a/app/controllers/lnurlpay_controller.rb +++ b/app/controllers/lnurlpay_controller.rb @@ -1,4 +1,5 @@ class LnurlpayController < ApplicationController + before_action :check_feature_enabled before_action :find_user_by_address MIN_SATS = 10 @@ -17,6 +18,20 @@ class LnurlpayController < ApplicationController } end + def keysend + http_status :not_found unless Setting.lndhub_keysend_enabled? + + render json: { + status: "OK", + tag: "keysend", + pubkey: Setting.lndhub_public_key, + customData: [{ + customKey: "696969", + customValue: @user.ln_account + }] + } + end + def invoice amount = params[:amount].to_i / 1000 # msats address = params[:address] @@ -72,4 +87,9 @@ class LnurlpayController < ApplicationController comment.length <= MAX_COMMENT_CHARS end + private + + def check_feature_enabled + http_status :not_found if !Setting.lndhub_enabled? + end end diff --git a/app/models/setting.rb b/app/models/setting.rb index fb4dae5..d85b82e 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -2,10 +2,27 @@ class Setting < RailsSettings::Base cache_prefix { "v1" } + # + # Registrations + # + field :reserved_usernames, type: :array, default: %w[ account accounts admin donations mail webmaster support ] - field :lndhub_enabled, default: (ENV["LNDHUB_API_URL"].present?.to_s || "false"), type: :boolean - field :lndhub_admin_enabled, default: (ENV["LNDHUB_ADMIN_UI"] || "false"), type: :boolean + # + # Lightning Network + # + + field :lndhub_enabled, type: :boolean, + default: (ENV["LNDHUB_API_URL"].present?.to_s || false) + + field :lndhub_admin_enabled, type: :boolean, + default: (ENV["LNDHUB_ADMIN_UI"] || false) + + field :lndhub_public_key, type: :string, + default: (ENV["LNDHUB_PUBLIC_KEY"] || "") + + field :lndhub_keysend_enabled, type: :boolean, + default: -> { self.lndhub_public_key.present?.to_s || false } end diff --git a/config/routes.rb b/config/routes.rb index 7559393..fd8a40d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,6 +30,7 @@ Rails.application.routes.draw do get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/} get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/} + get 'keysend/:address', to: 'lnurlpay#keysend', constraints: { address: /[^\/]+/} post 'webhooks/lndhub', to: 'webhooks#lndhub' From 68ab88c481791ea4dffb39b5984ee4f4815c87e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 23 Feb 2023 17:46:19 +0800 Subject: [PATCH 2/6] Add names for lnurl routes --- config/routes.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index fd8a40d..cef51b7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -28,9 +28,12 @@ Rails.application.routes.draw do get 'wallet', to: 'wallet#index' get 'wallet/transactions', to: 'wallet#transactions' - get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/} - get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/} - get 'keysend/:address', to: 'lnurlpay#keysend', constraints: { address: /[^\/]+/} + get 'lnurlpay/:address', to: 'lnurlpay#index', + as: 'lightning_address', constraints: { address: /[^\/]+/} + get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', + as: 'lnurlpay_invoice', constraints: { address: /[^\/]+/} + get 'keysend/:address', to: 'lnurlpay#keysend', + as: 'lightning_address_keysend', constraints: { address: /[^\/]+/} post 'webhooks/lndhub', to: 'webhooks#lndhub' From e580cc9991ddfd5826323e295dc1421cec2c5786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 23 Feb 2023 17:46:36 +0800 Subject: [PATCH 3/6] Add specs for Lightning Address and lnurlpay requests --- spec/requests/lnurlpay_spec.rb | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 spec/requests/lnurlpay_spec.rb diff --git a/spec/requests/lnurlpay_spec.rb b/spec/requests/lnurlpay_spec.rb new file mode 100644 index 0000000..33ac543 --- /dev/null +++ b/spec/requests/lnurlpay_spec.rb @@ -0,0 +1,110 @@ +require 'rails_helper' + +RSpec.describe "/lnurlpay", type: :request do + + context "Non-existent user/address" do + describe "GET /lnurlpay/:address" do + it "returns a 404" do + get lightning_address_path(address: "csw@kosmos.org") + expect(response).to have_http_status(:not_found) + end + end + + describe "GET /lnurlpay/:address/invoice" do + it "returns a 404" do + get lnurlpay_invoice_path(address: "csw@kosmos.org", params: { amount: 5000 }) + expect(response).to have_http_status(:not_found) + end + end + + describe "GET /keysend/:address/" do + it "returns a 404" do + get lightning_address_keysend_path(address: "csw@kosmos.org") + expect(response).to have_http_status(:not_found) + end + end + end + + context "Valid user/address" do + let(:user) { create :user, cn: 'satoshi', ou: 'kosmos.org', ln_account: 'abcdefg123456' } + + before do + login_as user, :scope => :user + end + + describe "GET /lnurlpay/:address" do + it "returns a formatted Lightning Address response" do + get lightning_address_path(address: "satoshi@kosmos.org") + + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["status"]).to eq('OK') + expect(res["tag"]).to eq('payRequest') + expect(res["callback"]).to match(lnurlpay_invoice_path('satoshi@kosmos.org')) + expect(res["minSendable"]).to be_a(Integer) + expect(res["maxSendable"]).to be_a(Integer) + expect(res["commentAllowed"]).to be_a(Integer) + end + end + + describe "GET /lnurlpay/:address/invoice" do + before do + allow_any_instance_of(User).to receive(:ln_create_invoice).and_return("lnbc50u1p3lwgknpp52g78gqya5euvzjc53fc6hkmlm2rfjhcd305tcmc0g9gaestav48sdq4gdhkven9v5sx6mmwv4ujzcqzpgxqyz5vqsp5skkz4jlqr6tkvv2g9739ygrjupc4rkqd94mc7dfpj3pgx3f6w7qs9qyyssq7mf3fzcuxlmkr9nqatcch3u8uf4gjyawe052tejz8e9fqxu4pncqk3qklt8g6ylpshg09xyjquyrgtc72vcw5cp0dzcf406apyua7dgpnfn7an") + end + + it "returns a formatted lnurlpay response" do + get lnurlpay_invoice_path(address: "satoshi@kosmos.org", params: { + amount: 50000, comment: "Coffee time!" + }) + + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["status"]).to eq('OK') + expect(res["successAction"]["tag"]).to eq('message') + expect(res["successAction"]["message"]).to match('Thank you') + expect(res["pr"]).to eq("lnbc50u1p3lwgknpp52g78gqya5euvzjc53fc6hkmlm2rfjhcd305tcmc0g9gaestav48sdq4gdhkven9v5sx6mmwv4ujzcqzpgxqyz5vqsp5skkz4jlqr6tkvv2g9739ygrjupc4rkqd94mc7dfpj3pgx3f6w7qs9qyyssq7mf3fzcuxlmkr9nqatcch3u8uf4gjyawe052tejz8e9fqxu4pncqk3qklt8g6ylpshg09xyjquyrgtc72vcw5cp0dzcf406apyua7dgpnfn7an") + end + + context "amount too low" do + it "retuns an error" do + get lnurlpay_invoice_path(address: "satoshi@kosmos.org", params: { + amount: 5000, comment: "Coffee time!" + }) + expect(response).to have_http_status(:ok) + res = JSON.parse(response.body) + expect(res["status"]).to eq('ERROR') + expect(res["reason"]).to eq('Invalid amount') + end + end + + context "comment too long" do + it "retuns an error" do + get lnurlpay_invoice_path(address: "satoshi@kosmos.org", params: { + amount: 5000000, comment: "Coffee time is the best time, so here's some money for you to get some. May I suggest to sample some Pacamara beans from El Salvador?" + }) + expect(response).to have_http_status(:ok) + res = JSON.parse(response.body) + expect(res["status"]).to eq('ERROR') + expect(res["reason"]).to eq('Comment too long') + end + end + end + + describe "GET /keysend/:address/" do + it "returns a formatted Lightning Address keysend response" do + get lightning_address_keysend_path(address: "satoshi@kosmos.org") + + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["status"]).to eq('OK') + expect(res["tag"]).to eq('keysend') + expect(res["pubkey"]).to eq(Setting.lndhub_public_key) + expect(res["customData"][0]["customKey"]).to eq('696969') + expect(res["customData"][0]["customValue"]).to eq('abcdefg123456') + end + end + end +end From b87b9c2437211b2222f27536b2be97c89dfee915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 23 Feb 2023 17:54:34 +0800 Subject: [PATCH 4/6] Prevent double render --- app/controllers/lnurlpay_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/lnurlpay_controller.rb b/app/controllers/lnurlpay_controller.rb index f880151..92a846a 100644 --- a/app/controllers/lnurlpay_controller.rb +++ b/app/controllers/lnurlpay_controller.rb @@ -19,7 +19,7 @@ class LnurlpayController < ApplicationController end def keysend - http_status :not_found unless Setting.lndhub_keysend_enabled? + http_status :not_found and return unless Setting.lndhub_keysend_enabled? render json: { status: "OK", @@ -90,6 +90,6 @@ class LnurlpayController < ApplicationController private def check_feature_enabled - http_status :not_found if !Setting.lndhub_enabled? + http_status :not_found unless Setting.lndhub_enabled? end end From dc635061026f1c0888324bb89e109823822f5e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 23 Feb 2023 17:56:38 +0800 Subject: [PATCH 5/6] Add ln node public key to test env --- .env.test | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.test b/.env.test index 03daa76..d939774 100644 --- a/.env.test +++ b/.env.test @@ -5,5 +5,6 @@ BTCPAY_API_URL='http://btcpay.example.com/api/v1' LNDHUB_LEGACY_API_URL='http://localhost:3023' LNDHUB_API_URL='http://localhost:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' +LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' WEBHOOKS_ALLOWED_IPS='10.1.1.23' From f2c7aa2f09a6010b7487e0bbbb620a0eff7495cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 3 Mar 2023 21:27:18 +0800 Subject: [PATCH 6/6] Fix typos --- spec/requests/lnurlpay_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/lnurlpay_spec.rb b/spec/requests/lnurlpay_spec.rb index 33ac543..e36278e 100644 --- a/spec/requests/lnurlpay_spec.rb +++ b/spec/requests/lnurlpay_spec.rb @@ -68,7 +68,7 @@ RSpec.describe "/lnurlpay", type: :request do end context "amount too low" do - it "retuns an error" do + it "returns an error" do get lnurlpay_invoice_path(address: "satoshi@kosmos.org", params: { amount: 5000, comment: "Coffee time!" }) @@ -80,7 +80,7 @@ RSpec.describe "/lnurlpay", type: :request do end context "comment too long" do - it "retuns an error" do + it "returns an error" do get lnurlpay_invoice_path(address: "satoshi@kosmos.org", params: { amount: 5000000, comment: "Coffee time is the best time, so here's some money for you to get some. May I suggest to sample some Pacamara beans from El Salvador?" })