lighstorm 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +15 -10
  7. data/README.md +4 -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 +139 -17
  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 +71 -13
  16. data/controllers/invoice/all.rb +16 -6
  17. data/controllers/invoice/decode.rb +44 -0
  18. data/controllers/invoice/find_by_secret_hash.rb +7 -1
  19. data/controllers/invoice.rb +17 -3
  20. data/controllers/node/actions/pay.rb +109 -0
  21. data/controllers/payment/actions/pay.rb +104 -0
  22. data/controllers/payment/all.rb +49 -16
  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 +292 -49
  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 +67 -11
  35. data/models/nodes/node.rb +32 -0
  36. data/models/satoshis.rb +11 -3
  37. data/models/secret.rb +31 -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 +12 -4
  43. data/adapters/payment_request.rb +0 -87
  44. data/models/payment_request.rb +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3a0597ad2e13c24bcf64efd0e12e2b6c8ae0c7b6b012b6fab89caffd2ac06a9
4
- data.tar.gz: 01fa72d76dcdb97c62a1edd5f8829d8038baba9639de245e75059bbf6309d199
3
+ metadata.gz: 551ffb5be38f96d6c0bb4f7b8fbde5763c623f0d100f0a1b34acb90a6074fb23
4
+ data.tar.gz: 59451d54f2ad6d45c95c7649ab0a048a4adeb7a15f1af08f133d8eaae107d9bc
5
5
  SHA512:
6
- metadata.gz: e7e7d996345446669475259095ad4843783ec21083094e3acbb883fbde421b57efec0d1ca5a495526ee6e1c536920003a3472db21eb6ee2394218bcf07fc235e
7
- data.tar.gz: 8e4b80f814feff68293a6db0ecdc9346640d19e3f761d16fafa5c0f1dcfbea978208dca9c6e186834ef9b28bac0b296f37d5b3aeba5a0b05f4cac06e38bcbc4f
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.45', '>= 1.45.1'
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.7)
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.46.0)
54
+ rubocop (1.48.0)
54
55
  json (~> 2.3)
55
56
  parallel (~> 1.10)
56
57
  parser (>= 3.2.0.0)
@@ -60,14 +61,16 @@ GEM
60
61
  rubocop-ast (>= 1.26.0, < 2.0)
61
62
  ruby-progressbar (~> 1.7)
62
63
  unicode-display_width (>= 2.4.0, < 3.0)
63
- rubocop-ast (1.26.0)
64
+ rubocop-ast (1.27.0)
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
- ruby-progressbar (1.11.0)
73
+ ruby-progressbar (1.13.0)
71
74
  unicode-display_width (2.4.2)
72
75
  zache (0.12.0)
73
76
 
@@ -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.45, >= 1.45.1)
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.7'
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.7
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,7 +2,7 @@
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
@@ -10,43 +10,165 @@ module Lighstorm
10
10
  def self.add_invoice(grpc)
11
11
  {
12
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
+
21
+ def self.decode_pay_req(grpc, request_code = nil)
22
+ adapted = {
23
+ _source: :decode_pay_req,
13
24
  _key: Digest::SHA256.hexdigest(
14
25
  [
15
- grpc[:r_hash],
16
- grpc[:add_index],
17
- grpc[:payment_request],
26
+ grpc[:payment_hash],
27
+ grpc[:num_satoshis],
28
+ grpc[:timestamp],
18
29
  grpc[:payment_addr]
19
30
  ].join('/')
20
31
  ),
21
- request: PaymentRequest.add_invoice(grpc)
32
+ payable: 'once',
33
+ created_at: Time.at(grpc[:timestamp]),
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
+ }
22
44
  }
23
- end
24
45
 
25
- def self.lookup_invoice(grpc)
26
- adapted = list_or_lookup(grpc)
46
+ adapted[:code] = request_code unless request_code.nil?
27
47
 
28
- adapted[:_source] = :lookup_invoice
29
- adapted[:request] = PaymentRequest.lookup_invoice(grpc)
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'
50
+
51
+ adapted[:payable] = 'indefinitely'
52
+ end
30
53
 
31
54
  adapted
32
55
  end
33
56
 
34
- def self.list_invoices(grpc)
35
- adapted = list_or_lookup(grpc)
57
+ def self.lookup_invoice(grpc, at)
58
+ adapted = list_or_lookup_invoice(grpc, at)
59
+ adapted[:_source] = :lookup_invoice
60
+ adapted
61
+ end
36
62
 
63
+ def self.list_invoices(grpc, at)
64
+ adapted = list_or_lookup_invoice(grpc, at)
37
65
  adapted[:_source] = :list_invoices
38
- adapted[:request] = PaymentRequest.list_invoices(grpc)
66
+ adapted
67
+ end
68
+
69
+ def self.list_or_lookup_invoice(grpc, at)
70
+ raise 'missing at' if at.nil?
71
+
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?
39
140
 
40
141
  adapted
41
142
  end
42
143
 
43
- def self.list_or_lookup(grpc)
44
- {
144
+ def self.list_payments(grpc, invoice_decode = nil)
145
+ raise UnexpectedNumberOfHTLCsError, "htlcs: #{grpc[:htlcs].size}" if grpc[:htlcs].size > 1
146
+
147
+ data = {
45
148
  _key: _key(grpc),
149
+ _source: :list_payments,
46
150
  created_at: Time.at(grpc[:creation_date]),
47
- settle_at: grpc[:settle_date].nil? || !grpc[:settle_date].positive? ? nil : Time.at(grpc[:settle_date]),
48
- 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
+ }
49
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
50
172
  end
51
173
 
52
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