lighstorm 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 643a80fa54f053a2f647dbeb4df8b959cb0d512a7aa220c338c136220775c960
4
- data.tar.gz: 5fd770e41ad436cc920c4c3723eeddc4027feb0bb9a3ace19f3856e5f516b948
3
+ metadata.gz: 551ffb5be38f96d6c0bb4f7b8fbde5763c623f0d100f0a1b34acb90a6074fb23
4
+ data.tar.gz: 59451d54f2ad6d45c95c7649ab0a048a4adeb7a15f1af08f133d8eaae107d9bc
5
5
  SHA512:
6
- metadata.gz: 403f808bf48235a765f8c50fb840cddd86c0c77ece348b3b907460c8f84cb932c91fc688ac8e7f488ced08974f1acabd0e5019661de6bdd0956178b657249ac7
7
- data.tar.gz: 4dbbb229dbe1204dc189d179b2106e55ad45d3ed36f28ded6206674400aeac80595fd45f52f755bd118f90e6e14bfff5f4fc7e096a8b2dfab1bfa94da4ddc430
6
+ metadata.gz: 54e1e6929a1f5dc67f319461d6fc3799dd5cd5c349224c19198a6bffd203bd3e6431fb6bf571976c2a1c0d3d1d07ceb9cb272206f06794c52595c4ec8d5d57d3
7
+ data.tar.gz: a903325535c6c2e98b95c4e54cb51f7bbd7123b40dfb2b9143fc9d7a392290230f1bb026c7bd30a669c8f981286ef56acfe0a2bf3751ea9cce5a4d663e160d61
data/.env.example CHANGED
@@ -3,3 +3,4 @@ LIGHSTORM_CERTIFICATE_PATH=/lnd/tls.cert
3
3
  LIGHSTORM_MACAROON_PATH=/lnd/data/chain/bitcoin/mainnet/admin.macaroon
4
4
  LIGHSTORM_RUN_INTEGRATION_TESTS=false
5
5
  LIGHSTORM_RUN_INTEGRATION_TESTS_SLOW=false
6
+ LIGHSTORM_DELETE_UNUSED_TEST_DATA=false
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  *.gem
2
2
  .env
3
+ tags
data/.rubocop.yml CHANGED
@@ -22,3 +22,4 @@ Security/MarshalLoad:
22
22
 
23
23
  require:
24
24
  - rubocop-rspec
25
+ - rubocop-rake
data/Gemfile CHANGED
@@ -6,9 +6,11 @@ gemspec
6
6
 
7
7
  group :test, :development do
8
8
  gem 'babosa', '~> 2.0'
9
- gem 'dotenv', '~> 2.8', '>= 2.8.1'
10
9
  gem 'pry-byebug', '~> 3.10', '>= 3.10.1'
10
+ gem 'rainbow', '~> 3.1', '>= 3.1.1'
11
+ gem 'rake', '~> 13.0', '>= 13.0.6'
11
12
  gem 'rspec', '~> 3.12'
12
- gem 'rubocop', '~> 1.47'
13
- gem 'rubocop-rspec', '~> 2.18', '>= 2.18.1'
13
+ gem 'rubocop', '~> 1.48'
14
+ gem 'rubocop-rake', '~> 0.6.0'
15
+ gem 'rubocop-rspec', '~> 2.19'
14
16
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lighstorm (0.0.8)
4
+ lighstorm (0.0.9)
5
5
  dotenv (~> 2.8, >= 2.8.1)
6
6
  lnd-client (~> 0.0.5)
7
7
  zache (~> 0.12.0)
@@ -15,7 +15,7 @@ GEM
15
15
  coderay (1.1.3)
16
16
  diff-lcs (1.5.0)
17
17
  dotenv (2.8.1)
18
- google-protobuf (3.22.0-x86_64-linux)
18
+ google-protobuf (3.22.2-x86_64-linux)
19
19
  googleapis-common-protos-types (1.5.0)
20
20
  google-protobuf (~> 3.14)
21
21
  grpc (1.52.0-x86_64-linux)
@@ -26,7 +26,7 @@ GEM
26
26
  grpc (~> 1.52)
27
27
  method_source (1.0.0)
28
28
  parallel (1.22.1)
29
- parser (3.2.1.0)
29
+ parser (3.2.1.1)
30
30
  ast (~> 2.4.1)
