lighstorm 0.0.8 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +1 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +1 -0
  5. data/Gemfile +5 -3
  6. data/Gemfile.lock +13 -8
  7. data/README.md +11 -3
  8. data/Rakefile +11 -0
  9. data/adapters/edges/payment/purpose.rb +22 -11
  10. data/adapters/edges/payment.rb +51 -12
  11. data/adapters/invoice.rb +133 -31
  12. data/components/cache.rb +7 -2
  13. data/controllers/forward/group_by_channel.rb +1 -1
  14. data/controllers/invoice/actions/create.rb +22 -6
  15. data/controllers/invoice/actions/pay.rb +79 -13
  16. data/controllers/invoice/all.rb +16 -6
  17. data/controllers/invoice/decode.rb +6 -6
  18. data/controllers/invoice/find_by_secret_hash.rb +7 -1
  19. data/controllers/invoice.rb +15 -6
  20. data/controllers/node/actions/pay.rb +114 -0
  21. data/controllers/payment/actions/pay.rb +104 -0
  22. data/controllers/payment/all.rb +79 -28
  23. data/controllers/transaction/all.rb +54 -0
  24. data/controllers/transaction.rb +13 -0
  25. data/deleted.sh +1 -0
  26. data/docs/README.md +307 -51
  27. data/docs/_coverpage.md +1 -1
  28. data/docs/index.html +1 -1
  29. data/helpers/time_expression.rb +33 -0
  30. data/models/connections/payment_channel.rb +13 -8
  31. data/models/edges/channel.rb +1 -1
  32. data/models/edges/payment.rb +51 -20
  33. data/models/errors.rb +29 -1
  34. data/models/invoice.rb +69 -11
  35. data/models/nodes/node.rb +35 -0
  36. data/models/satoshis.rb +11 -3
  37. data/models/secret.rb +37 -0
  38. data/models/transaction.rb +42 -0
  39. data/ports/dsl/lighstorm.rb +2 -0
  40. data/static/cache.rb +14 -13
  41. data/static/spec.rb +1 -1
  42. metadata +11 -4
  43. data/adapters/payment_request.rb +0 -87
  44. data/models/payment_request.rb +0 -72
