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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 643a80fa54f053a2f647dbeb4df8b959cb0d512a7aa220c338c136220775c960
4
- data.tar.gz: 5fd770e41ad436cc920c4c3723eeddc4027feb0bb9a3ace19f3856e5f516b948
3
+ metadata.gz: ed188ef291ac08a9cc6f91dca92931be2c88257ee184416fd0f40a3224740b20
4
+ data.tar.gz: 1be060a12d9e755354764cc8151da8a26e114fcc9a0ecac470e9fe9b685a60a4
5
5
  SHA512:
6
- metadata.gz: 403f808bf48235a765f8c50fb840cddd86c0c77ece348b3b907460c8f84cb932c91fc688ac8e7f488ced08974f1acabd0e5019661de6bdd0956178b657249ac7
7
- data.tar.gz: 4dbbb229dbe1204dc189d179b2106e55ad45d3ed36f28ded6206674400aeac80595fd45f52f755bd118f90e6e14bfff5f4fc7e096a8b2dfab1bfa94da4ddc430
6
+ metadata.gz: b37802f04bae65416f4e80590506518025d62f1ca9b502b97db3cc108792b13a077f2ebb89c3e0be4cf311fb10a53f916a3c37e2f6c729b9bf01f187b0ab4892
7
+ data.tar.gz: ac5f5a1f5f4ff271ab2ddef517763f380f1868b71339bcf1758975e35c7e520ddd65fddb9d210539fedde2dc3830912db8b0bbdbfed1aa454e781c8a6e43e948
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.10)
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.10'
38
38
  ```
39
39
 
40
40
  ```ruby
@@ -46,12 +46,20 @@ 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.10
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',
55
+ amount: { millisatoshis: 1_000 },
56
+ payable: 'once'
57
+ )
58
+
59
+ Lighstorm::Invoice.decode('lnbc20m1pv...qqdhhwkj').pay
60
+
61
+ Lighstorm::Invoice.decode('lnbc20m1pv...qqdhhwkj').pay(
62
+ fee: { maximum: { millisatoshis: 1000 } }
55
63
  )
56
64
 
57
65
  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,12 +2,23 @@
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.decode_pay_req(grpc, request_code = nil)
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
+
21
+ def self.decode_pay_req(grpc, code = nil)
11
22
  adapted = {
12
23
  _source: :decode_pay_req,
13
24
  _key: Digest::SHA256.hexdigest(
@@ -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] = code unless 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, amount: 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] = amount[:millisatoshis] unless amount.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, amount: nil, preview: false, &vcr)
46
59
  grpc_request = prepare(
47
- description: description, millisatoshis: millisatoshis
60
+ description: description,
61
+ amount: amount,
62
+ expires_in: expires_in,
63
+ payable: payable
48
64
  )
49
65
 
50
66
  return grpc_request if preview