31
31
  pry (0.14.2)
32
32
  coderay (~> 1.1)
@@ -35,6 +35,7 @@ GEM
35
35
  byebug (~> 11.0)
36
36
  pry (>= 0.13, < 0.15)
37
37
  rainbow (3.1.1)
38
+ rake (13.0.6)
38
39
  regexp_parser (2.7.0)
39
40
  rexml (3.2.5)
40
41
  rspec (3.12.0)
@@ -50,7 +51,7 @@ GEM
50
51
  diff-lcs (>= 1.2.0, < 2.0)
51
52
  rspec-support (~> 3.12.0)
52
53
  rspec-support (3.12.0)
53
- rubocop (1.47.0)
54
+ rubocop (1.48.0)
54
55
  json (~> 2.3)
55
56
  parallel (~> 1.10)
56
57
  parser (>= 3.2.0.0)
@@ -64,7 +65,9 @@ GEM
64
65
  parser (>= 3.2.1.0)
65
66
  rubocop-capybara (2.17.1)
66
67
  rubocop (~> 1.41)
67
- rubocop-rspec (2.18.1)
68
+ rubocop-rake (0.6.0)
69
+ rubocop (~> 1.0)
70
+ rubocop-rspec (2.19.0)
68
71
  rubocop (~> 1.33)
69
72
  rubocop-capybara (~> 2.17)
70
73
  ruby-progressbar (1.13.0)
@@ -76,12 +79,14 @@ PLATFORMS
76
79
 
77
80
  DEPENDENCIES
78
81
  babosa (~> 2.0)
79
- dotenv (~> 2.8, >= 2.8.1)
80
82
  lighstorm!
81
83
  pry-byebug (~> 3.10, >= 3.10.1)
84
+ rainbow (~> 3.1, >= 3.1.1)
85
+ rake (~> 13.0, >= 13.0.6)
82
86
  rspec (~> 3.12)
83
- rubocop (~> 1.47)
84
- rubocop-rspec (~> 2.18, >= 2.18.1)
87
+ rubocop (~> 1.48)
88
+ rubocop-rake (~> 0.6.0)
89
+ rubocop-rspec (~> 2.19)
85
90
 
86
91
  BUNDLED WITH
87
92
  2.4.4
