lighstorm 0.0.8 → 0.0.10

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 +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