@@ -2,25 +2,91 @@
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(code, &vcr)
22
+ Payment::Pay.fetch(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(code:, times_out_in:, amount: nil, fee: nil, message: nil)
34
+ request = {
35
+ service: :router,
36
+ method: :send_payment_v2,
37
+ params: {
38
+ payment_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] = amount[:millisatoshis] unless amount.nil?
46
+
47
+ unless fee.nil? || fee[:maximum].nil? || fee[:maximum][:millisatoshis].nil?
48
+ request[:params][:fee_limit_msat] = fee[:maximum][:millisatoshis]
49
+ end
50
+
51
+ if !message.nil? && !message.empty?
52
+ # https://github.com/satoshisstream/satoshis.stream/blob/main/TLV_registry.md
53
+ request[:params][:dest_custom_records][34_349_334] = message
23
54
  end
55
+
56
+ request[:params].delete(:dest_custom_records) if request[:params][:dest_custom_records].empty?
57
+
58
+ request
59
+ end
60
+
61
+ def self.perform(
62
+ times_out_in:, code:,
63
+ amount: nil, fee: nil,
64
+ message: nil,
65
+ preview: false, &vcr
66
+ )
67
+ grpc_request = prepare(
68
+ code: code,
69
+ amount: amount,
70
+ fee: fee,
71
+ message: message,
72
+ times_out_in: times_out_in
73
+ )
74
+
75
+ return grpc_request if preview
76
+
77
+ response = dispatch(grpc_request, &vcr)
78
+
79
+ Payment::Pay.raise_error_if_exists!(response)
80
+
81
+ data = fetch(code, &vcr)
82
+
83
+ adapted = adapt(response, data)
84
+
85
+ model = self.model(adapted)
86
+
87
+ Payment::Pay.raise_failure_if_exists!(model, response)
88
+
89
+ Action::Output.new({ response: response[:response], result: model })
24
90
  end
25
91
  end
26
92
  end
@@ -8,7 +8,9 @@ module Lighstorm
8
8
  module Controllers
9
9
  module Invoice
10
10
  module All
11
- def self.fetch(limit: nil)
11
+ def self.fetch(limit: nil, spontaneous: false)
12
+ at = Time.now
13
+
12
14
  last_offset = 0
13
15
 
14
16
  invoices = []
@@ -19,7 +21,9 @@ module Lighstorm
19
21
  num_max_invoices: 10
20
22
  )
21
23
 
22
- response.invoices.each { |invoice| invoices << invoice.to_h }
24
+ response.invoices.each do |invoice|
25
+ invoices << invoice.to_h if spontaneous || !invoice.payment_request.empty?
26
+ end
23
27
 
24
28
  # TODO: How to optimize this?
25
29
  # break if !limit.nil? && invoices.size >= limit
@@ -33,13 +37,15 @@ module Lighstorm
33
37
 
34
38
  invoices = invoices[0..limit - 1] unless limit.nil?
35
39
 
36
- { list_invoices: invoices }
40
+ { at: at, list_invoices: invoices }
37
41
  end
38
42
 
39
43
  def self.adapt(raw)
44
+ raise 'missing at' if raw[:at].nil?
45
+
40
46
  {
41
47
  list_invoices: raw[:list_invoices].map do |raw_invoice|
42
- Lighstorm::Adapter::Invoice.list_invoices(raw_invoice)
48
+ Lighstorm::Adapter::Invoice.list_invoices(raw_invoice, raw[:at])
43
49
  end
44
50
  }
45
51
  end
@@ -51,8 +57,12 @@ module Lighstorm
51
57
  end
52
58
  end
53
59
 
54
- def self.data(limit: nil, &vcr)
55
- raw = vcr.nil? ? fetch(limit: limit) : vcr.call(-> { fetch(limit: limit) })
60
+ def self.data(limit: nil, spontaneous: false, &vcr)
61
+ raw = if vcr.nil?
62
+ fetch(limit: limit, spontaneous: spontaneous)
63
+ else
64
+ vcr.call(-> { fetch(limit: limit, spontaneous: spontaneous) })
65
+ end
56
66
 
57
67
  adapted = adapt(raw)
58
68
 
@@ -8,17 +8,17 @@ module Lighstorm
8
8
  module Controllers
9
9
  module Invoice
10
10
  module Decode
11
- def self.fetch(request_code)
11
+ def self.fetch(code)
12
12
  {
13
- _request_code: request_code,
14
- decode_pay_req: Ports::GRPC.lightning.decode_pay_req(pay_req: request_code).to_h
13
+ _code: code,
14
+ decode_pay_req: Ports::GRPC.lightning.decode_pay_req(pay_req: code).to_h
15
15
  }
16
16
  end
17
17
 
18
18
  def self.adapt(raw)
19
19
  {
20
20
  decode_pay_req: Lighstorm::Adapter::Invoice.decode_pay_req(
21
- raw[:decode_pay_req], raw[:_request_code]
21
+ raw[:decode_pay_req], raw[:_code]
22
22
  )
23
23
  }
24
24
  end
@@ -27,8 +27,8 @@ module Lighstorm
27
27
  adapted[:decode_pay_req]
28
28
  end
29
29
 
30
- def self.data(request_code, &vcr)
31
- raw = vcr.nil? ? fetch(request_code) : vcr.call(-> { fetch(request_code) })
30
+ def self.data(code, &vcr)
31
+ raw = vcr.nil? ? fetch(code) : vcr.call(-> { fetch(code) })
32
32
 
33
33
  adapted = adapt(raw)
34
34
 
@@ -10,13 +10,19 @@ module Lighstorm
10
10
  module FindBySecretHash
11
11
  def self.fetch(secret_hash)
12
12
  {
13
+ at: Time.now,
13
14
  lookup_invoice: Ports::GRPC.lightning.lookup_invoice(r_hash_str: secret_hash).to_h
14
15
  }
15
16
  end
16
17
 
17
18
  def self.adapt(raw)
19
+ raise 'missing at' if raw[:at].nil?
20
+
18
21
  {
19
- lookup_invoice: Lighstorm::Adapter::Invoice.lookup_invoice(raw[:lookup_invoice])
22
+ lookup_invoice: Lighstorm::Adapter::Invoice.lookup_invoice(
23
+ raw[:lookup_invoice],
24
+ raw[:at]
25
+ )
20
26
  }
21
27
  end
22
28
 
@@ -8,8 +8,8 @@ require_relative './invoice/actions/create'
8
8
  module Lighstorm
9
9
  module Controllers
10
10
  module Invoice
11
- def self.all(limit: nil)
12
- All.model(All.data(limit: limit))
11
+ def self.all(limit: nil, spontaneous: false)
12
+ All.model(All.data(limit: limit, spontaneous: spontaneous))
13
13
  end
14
14
 
15
15
  def self.first
@@ -24,14 +24,23 @@ module Lighstorm
24
24
  FindBySecretHash.model(FindBySecretHash.data(secret_hash))
25
25
  end
26
26
 
27
- def self.decode(request_code, &vcr)
28
- Decode.model(Decode.data(request_code, &vcr))
27
+ def self.decode(code, &vcr)
28
+ Decode.model(Decode.data(code, &vcr))
29
29
  end
30
30
 
31
- def self.create(description: nil, millisatoshis: nil, preview: false, &vcr)
31
+ def self.create(
32
+ payable:,
33
+ description: nil, amount: nil,
34
+ # Lightning Invoice Expiration: UX Considerations
35
+ # https://d.elor.me/2022/01/lightning-invoice-expiration-ux-considerations/
36
+ expires_in: { hours: 24 },
37
+ preview: false, &vcr
38
+ )
32
39
  Create.perform(
40
+ payable: payable,
33
41
  description: description,
34
- millisatoshis: millisatoshis,
42
+ amount: amount,
43
+ expires_in: expires_in,
35
44
  preview: preview,
36
45
  &vcr
37
46
  )
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'digest'
5
+
6
+ require_relative '../../../ports/grpc'
7
+ require_relative '../../../models/secret'
8
+ require_relative '../../../models/errors'
9
+ require_relative '../../../models/edges/payment'
10
+ require_relative '../../../adapters/edges/payment'
11
+ require_relative '../../invoice'
12
+ require_relative '../../action'
13
+ require_relative '../../node/myself'
14
+
15
+ require_relative '../../payment/actions/pay'
16
+
17
+ module Lighstorm
18
+ module Controllers
19
+ module Node
20
+ module Pay
21
+ def self.dispatch(grpc_request, &vcr)
22
+ Payment::Pay.dispatch(grpc_request, &vcr)
23
+ end
24
+
25
+ def self.fetch(&vcr)
26
+ Payment::Pay.fetch(&vcr)
27
+ end
28
+
29
+ def self.adapt(data, node_get_info)
30
+ Payment::Pay.adapt(data, node_get_info)
31
+ end
32
+
33
+ def self.model(data)
34
+ Payment::Pay.model(data)
35
+ end
36
+
37
+ def self.prepare(public_key:, amount:, times_out_in:, secret:, through:, fee: nil, message: nil)
38
+ # Appreciation note for people that suffered in the past and shared
39
+ # their knowledge, so we don't have to struggle the same:
40
+ # - https://github.com/lightningnetwork/lnd/discussions/6357
41
+ # - https://docs.lightning.engineering/lightning-network-tools/lnd/send-messages-with-keysend
42
+ # - https://peakd.com/@brianoflondon/lightning-keysend-is-strange-and-how-to-send-keysend-payment-in-lightning-with-the-lnd-rest-api-via-python
43
+ # We are standing on the shoulders of giants, thank you very much. :)
44
+ request = {
45
+ service: :router,
46
+ method: :send_payment_v2,
47
+ params: {
48
+ dest: [public_key].pack('H*'),
49
+ amt_msat: amount[:millisatoshis],
50
+ timeout_seconds: Helpers::TimeExpression.seconds(times_out_in),
51
+ allow_self_payment: true,
52
+ dest_custom_records: {}
53
+ }
54
+ }
55
+
56
+ if !message.nil? && !message.empty?
57
+ # https://github.com/satoshisstream/satoshis.stream/blob/main/TLV_registry.md
58
+ request[:params][:dest_custom_records][34_349_334] = message
59
+ end
60
+
61
+ unless fee.nil? || fee[:maximum].nil? || fee[:maximum][:millisatoshis].nil?
62
+ request[:params][:fee_limit_msat] = fee[:maximum][:millisatoshis]
63
+ end
64
+
65
+ if through.to_sym == :keysend
66
+ request[:params][:payment_hash] = [secret[:hash]].pack('H*')
67
+ request[:params][:dest_custom_records][5_482_373_484] = [secret[:preimage]].pack('H*')
68
+ elsif through.to_sym == :amp
69
+ request[:params][:amp] = true
70
+ end
71
+
72
+ request[:params].delete(:dest_custom_records) if request[:params][:dest_custom_records].empty?
73
+
74
+ request
75
+ end
76
+
77
+ def self.perform(
78
+ public_key:, amount:, through:,
79
+ times_out_in:, fee: nil,
80
+ message: nil, secret: nil,
81
+ preview: false, &vcr
82
+ )
83
+ secret = Models::Secret.create.to_h if secret.nil? && through.to_sym == :keysend
84
+
85
+ grpc_request = prepare(
86
+ public_key: public_key,
87
+ amount: amount,
88
+ fee: fee,
89
+ through: through,
90
+ times_out_in: times_out_in,
91
+ secret: secret,
92
+ message: message
93
+ )
94
+
95
+ return grpc_request if preview
96
+
97
+ response = dispatch(grpc_request, &vcr)
98
+
99
+ Payment::Pay.raise_error_if_exists!(response)
100
+
101
+ data = fetch(&vcr)
102
+
103
+ adapted = adapt(response, data)
104
+
105
+ model = self.model(adapted)
106
+
107
+ Payment::Pay.raise_failure_if_exists!(model, response)
108
+
109
+ Action::Output.new({ response: response[:response], result: model })
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'digest'
5
+
6
+ require_relative '../../../ports/grpc'
7
+ require_relative '../../../models/errors'
8
+ require_relative '../../../models/edges/payment'
9
+ require_relative '../../../adapters/edges/payment'
10
+ require_relative '../../node/myself'
11
+ require_relative '../../invoice/decode'
12
+
13
+ module Lighstorm
14
+ module Controllers
15
+ module Payment
16
+ module Pay
17
+ def self.call(grpc_request)
18
+ result = []
19
+ Lighstorm::Ports::GRPC.send(grpc_request[:service]).send(
20
+ grpc_request[:method], grpc_request[:params]
21
+ ) do |response|
22
+ result << response.to_h
23
+ end
24
+ { response: result, exception: nil }
25
+ rescue StandardError => e
26
+ { exception: e }
27
+ end
28
+
29
+ def self.dispatch(grpc_request, &vcr)
30
+ vcr.nil? ? call(grpc_request) : vcr.call(-> { call(grpc_request) }, :dispatch)
31
+ end
32
+
33
+ def self.fetch_all(code)
34
+ {
35
+ invoice_decode: code.nil? ? nil : Invoice::Decode.data(code),
36
+ node_myself: Node::Myself.data
37
+ }
38
+ end
39
+
40
+ def self.fetch(code = nil, &vcr)
41
+ raw = vcr.nil? ? fetch_all(code) : vcr.call(-> { fetch_all(code) })
42
+ end
43
+
44
+ def self.adapt(grpc_data, fetch_data)
45
+ Adapter::Payment.send_payment_v2(
46
+ grpc_data[:response].last,
47
+ fetch_data[:node_myself],
48
+ fetch_data[:invoice_decode]
49
+ )
50
+ end
51
+
52
+ def self.model(data)
53
+ Models::Payment.new(data)
54
+ end
55
+
56
+ def self.raise_error_if_exists!(response)
57
+ return if response[:exception].nil?
58
+
59
+ if response[:exception].is_a?(GRPC::AlreadyExists)
60
+ raise AlreadyPaidError.new(
61
+ 'The invoice is already paid.',
62
+ grpc: response[:exception]
63
+ )
64
+ end
65
+
66
+ if response[:exception].message =~ /amount must not be specified when paying a non-zero/
67
+ raise AmountForNonZeroError.new(
68
+ 'Millisatoshis must not be specified when paying a non-zero amount invoice.',
69
+ grpc: response[:exception]
70
+ )
71
+ end
72
+
73
+ if response[:exception].message =~ /amount must be specified when paying a zero amount/
74
+ raise MissingMillisatoshisError.new(
75
+ 'Millisatoshis must be specified when paying a zero amount invoice.',
76
+ grpc: response[:exception]
77
+ )
78
+ end
79
+
80
+ raise PaymentError.new(
81
+ response[:exception].message,
82
+ grpc: response[:exception]
83
+ )
84
+ end
85
+
86
+ def self.raise_failure_if_exists!(model, response)
87
+ return unless model.state == 'failed'
88
+
89
+ if response[:response].last[:failure_reason] == :FAILURE_REASON_NO_ROUTE
90
+ raise NoRouteFoundError.new(
91
+ response[:response].last[:failure_reason],
92
+ response: response[:response], result: model
93
+ )
94
+ else
95
+ raise PaymentError.new(
96
+ response[:response].last[:failure_reason],
97
+ response: response[:response], result: model
98
+ )
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -108,9 +108,13 @@ module Lighstorm
108
108
 
109
109
  next if fetch[:get_node_info] == false || data[:get_node_info][hop[:pub_key]]
110
110
 
111
- data[:get_node_info][hop[:pub_key]] = grpc.lightning.get_node_info(
112
- pub_key: hop[:pub_key]
113
- ).to_h
111
+ begin
112
+ data[:get_node_info][hop[:pub_key]] = grpc.lightning.get_node_info(
113
+ pub_key: hop[:pub_key]
114
+ ).to_h
115
+ rescue StandardError => e
116
+ data[:get_node_info][hop[:pub_key]] = { _error: e }
117
+ end
114
118
  end
115
119
  end
116
120
  end
@@ -151,24 +155,36 @@ module Lighstorm
151
155
  end
152
156
 
153
157
  unless fetch[:get_node_info] == false || data[:get_node_info][channel[:node1_pub]]
154
- data[:get_node_info][channel[:node1_pub]] = grpc.lightning.get_node_info(
155
- pub_key: channel[:node1_pub]
156
- ).to_h
158
+ begin
159
+ data[:get_node_info][channel[:node1_pub]] = grpc.lightning.get_node_info(
160
+ pub_key: channel[:node1_pub]
161
+ ).to_h
162
+ rescue StandardError => e
163
+ data[:get_node_info][channel[:node1_pub]] = { _error: e }
164
+ end
157
165
  end
158
166
 
159
167
  next if fetch[:get_node_info] == false || data[:get_node_info][channel[:node2_pub]]
160
168
 
161
- data[:get_node_info][channel[:node2_pub]] = grpc.lightning.get_node_info(
162
- pub_key: channel[:node2_pub]
163
- ).to_h
169
+ begin
170
+ data[:get_node_info][channel[:node2_pub]] = grpc.lightning.get_node_info(
171
+ pub_key: channel[:node2_pub]
172
+ ).to_h
173
+ rescue StandardError => e
174
+ data[:get_node_info][channel[:node2_pub]] = { _error: e }
175
+ end
164
176
  end
165
177
 
166
178
  data[:list_channels].each_value do |channel|
167
179
  next if fetch[:get_node_info] == false || data[:get_node_info][channel[:remote_pubkey]]
168
180
 
169
- data[:get_node_info][channel[:remote_pubkey]] = grpc.lightning.get_node_info(
170
- pub_key: channel[:remote_pubkey]
171
- ).to_h
181
+ begin
182
+ data[:get_node_info][channel[:remote_pubkey]] = grpc.lightning.get_node_info(
183
+ pub_key: channel[:remote_pubkey]
184
+ ).to_h
185
+ rescue StandardError => e
186
+ data[:get_node_info][channel[:remote_pubkey]] = { _error: e }
187
+ end
172
188
  end
173
189
 
174
190
  data[:@meta] = { calls: grpc.calls }
@@ -195,7 +211,7 @@ module Lighstorm
195
211
  raw[:decode_pay_req].each_key do |key|
196
212
  next if raw[:decode_pay_req][key][:_error]
197
213
 
198
- adapted[:decode_pay_req][key] = Lighstorm::Adapter::PaymentRequest.decode_pay_req(
214
+ adapted[:decode_pay_req][key] = Lighstorm::Adapter::Invoice.decode_pay_req(
199
215
  raw[:decode_pay_req][key]
200
216
  )
201
217
  end
@@ -204,7 +220,7 @@ module Lighstorm
204
220
  next if raw[:lookup_invoice][key][:_error]
205
221
 
206
222
  adapted[:lookup_invoice][key] = Lighstorm::Adapter::Invoice.lookup_invoice(
207
- raw[:lookup_invoice][key]
223
+ raw[:lookup_invoice][key], raw[:at]
208
224
  )
209
225
  end
210
226
 
@@ -223,6 +239,8 @@ module Lighstorm
223
239
  end
224
240
 
225
241
  raw[:get_node_info].each_key do |key|
242
+ next if raw[:get_node_info][key][:_error]
243
+
226
244
  adapted[:get_node_info][key] = Lighstorm::Adapter::Node.get_node_info(
227
245
  raw[:get_node_info][key]
228
246
  )
@@ -309,34 +327,67 @@ module Lighstorm
309
327
  end
310
328
 
311
329
  def self.transform(list_payments, adapted)
312
- if adapted[:lookup_invoice][list_payments[:request][:secret][:hash]] &&
313
- !adapted[:lookup_invoice][list_payments[:request][:secret][:hash]][:_error]
330
+ if adapted[:lookup_invoice][list_payments[:secret][:hash]] &&
331
+ !adapted[:lookup_invoice][list_payments[:secret][:hash]][:_error]
314
332
 
315
- list_payments[:request] = adapted[:lookup_invoice][list_payments[:request][:secret][:hash]][:request]
316
- else
317
- list_payments[:request][:_key] = Digest::SHA256.hexdigest(
318
- list_payments[:request][:code]
319
- )
333
+ if list_payments[:invoice]
334
+ lookup = adapted[:lookup_invoice][list_payments[:secret][:hash]]
335
+
336
+ list_payments[:invoice][:description] = lookup[:description]
337
+
338
+ lookup.each_key do |key|
339
+ if lookup[key].is_a?(Hash)
340
+ unless list_payments[:invoice].key?(key) && !list_payments[:invoice][key].nil?
341
+ list_payments[:invoice][key] = lookup[:key]
342
+ end
343
+
344
+ next
345
+ end
346
+
347
+ unless list_payments[:invoice].key?(key) && !list_payments[:invoice][key].nil? &&
348
+ (!list_payments[:invoice][key].is_a?(String) || !list_payments[:invoice][key].empty?)
349
+ list_payments[:invoice][key] = lookup[key]
350
+ end
351
+ end
352
+ else
353
+ list_payments[:invoice] = adapted[:lookup_invoice][list_payments[:secret][:hash]]
354
+ end
320
355
  end
356
+
321
357
  list_payments[:hops].each do |hop|
322
358
  hop[:channel] = transform_channel(hop[:channel], adapted)
323
359
  end
324
360
 
325
- if adapted[:decode_pay_req][list_payments[:request][:code]]
326
- decoded = adapted[:decode_pay_req][list_payments[:request][:code]]
327
- request = list_payments[:request]
361
+ if adapted[:decode_pay_req][list_payments[:invoice][:code]]
362
+ decoded = adapted[:decode_pay_req][list_payments[:invoice][:code]]
363
+ invoice = list_payments[:invoice]
328
364
 
329
365
  decoded.each_key do |key|
330
- request[key] = decoded[key] unless request.key?(key)
366
+ if !decoded[key].is_a?(Hash)
367
+ invoice[key] = decoded[key]
368
+ elsif decoded[key].is_a?(Hash)
369
+ invoice[key] = {} unless invoice.key?(key)
331
370
 
332
- next unless decoded[key].is_a?(Hash)
371
+ next if key == :secret
333
372
 
334
- decoded[key].each_key do |sub_key|
335
- request[key][sub_key] = decoded[key][sub_key] unless request[key].key?(sub_key)
373
+ decoded[key].each_key do |sub_key|
374
+ next if decoded[key][sub_key].nil? ||
375
+ (decoded[key][sub_key].is_a?(String) && decoded[key][sub_key].empty?)
376
+
377
+ invoice[key][sub_key] = decoded[key][sub_key]
378
+ end
336
379
  end
337
380
  end
338
381
  end
339
382
 
383
+ if list_payments[:invoice][:code]
384
+ if list_payments[:invoice][:payable] == 'once'
385
+ list_payments[:through] = 'non-amp'
386
+ elsif list_payments[:invoice][:payable] == 'indefinitely'
387
+ list_payments[:through] = 'amp'
388
+ end
389
+ end
390
+
340
391
  list_payments
341
392
  end
342
393