data/README.md CHANGED
@@ -34,7 +34,7 @@ Although it tries to stay close to [Lightning's terminologies](https://docs.ligh
34
34
  Add to your `Gemfile`:
35
35
 
36
36
  ```ruby
37
- gem 'lighstorm', '~> 0.0.8'
37
+ gem 'lighstorm', '~> 0.0.9'
38
38
  ```
39
39
 
40
40
  ```ruby
@@ -46,12 +46,13 @@ Lighstorm.config!(
46
46
  macaroon_path: '/lnd/data/chain/bitcoin/mainnet/admin.macaroon',
47
47
  )
48
48
 
49
- puts Lighstorm.version # => 0.0.8
49
+ puts Lighstorm.version # => 0.0.9
50
50
 
51
51
  Lighstorm::Node.myself.alias # => icebaker/old-stone
52
52
 
53
53
  Lighstorm::Invoice.create(
54
- description: 'Coffee', millisatoshis: 1_000
54
+ description: 'Coffee', millisatoshis: 1_000,
55
+ payable: 'once'
55
56
  )
56
57
 
57
58
  Lighstorm::Satoshis.new(
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './spec/helpers/tasks/contracts'
4
+
5
+ # rspec --format json | bundle exec rake contracts:fix
6
+ namespace :contracts do
7
+ desc 'Fix Contract Tests Hash'
8
+ task :fix do
9
+ Tasks::Contracts.fix($stdin.gets)
10
+ end
11
+ end
@@ -3,27 +3,38 @@
3
3
  module Lighstorm
4
4
  module Adapter
5
5
  class Purpose
6
+ def self.send_payment_v2(grpc, node_get_info)
7
+ return 'unknown' if grpc[:payment_route][:hops].empty?
8
+
9
+ return 'self-payment' if self_payment?(grpc[:payment_route][:hops])
10
+ return 'peer-to-peer' if peer_to_peer?(grpc[:payment_route][:hops])
11
+ return 'rebalance' if rebalance?(grpc[:payment_route][:hops], node_get_info)
12
+
13
+ 'payment'
14
+ end
15
+
6
16
  def self.list_payments(grpc, node_get_info)
7
- return 'self-payment' if self_payment?(grpc)
8
- return 'peer-to-peer' if peer_to_peer?(grpc)
9
- return 'rebalance' if rebalance?(grpc, node_get_info)
17
+ return 'unknown' if grpc[:htlcs].empty?
18
+
19
+ return 'self-payment' if self_payment?(grpc[:htlcs].first[:route][:hops])
20
+ return 'peer-to-peer' if peer_to_peer?(grpc[:htlcs].first[:route][:hops])
21
+ return 'rebalance' if rebalance?(grpc[:htlcs].first[:route][:hops], node_get_info)
10
22
 
11
23
  'payment'
12
24
  end
13
25
 
14
- def self.self_payment?(grpc)
15
- grpc[:htlcs].first[:route][:hops].size == 2 &&
16
- grpc[:htlcs].first[:route][:hops][0][:chan_id] == grpc[:htlcs].first[:route][:hops][1][:chan_id]
26
+ def self.self_payment?(hops)
27
+ hops.size == 2 && hops[0][:chan_id] == hops[1][:chan_id]
17
28
  end
18
29
 
19
- def self.peer_to_peer?(grpc)
20
- grpc[:htlcs].first[:route][:hops].size == 1
30
+ def self.peer_to_peer?(hops)
31
+ hops.size == 1
21
32
  end
22
33
 
23
- def self.rebalance?(grpc, node_get_info)
24
- return false if grpc[:htlcs].first[:route][:hops].size <= 2
34
+ def self.rebalance?(hops, node_get_info)
35
+ return false if hops.size <= 2
25
36
 
26
- destination_public_key = grpc[:htlcs].first[:route][:hops].last[:pub_key]
37
+ destination_public_key = hops.last[:pub_key]
27
38
 
28
39
  node_get_info[:identity_pubkey] == destination_public_key
29
40
  end
@@ -5,7 +5,7 @@ require 'digest'
5
5
  require_relative '../connections/payment_channel'
6
6
  require_relative 'payment/purpose'
7
7
 
8
- require_relative '../payment_request'
8
+ require_relative '../invoice'
9
9
 
10
10
  require_relative '../../ports/dsl/lighstorm/errors'
11
11
 
@@ -23,25 +23,64 @@ module Lighstorm
23
23
  )
24
24
  end
25
25
 
26
- def self.list_payments(grpc, node_get_info)
26
+ def self.send_payment_v2(grpc, node_myself, invoice_decode)
27
+ adapted = list_payments(grpc, node_myself, invoice_decode)
28
+ adapted[:_source] = :send_payment_v2
29
+ adapted
30
+ end
31
+
32
+ def self.list_payments(grpc, node_myself, invoice_decode = nil)
27
33
  raise UnexpectedNumberOfHTLCsError, "htlcs: #{grpc[:htlcs].size}" if grpc[:htlcs].size > 1
28
34
 
29
35
  data = {
30
36
  _source: :list_payments,
31
37
  _key: _key(grpc),
32
- created_at: Time.at(grpc[:creation_time_ns] / 1e+9),
33
- status: grpc[:status].to_s.downcase,
38
+ at: Time.at(grpc[:creation_time_ns] / 1e+9),
39
+ state: grpc[:status].to_s.downcase,
34
40
  fee: { millisatoshis: grpc[:fee_msat] },
35
- purpose: Purpose.list_payments(grpc, node_get_info),
36
- request: PaymentRequest.list_payments(grpc),
37
- hops: grpc[:htlcs].first[:route][:hops].map.with_index do |raw_hop, i|
38
- hop = PaymentChannel.list_payments(raw_hop, i)
39
- hop[:channel][:target] = { public_key: raw_hop[:pub_key] }
40
- hop
41
- end
41
+ amount: { millisatoshis: grpc[:value_msat] },
42
+ purpose: Purpose.list_payments(grpc, node_myself),
43
+ invoice: Invoice.list_payments(grpc, invoice_decode)
42
44
  }
43
45
 
44
- data[:settled_at] = Time.at(grpc[:htlcs].first[:resolve_time_ns] / 1e+9) if grpc[:htlcs].first[:resolve_time_ns]
46
+ data[:secret] = data[:invoice][:secret]
47
+
48
+ htlc = grpc[:htlcs].first
49
+
50
+ return data if htlc.nil?
51
+
52
+ data[:hops] = htlc[:route][:hops].map.with_index do |raw_hop, i|
53
+ hop = PaymentChannel.list_payments(raw_hop, i)
54
+ hop[:channel][:target] = { public_key: raw_hop[:pub_key] }
55
+ hop
56
+ end
57
+
58
+ data[:invoice][:settled_at] = Time.at(htlc[:resolve_time_ns] / 1e+9) if htlc[:resolve_time_ns]
59
+
60
+ last_hop = htlc[:route][:hops].last
61
+
62
+ return data if last_hop.nil?
63
+
64
+ # https://github.com/satoshisstream/satoshis.stream/blob/main/TLV_registry.md
65
+ if last_hop[:custom_records][34_349_334]
66
+ data[:message] = last_hop[:custom_records][34_349_334].dup
67
+
68
+ unless data[:message].force_encoding('UTF-8').valid_encoding?
69
+ data[:message] = data[:message].unpack1('H*')
70
+
71
+ data[:message] = data[:message].scrub('?') unless data[:message].force_encoding('UTF-8').valid_encoding?
72
+ end
73
+ end
74
+
75
+ if data[:invoice] && data[:invoice][:code] && !data[:invoice][:code].nil? && !data[:invoice][:code].empty?
76
+ data[:through] = if data[:invoice][:payable] == 'indefinitely'
77
+ 'amp'
78
+ else
79
+ 'non-amp'
80
+ end
81
+ else
82
+ data[:through] = last_hop[:mpp_record] ? 'amp' : 'keysend'
83
+ end
45
84
 
46
85
  data
47
86
  end
data/adapters/invoice.rb CHANGED
@@ -2,11 +2,22 @@
2
2
 
3
3
  require 'digest'
4
4
 
5
- require_relative 'payment_request'
5
+ require_relative '../ports/dsl/lighstorm/errors'
6
6
 
7
7
  module Lighstorm
8
8
  module Adapter
9
9
  class Invoice
10
+ def self.add_invoice(grpc)
11
+ {
12
+ _source: :add_invoice,
13
+ code: grpc[:payment_request],
14
+ address: grpc[:payment_addr].unpack1('H*'),
15
+ secret: {
16
+ hash: grpc[:r_hash].unpack1('H*')
17
+ }
18
+ }
19
+ end
20
+
10
21
  def self.decode_pay_req(grpc, request_code = nil)
11
22
  adapted = {
12
23
  _source: :decode_pay_req,
@@ -18,55 +29,146 @@ module Lighstorm
18
29
  grpc[:payment_addr]
19
30
  ].join('/')
20
31
  ),
32
+ payable: 'once',
21
33
  created_at: Time.at(grpc[:timestamp]),
22
- request: PaymentRequest.decode_pay_req(grpc)
34
+ expires_at: Time.at(grpc[:timestamp] + grpc[:expiry]),
35
+ amount: (grpc[:num_msat]).zero? ? nil : { millisatoshis: grpc[:num_msat] },
36
+ description: {
37
+ memo: grpc[:description].empty? ? nil : grpc[:description],
38
+ hash: grpc[:description_hash] == '' ? nil : grpc[:description_hash]
39
+ },
40
+ address: grpc[:payment_addr].unpack1('H*'),
41
+ secret: {
42
+ hash: grpc[:payment_hash]
43
+ }
23
44
  }
24
45
 
25
- adapted[:request][:code] = request_code unless request_code.nil?
46
+ adapted[:code] = request_code unless request_code.nil?
26
47
 
27
- adapted
28
- end
48
+ if grpc[:features].key?(30) && grpc[:features][30][:is_required]
49
+ raise "unexpected feature[30] name #{grpc[:features][30][:name]}" if grpc[:features][30][:name] != 'amp'
29
50
 
30
- def self.add_invoice(grpc)
31
- {
32
- _source: :add_invoice,
33
- _key: Digest::SHA256.hexdigest(
34
- [
35
- grpc[:r_hash],
36
- grpc[:add_index],
37
- grpc[:payment_request],
38
- grpc[:payment_addr]
39
- ].join('/')
40
- ),
41
- request: PaymentRequest.add_invoice(grpc)
42
- }
43
- end
51
+ adapted[:payable] = 'indefinitely'
52
+ end
44
53
 
45
- def self.lookup_invoice(grpc)
46
- adapted = list_or_lookup(grpc)
54
+ adapted
55
+ end
47
56
 
57
+ def self.lookup_invoice(grpc, at)
58
+ adapted = list_or_lookup_invoice(grpc, at)
48
59
  adapted[:_source] = :lookup_invoice
49
- adapted[:request] = PaymentRequest.lookup_invoice(grpc)
60
+ adapted
61
+ end
50
62
 
63
+ def self.list_invoices(grpc, at)
64
+ adapted = list_or_lookup_invoice(grpc, at)
65
+ adapted[:_source] = :list_invoices
51
66
  adapted
52
67
  end
53
68
 
54
- def self.list_invoices(grpc)
55
- adapted = list_or_lookup(grpc)
69
+ def self.list_or_lookup_invoice(grpc, at)
70
+ raise 'missing at' if at.nil?
56
71
 
57
- adapted[:_source] = :list_invoices
58
- adapted[:request] = PaymentRequest.list_invoices(grpc)
72
+ adapted = {
73
+ _key: _key(grpc),
74
+ created_at: Time.at(grpc[:creation_date]),
75
+ expires_at: Time.at(grpc[:creation_date] + grpc[:expiry]),
76
+ settled_at: grpc[:settle_date].nil? || !grpc[:settle_date].positive? ? nil : Time.at(grpc[:settle_date]),
77
+ state: grpc[:state].to_s.downcase,
78
+ code: grpc[:payment_request].empty? ? nil : grpc[:payment_request],
79
+ payable: grpc[:is_amp] == true ? 'indefinitely' : 'once',
80
+ description: {
81
+ memo: grpc[:memo].empty? ? nil : grpc[:memo],
82
+ hash: grpc[:description_hash].empty? ? nil : grpc[:description_hash]
83
+ },
84
+ address: grpc[:payment_addr].unpack1('H*'),
85
+ secret: {
86
+ preimage: grpc[:r_preimage].empty? ? nil : grpc[:r_preimage].unpack1('H*'),
87
+ hash: grpc[:r_hash].empty? ? nil : grpc[:r_hash].unpack1('H*')
88
+ }
89
+ }
90
+
91
+ adapted[:amount] = { millisatoshis: grpc[:value_msat] } if grpc[:value_msat] != 0
92
+
93
+ adapted[:received] = { millisatoshis: grpc[:amt_paid_msat] } if grpc[:amt_paid_msat] != 0
94
+
95
+ if adapted[:state] == 'settled' &&
96
+ adapted[:payable] == 'indefinitely' &&
97
+ adapted[:expires_at] > at
98
+ adapted[:state] = 'open'
99
+ end
100
+
101
+ raise 'todo: received with canceled' if !adapted[:received].nil? && adapted[:state] == 'canceled'
102
+
103
+ adapted[:payments] = []
104
+
105
+ grpc[:htlcs].each do |htlc|
106
+ next unless htlc[:state] == :SETTLED
107
+
108
+ payment = {
109
+ amount: { millisatoshis: htlc[:amt_msat] },
110
+ hops: [{ channel: { id: htlc[:chan_id] } }],
111
+ at: Time.at(htlc[:resolve_time])
112
+ }
113
+
114
+ if grpc[:is_amp]
115
+ payment[:secret] = {
116
+ preimage: htlc[:amp][:preimage].unpack1('H*'),
117
+ hash: htlc[:amp][:hash].unpack1('H*')
118
+ }
119
+ end
120
+
121
+ # https://github.com/satoshisstream/satoshis.stream/blob/main/TLV_registry.md
122
+ if htlc[:custom_records][34_349_334]
123
+ payment[:message] = htlc[:custom_records][34_349_334].dup
124
+
125
+ unless payment[:message].force_encoding('UTF-8').valid_encoding?
126
+ payment[:message] = payment[:message].unpack1('H*')
127
+
128
+ unless payment[:message].force_encoding('UTF-8').valid_encoding?
129
+ payment[:message] = payment[:message].scrub('?')
130
+ end
131
+ end
132
+ end
133
+
134
+ adapted[:payments] << payment
135
+ end
136
+
137
+ adapted[:payments] = adapted[:payments].sort_by { |payment| -payment[:at].to_i }
138
+
139
+ adapted.delete(:payments) if adapted[:payments].empty?
59
140
 
60
141
  adapted
61
142
  end
62
143
 
63
- def self.list_or_lookup(grpc)
64
- {
144
+ def self.list_payments(grpc, invoice_decode = nil)
145
+ raise UnexpectedNumberOfHTLCsError, "htlcs: #{grpc[:htlcs].size}" if grpc[:htlcs].size > 1
146
+
147
+ data = {
65
148
  _key: _key(grpc),
149
+ _source: :list_payments,
66
150
  created_at: Time.at(grpc[:creation_date]),
67
- settle_at: grpc[:settle_date].nil? || !grpc[:settle_date].positive? ? nil : Time.at(grpc[:settle_date]),
68
- state: grpc[:state].to_s.downcase
151
+ settled_at: grpc[:settle_date].nil? || !grpc[:settle_date].positive? ? nil : Time.at(grpc[:settle_date]),
152
+ state: nil,
153
+ payable: grpc[:is_amp] == true ? 'indefinitely' : 'once',
154
+ code: grpc[:payment_request].empty? ? nil : grpc[:payment_request],
155
+ amount: { millisatoshis: grpc[:value_msat] },
156
+ description: {
157
+ memo: grpc[:memo],
158
+ hash: grpc[:description_hash] == '' ? nil : grpc[:description_hash]
159
+ },
160
+ secret: {
161
+ preimage: grpc[:payment_preimage],
162
+ hash: grpc[:payment_hash]
163
+ }
69
164
  }
165
+
166
+ unless invoice_decode.nil?
167
+ data[:payable] = invoice_decode[:payable]
168
+ data[:expires_at] = invoice_decode[:expires_at]
169
+ end
170
+
171
+ data
70
172
  end
71
173
 
72
174
  def self._key(grpc)
data/components/cache.rb CHANGED
@@ -21,10 +21,15 @@ module Lighstorm
21
21
  @client = Zache.new
22
22
  end
23
23
 
24
+ def safety_key(key)
25
+ key.gsub('.', '_').to_sym
26
+ end
27
+
24
28
  def for(key, ttl: nil, params: {}, &block)
25
29
  if ttl.nil?
26
- ttl = Lighstorm::Static::CACHE[key.sub('lightning.', '').to_sym]
27
- raise MissingTTLError, "missing ttl for #{key.sub('lightning.', '')} static/cache.rb" if ttl.nil?
30
+ safety_key = self.safety_key(key)
31
+ ttl = Lighstorm::Static::CACHE[safety_key]
32
+ raise MissingTTLError, "missing ttl for #{safety_key} static/cache.rb" if ttl.nil?
28
33
 
29
34
  ttl = ttl == false ? false : ttl[:ttl]
30
35
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'digest'
4
4
 
5
- require_relative 'all'
5
+ require_relative './all'
6
6
 
7
7
  require_relative '../../models/edges/groups/channel_forwards'
8
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../../../ports/grpc'
4
4
  require_relative '../../../models/errors'
5
+ require_relative '../../../helpers/time_expression'
5
6
  require_relative '../../invoice'
6
7
  require_relative '../../action'
7
8
 
@@ -15,15 +16,27 @@ module Lighstorm
15
16
  ).to_h
16
17
  end
17
18
 
18
- def self.prepare(description: nil, millisatoshis: nil)
19
- {
19
+ def self.prepare(payable:, expires_in:, description: nil, millisatoshis: nil)
20
+ request = {
20
21
  service: :lightning,
21
22
  method: :add_invoice,
22
23
  params: {
23
24
  memo: description,
24
- value_msat: millisatoshis
25
+ # Lightning Invoice Expiration: UX Considerations
26
+ # https://d.elor.me/2022/01/lightning-invoice-expiration-ux-considerations/
27
+ expiry: Helpers::TimeExpression.seconds(expires_in)
25
28
  }
26
29
  }
30
+
31
+ request[:params][:value_msat] = millisatoshis unless millisatoshis.nil?
32
+
33
+ if payable.to_sym == :indefinitely
34
+ request[:params][:is_amp] = true
35
+ elsif payable.to_sym != :once
36
+ raise Errors::ArgumentError, "payable: accepts 'indefinitely' or 'once', '#{payable}' is not valid."
37
+ end
38
+
39
+ request
27
40
  end
28
41
 
29
42
  def self.dispatch(grpc_request, &vcr)
@@ -35,16 +48,19 @@ module Lighstorm
35
48
  end
36
49
 
37
50
  def self.fetch(adapted, &vcr)
38
- FindBySecretHash.data(adapted[:request][:secret][:hash], &vcr)
51
+ FindBySecretHash.data(adapted[:secret][:hash], &vcr)
39
52
  end
40
53
 
41
54
  def self.model(data)
42
55
  FindBySecretHash.model(data)
43
56
  end
44
57
 
45
- def self.perform(description: nil, millisatoshis: nil, preview: false, &vcr)
58
+ def self.perform(payable:, expires_in:, description: nil, millisatoshis: nil, preview: false, &vcr)
46
59
  grpc_request = prepare(
47
- description: description, millisatoshis: millisatoshis
60
+ description: description,
61
+ millisatoshis: millisatoshis,
62
+ expires_in: expires_in,
63
+ payable: payable
48
64
  )
49
65
 
50
66
  return grpc_request if preview
@@ -2,25 +2,83 @@
2
2
 
3
3
  require_relative '../../../ports/grpc'
4
4
  require_relative '../../../models/errors'
5
+ require_relative '../../../models/edges/payment'
6
+ require_relative '../../../adapters/edges/payment'
7
+ require_relative '../../invoice'
8
+ require_relative '../../action'
9
+ require_relative '../../node/myself'
10
+
11
+ require_relative '../../payment/actions/pay'
5
12
 
6
13
  module Lighstorm
7
14
  module Controllers
8
15
  module Invoice
9
16
  module Pay
10
- def self.perform(_invoice, preview: false, fake: false)
11
- raise Errors::ToDoError, self
12
-
13
- LND.instance.middleware('router.send_payment_v2') do
14
- result = []
15
- LND.instance.client.router.send_payment_v2(
16
- payment_request: request,
17
- timeout_seconds: 5,
18
- allow_self_payment: true
19
- ) do |response|
20
- result << response
21
- end
22
- result
17
+ def self.dispatch(grpc_request, &vcr)
18
+ Payment::Pay.dispatch(grpc_request, &vcr)
19
+ end
20
+
21
+ def self.fetch(request_code, &vcr)
22
+ Payment::Pay.fetch(request_code, &vcr)
23
+ end
24
+
25
+ def self.adapt(data, node_get_info)
26
+ Payment::Pay.adapt(data, node_get_info)
27
+ end
28
+
29
+ def self.model(data)
30
+ Payment::Pay.model(data)
31
+ end
32
+
33
+ def self.prepare(request_code:, times_out_in:, millisatoshis: nil, message: nil)
34
+ request = {
35
+ service: :router,
36
+ method: :send_payment_v2,
37
+ params: {
38
+ payment_request: request_code,
39
+ timeout_seconds: Helpers::TimeExpression.seconds(times_out_in),
40
+ allow_self_payment: true,
41
+ dest_custom_records: {}
42
+ }
43
+ }
44
+
45
+ request[:params][:amt_msat] = millisatoshis unless millisatoshis.nil?
46
+
47
+ if !message.nil? && !message.empty?
48
+ # https://github.com/satoshisstream/satoshis.stream/blob/main/TLV_registry.md
49
+ request[:params][:dest_custom_records][34_349_334] = message
23
50
  end
51
+
52
+ request[:params].delete(:dest_custom_records) if request[:params][:dest_custom_records].empty?
53
+
54
+ request
55
+ end
56
+
57
+ def self.perform(
58
+ times_out_in:, request_code:, millisatoshis: nil, message: nil, preview: false, &vcr
59
+ )
60
+ grpc_request = prepare(
61
+ request_code: request_code,
62
+ millisatoshis: millisatoshis,
63
+ message: message,
64
+ times_out_in: times_out_in
65
+ )
66
+
67
+ return grpc_request if preview
68
+
69
+ response = dispatch(grpc_request, &vcr)
70
+
71
+ Payment::Pay.raise_error_if_exists!(response)
72
+
73
+ data = fetch(request_code, &vcr)
74
+
75
+ adapted = adapt(response, data)
76
+
77
+ model = self.model(adapted)
78
+
79
+ Payment::Pay.raise_failure_if_exists!(model, response)
80
+
81
+ Action::Output.new({ response: response[:response], result: model })
24
82
  end
25
83
  end
26
84
  end