lighstorm 0.0.15 → 0.0.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +7 -7
  4. data/adapters/bitcoin/address.rb +19 -0
  5. data/adapters/bitcoin/transaction.rb +34 -0
  6. data/adapters/lightning/connections/channel_node/fee.rb +28 -0
  7. data/adapters/lightning/connections/channel_node/policy.rb +63 -0
  8. data/adapters/lightning/connections/channel_node.rb +77 -0
  9. data/adapters/lightning/connections/payment_channel.rb +30 -0
  10. data/adapters/lightning/edges/channel.rb +98 -0
  11. data/adapters/lightning/edges/forward.rb +42 -0
  12. data/adapters/lightning/edges/payment/purpose.rb +45 -0
  13. data/adapters/lightning/edges/payment.rb +89 -0
  14. data/adapters/lightning/invoice.rb +186 -0
  15. data/adapters/lightning/nodes/node.rb +113 -0
  16. data/controllers/action.rb +1 -1
  17. data/controllers/bitcoin/address/actions/create.rb +91 -0
  18. data/controllers/bitcoin/address/actions/pay.rb +119 -0
  19. data/controllers/bitcoin/address.rb +24 -0
  20. data/controllers/bitcoin/transaction/all.rb +65 -0
  21. data/controllers/bitcoin/transaction.rb +25 -0
  22. data/controllers/concerns/impersonatable.rb +5 -3
  23. data/controllers/connection.rb +1 -1
  24. data/controllers/lightning/channel/actions/apply_gossip.rb +201 -0
  25. data/controllers/lightning/channel/actions/update_fee.rb +94 -0
  26. data/controllers/lightning/channel/all.rb +85 -0
  27. data/controllers/lightning/channel/find_by_id.rb +155 -0
  28. data/controllers/lightning/channel/mine.rb +116 -0
  29. data/controllers/lightning/channel.rb +43 -0
  30. data/controllers/lightning/forward/all.rb +250 -0
  31. data/controllers/lightning/forward/group_by_channel.rb +91 -0
  32. data/controllers/lightning/forward.rb +36 -0
  33. data/controllers/lightning/invoice/actions/create.rb +90 -0
  34. data/controllers/lightning/invoice/actions/pay.rb +97 -0
  35. data/controllers/lightning/invoice/actions/pay_through_route.rb +73 -0
  36. data/controllers/lightning/invoice/all.rb +82 -0
  37. data/controllers/lightning/invoice/decode.rb +50 -0
  38. data/controllers/lightning/invoice/find_by_code.rb +76 -0
  39. data/controllers/lightning/invoice/find_by_secret_hash.rb +56 -0
  40. data/controllers/lightning/invoice.rb +64 -0
  41. data/controllers/lightning/node/actions/apply_gossip.rb +114 -0
  42. data/controllers/lightning/node/actions/pay.rb +117 -0
  43. data/controllers/lightning/node/all.rb +69 -0
  44. data/controllers/lightning/node/find_by_public_key.rb +55 -0
  45. data/controllers/lightning/node/myself.rb +36 -0
  46. data/controllers/lightning/node.rb +35 -0
  47. data/controllers/lightning/payment/actions/pay.rb +110 -0
  48. data/controllers/lightning/payment/all.rb +439 -0
  49. data/controllers/lightning/payment.rb +37 -0
  50. data/controllers/lightning/secret/valid_proof.rb +60 -0
  51. data/controllers/lightning/secret.rb +16 -0
  52. data/controllers/wallet/activity/all.rb +196 -0
  53. data/controllers/wallet/activity.rb +28 -0
  54. data/controllers/wallet/balance.rb +3 -3
  55. data/controllers/wallet.rb +1 -1
  56. data/docs/README.md +229 -148
  57. data/docs/_coverpage.md +1 -1
  58. data/docs/index.html +1 -1
  59. data/models/bitcoin/address.rb +49 -0
  60. data/models/bitcoin/transaction.rb +41 -0
  61. data/models/concerns/protectable.rb +1 -1
  62. data/models/errors.rb +7 -8
  63. data/models/lightning/connections/channel_node/accounting.rb +27 -0
  64. data/models/lightning/connections/channel_node/fee.rb +82 -0
  65. data/models/lightning/connections/channel_node/htlc/blocks/delta.rb +41 -0
  66. data/models/lightning/connections/channel_node/htlc.rb +88 -0
  67. data/models/lightning/connections/channel_node/policy.rb +41 -0
  68. data/models/lightning/connections/channel_node.rb +86 -0
  69. data/models/lightning/connections/forward_channel.rb +31 -0
  70. data/models/lightning/connections/payment_channel.rb +58 -0
  71. data/models/lightning/edges/channel/accounting.rb +87 -0
  72. data/models/lightning/edges/channel/hop.rb +67 -0
  73. data/models/lightning/edges/channel.rb +197 -0
  74. data/models/lightning/edges/forward.rb +51 -0
  75. data/models/lightning/edges/groups/channel_forwards/analysis.rb +71 -0
  76. data/models/lightning/edges/groups/channel_forwards.rb +39 -0
  77. data/models/lightning/edges/payment.rb +104 -0
  78. data/models/lightning/invoice.rb +115 -0
  79. data/models/lightning/nodes/node/lightning.rb +33 -0
  80. data/models/lightning/nodes/node/platform.rb +50 -0
  81. data/models/lightning/nodes/node.rb +145 -0
  82. data/models/lightning/rate.rb +37 -0
  83. data/models/lightning/secret.rb +57 -0
  84. data/models/satoshis.rb +1 -1
  85. data/models/wallet/activity.rb +54 -0
  86. data/models/wallet/balance.rb +42 -0
  87. data/ports/dsl/lighstorm.rb +29 -16
  88. data/static/cache.rb +2 -0
  89. data/static/spec.rb +1 -1
  90. metadata +74 -68
  91. data/adapters/connections/channel_node/fee.rb +0 -26
  92. data/adapters/connections/channel_node/policy.rb +0 -61
  93. data/adapters/connections/channel_node.rb +0 -75
  94. data/adapters/connections/payment_channel.rb +0 -28
  95. data/adapters/edges/channel.rb +0 -96
  96. data/adapters/edges/forward.rb +0 -40
  97. data/adapters/edges/payment/purpose.rb +0 -43
  98. data/adapters/edges/payment.rb +0 -87
  99. data/adapters/invoice.rb +0 -184
  100. data/adapters/nodes/node.rb +0 -111
  101. data/adapters/transaction.rb +0 -23
  102. data/controllers/activity/all.rb +0 -194
  103. data/controllers/activity.rb +0 -26
  104. data/controllers/channel/actions/apply_gossip.rb +0 -193
  105. data/controllers/channel/actions/update_fee.rb +0 -91
  106. data/controllers/channel/all.rb +0 -83
  107. data/controllers/channel/find_by_id.rb +0 -153
  108. data/controllers/channel/mine.rb +0 -114
  109. data/controllers/channel.rb +0 -41
  110. data/controllers/forward/all.rb +0 -248
  111. data/controllers/forward/group_by_channel.rb +0 -89
  112. data/controllers/forward.rb +0 -34
  113. data/controllers/invoice/actions/create.rb +0 -88
  114. data/controllers/invoice/actions/pay.rb +0 -95
  115. data/controllers/invoice/actions/pay_through_route.rb +0 -71
  116. data/controllers/invoice/all.rb +0 -80
  117. data/controllers/invoice/decode.rb +0 -44
  118. data/controllers/invoice/find_by_code.rb +0 -70
  119. data/controllers/invoice/find_by_secret_hash.rb +0 -54
  120. data/controllers/invoice.rb +0 -62
  121. data/controllers/node/actions/apply_gossip.rb +0 -112
  122. data/controllers/node/actions/pay.rb +0 -115
  123. data/controllers/node/all.rb +0 -67
  124. data/controllers/node/find_by_public_key.rb +0 -53
  125. data/controllers/node/myself.rb +0 -34
  126. data/controllers/node.rb +0 -33
  127. data/controllers/payment/actions/pay.rb +0 -108
  128. data/controllers/payment/all.rb +0 -437
  129. data/controllers/payment.rb +0 -35
  130. data/controllers/secret/valid_proof.rb +0 -58
  131. data/controllers/transaction/all.rb +0 -61
  132. data/controllers/transaction.rb +0 -25
  133. data/models/activity.rb +0 -52
  134. data/models/connections/channel_node/accounting.rb +0 -25
  135. data/models/connections/channel_node/fee.rb +0 -80
  136. data/models/connections/channel_node/htlc/blocks/delta.rb +0 -39
  137. data/models/connections/channel_node/htlc.rb +0 -86
  138. data/models/connections/channel_node/policy.rb +0 -39
  139. data/models/connections/channel_node.rb +0 -84
  140. data/models/connections/forward_channel.rb +0 -29
  141. data/models/connections/payment_channel.rb +0 -56
  142. data/models/edges/channel/accounting.rb +0 -85
  143. data/models/edges/channel/hop.rb +0 -65
  144. data/models/edges/channel.rb +0 -195
  145. data/models/edges/forward.rb +0 -49
  146. data/models/edges/groups/channel_forwards/analysis.rb +0 -69
  147. data/models/edges/groups/channel_forwards.rb +0 -37
  148. data/models/edges/payment.rb +0 -102
  149. data/models/invoice.rb +0 -113
  150. data/models/nodes/node/lightning.rb +0 -31
  151. data/models/nodes/node/platform.rb +0 -48
  152. data/models/nodes/node.rb +0 -143
  153. data/models/rate.rb +0 -35
  154. data/models/secret.rb +0 -55
  155. data/models/transaction.rb +0 -39
  156. data/models/wallet.rb +0 -40
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ require_relative '../../ports/dsl/lighstorm/errors'
6
+
7
+ module Lighstorm
8
+ module Adapter
9
+ module Lightning
10
+ class Invoice
11
+ def self.add_invoice(grpc)
12
+ {
13
+ _source: :add_invoice,
14
+ code: grpc[:payment_request],
15
+ address: grpc[:payment_addr].unpack1('H*'),
16
+ secret: {
17
+ hash: grpc[:r_hash].unpack1('H*')
18
+ }
19
+ }
20
+ end
21
+
22
+ def self.decode_pay_req(grpc, code = nil)
23
+ adapted = {
24
+ _source: :decode_pay_req,
25
+ _key: Digest::SHA256.hexdigest(
26
+ [
27
+ grpc[:payment_hash],
28
+ grpc[:num_satoshis],
29
+ grpc[:timestamp],
30
+ grpc[:payment_addr]
31
+ ].join('/')
32
+ ),
33
+ payable: 'once',
34
+ created_at: Time.at(grpc[:timestamp]),
35
+ expires_at: Time.at(grpc[:timestamp] + grpc[:expiry]),
36
+ amount: (grpc[:num_msat]).zero? ? nil : { millisatoshis: grpc[:num_msat] },
37
+ description: {
38
+ memo: grpc[:description].empty? ? nil : grpc[:description],
39
+ hash: grpc[:description_hash] == '' ? nil : grpc[:description_hash]
40
+ },
41
+ address: grpc[:payment_addr].unpack1('H*'),
42
+ secret: {
43
+ hash: grpc[:payment_hash]
44
+ }
45
+ }
46
+
47
+ adapted[:code] = code unless code.nil?
48
+
49
+ if grpc[:features].key?(30) && grpc[:features][30][:is_required]
50
+ raise "unexpected feature[30] name #{grpc[:features][30][:name]}" if grpc[:features][30][:name] != 'amp'
51
+
52
+ adapted[:payable] = 'indefinitely'
53
+ end
54
+
55
+ adapted
56
+ end
57
+
58
+ def self.lookup_invoice(grpc, at)
59
+ adapted = list_or_lookup_invoice(grpc, at)
60
+ adapted[:_source] = :lookup_invoice
61
+ adapted
62
+ end
63
+
64
+ def self.list_invoices(grpc, at)
65
+ adapted = list_or_lookup_invoice(grpc, at)
66
+ adapted[:_source] = :list_invoices
67
+ adapted
68
+ end
69
+
70
+ def self.list_or_lookup_invoice(grpc, at)
71
+ raise 'missing at' if at.nil?
72
+
73
+ adapted = {
74
+ _key: _key(grpc),
75
+ created_at: Time.at(grpc[:creation_date]),
76
+ expires_at: Time.at(grpc[:creation_date] + grpc[:expiry]),
77
+ settled_at: grpc[:settle_date].nil? || !grpc[:settle_date].positive? ? nil : Time.at(grpc[:settle_date]),
78
+ state: grpc[:state].to_s.downcase,
79
+ code: grpc[:payment_request].empty? ? nil : grpc[:payment_request],
80
+ payable: grpc[:is_amp] == true ? 'indefinitely' : 'once',
81
+ description: {
82
+ memo: grpc[:memo].empty? ? nil : grpc[:memo],
83
+ hash: grpc[:description_hash].empty? ? nil : grpc[:description_hash]
84
+ },
85
+ address: grpc[:payment_addr].unpack1('H*'),
86
+ secret: {
87
+ proof: grpc[:r_preimage].empty? ? nil : grpc[:r_preimage].unpack1('H*'),
88
+ hash: grpc[:r_hash].empty? ? nil : grpc[:r_hash].unpack1('H*')
89
+ }
90
+ }
91
+
92
+ adapted[:amount] = { millisatoshis: grpc[:value_msat] } if grpc[:value_msat] != 0
93
+
94
+ adapted[:received] = { millisatoshis: grpc[:amt_paid_msat] } if grpc[:amt_paid_msat] != 0
95
+
96
+ if adapted[:state] == 'settled' &&
97
+ adapted[:payable] == 'indefinitely' &&
98
+ adapted[:expires_at] > at
99
+ adapted[:state] = 'open'
100
+ end
101
+
102
+ raise 'todo: received with canceled' if !adapted[:received].nil? && adapted[:state] == 'canceled'
103
+
104
+ adapted[:payments] = []
105
+
106
+ grpc[:htlcs].each do |htlc|
107
+ next unless htlc[:state] == :SETTLED
108
+
109
+ payment = {
110
+ amount: { millisatoshis: htlc[:amt_msat] },
111
+ hops: [{ channel: { id: htlc[:chan_id] } }],
112
+ at: Time.at(htlc[:resolve_time])
113
+ }
114
+
115
+ if grpc[:is_amp]
116
+ payment[:secret] = {
117
+ proof: htlc[:amp][:preimage].unpack1('H*'),
118
+ hash: htlc[:amp][:hash].unpack1('H*')
119
+ }
120
+ end
121
+
122
+ # https://github.com/satoshisstream/satoshis.stream/blob/main/TLV_registry.md
123
+ if htlc[:custom_records][34_349_334]
124
+ payment[:message] = htlc[:custom_records][34_349_334].dup
125
+
126
+ unless payment[:message].force_encoding('UTF-8').valid_encoding?
127
+ payment[:message] = payment[:message].unpack1('H*')
128
+
129
+ unless payment[:message].force_encoding('UTF-8').valid_encoding?
130
+ payment[:message] = payment[:message].scrub('?')
131
+ end
132
+ end
133
+ end
134
+
135
+ adapted[:payments] << payment
136
+ end
137
+
138
+ adapted[:payments] = adapted[:payments].sort_by { |payment| -payment[:at].to_i }
139
+
140
+ adapted.delete(:payments) if adapted[:payments].empty?
141
+
142
+ adapted
143
+ end
144
+
145
+ def self.list_payments(grpc, invoice_decode = nil)
146
+ data = {
147
+ _key: _key(grpc),
148
+ _source: :list_payments,
149
+ created_at: Time.at(grpc[:creation_date]),
150
+ settled_at: grpc[:settle_date].nil? || !grpc[:settle_date].positive? ? nil : Time.at(grpc[:settle_date]),
151
+ state: nil,
152
+ payable: grpc[:is_amp] == true ? 'indefinitely' : 'once',
153
+ code: grpc[:payment_request].empty? ? nil : grpc[:payment_request],
154
+ amount: { millisatoshis: grpc[:value_msat] },
155
+ description: {
156
+ memo: grpc[:memo],
157
+ hash: grpc[:description_hash] == '' ? nil : grpc[:description_hash]
158
+ },
159
+ secret: {
160
+ proof: grpc[:payment_preimage],
161
+ hash: grpc[:payment_hash]
162
+ }
163
+ }
164
+
165
+ unless invoice_decode.nil?
166
+ data[:payable] = invoice_decode[:payable]
167
+ data[:expires_at] = invoice_decode[:expires_at]
168
+ end
169
+
170
+ data
171
+ end
172
+
173
+ def self._key(grpc)
174
+ Digest::SHA256.hexdigest(
175
+ [
176
+ grpc[:creation_date],
177
+ grpc[:settle_date],
178
+ grpc[:payment_request],
179
+ grpc[:state]
180
+ ].join('/')
181
+ )
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Lighstorm
6
+ module Adapter
7
+ module Lightning
8
+ class Node
9
+ def self._key(public_key)
10
+ Digest::SHA256.hexdigest(
11
+ [public_key].join('/')
12
+ )
13
+ end
14
+
15
+ def self.get_chan_info(grpc, index)
16
+ {
17
+ _source: :get_chan_info,
18
+ _key: _key(grpc[:"node#{index}_pub"]),
19
+ public_key: grpc[:"node#{index}_pub"]
20
+ }
21
+ end
22
+
23
+ def self.list_channels(grpc, key)
24
+ data = {
25
+ _source: :list_channels,
26
+ _key: _key(key == :remote ? grpc[:remote_pubkey] : nil),
27
+ public_key: key == :remote ? grpc[:remote_pubkey] : nil
28
+ }
29
+
30
+ return nil if data[:public_key].nil?
31
+
32
+ data
33
+ end
34
+
35
+ def self.get_info(grpc)
36
+ data = {
37
+ _source: :get_info,
38
+ _key: _key(grpc[:identity_pubkey]),
39
+ public_key: grpc[:identity_pubkey],
40
+ alias: grpc[:alias],
41
+ color: grpc[:color],
42
+ platform: {
43
+ blockchain: grpc[:chains][0][:chain],
44
+ network: grpc[:chains][0][:network],
45
+ lightning: {
46
+ implementation: 'lnd',
47
+ version: grpc[:version]
48
+ }
49
+ }
50
+ }
51
+
52
+ return nil if data[:public_key].nil? && data[:alias].nil? && data[:color].nil?
53
+
54
+ data
55
+ end
56
+
57
+ def self.get_node_info(grpc)
58
+ data = {
59
+ _source: :get_node_info,
60
+ _key: _key(grpc[:node][:pub_key]),
61
+ public_key: grpc[:node][:pub_key],
62
+ alias: grpc[:node][:alias],
63
+ color: grpc[:node][:color]
64
+ }
65
+
66
+ return nil if data[:public_key].nil? && data[:alias].nil? && data[:color].nil?
67
+
68
+ data
69
+ end
70
+
71
+ def self.describe_graph(grpc)
72
+ data = {
73
+ _source: :describe_graph,
74
+ _key: _key(grpc[:pub_key]),
75
+ public_key: grpc[:pub_key],
76
+ alias: grpc[:alias],
77
+ color: grpc[:color]
78
+ }
79
+
80
+ return nil if data[:public_key].nil? && data[:alias].nil? && data[:color].nil?
81
+
82
+ data
83
+ end
84
+
85
+ def self.describe_graph_from_channel(grpc, index)
86
+ data = {
87
+ _source: :describe_graph,
88
+ _key: _key(grpc[:"node#{index}_pub"]),
89
+ public_key: grpc[:"node#{index}_pub"]
90
+ }
91
+
92
+ return nil if data[:public_key].nil?
93
+
94
+ data
95
+ end
96
+
97
+ def self.subscribe_channel_graph(json)
98
+ data = {
99
+ _source: :subscribe_channel_graph,
100
+ _key: _key(json['identityKey']),
101
+ public_key: json['identityKey'],
102
+ alias: json['alias'],
103
+ color: json['color']
104
+ }
105
+
106
+ return nil if data[:public_key].nil? && data[:alias].nil? && data[:color].nil?
107
+
108
+ data
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lighstorm
4
- module Controllers
4
+ module Controller
5
5
  module Action
6
6
  Output = Struct.new(:data) do
7
7
  def request
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../ports/grpc'
4
+ require_relative '../../../../adapters/bitcoin/address'
5
+ require_relative '../../../../models/bitcoin/address'
6
+ require_relative '../../../action'
7
+
8
+ module Lighstorm
9
+ module Controller
10
+ module Bitcoin
11
+ module Address
12
+ module Create
13
+ def self.call(components, grpc_request)
14
+ {
15
+ at: Time.now,
16
+ new_address: components[:grpc].send(grpc_request[:service]).send(
17
+ grpc_request[:method], grpc_request[:params]
18
+ ).to_h
19
+ }
20
+ end
21
+
22
+ def self.prepare
23
+ # WITNESS_PUBKEY_HASH (0)
24
+ # A modern and efficient Bitcoin address type. It uses less space in transactions,
25
+ # so it has lower fees. Addresses usually start with "bc1" (mainnet) or "tb1" (testnet).
26
+
27
+ # NESTED_PUBKEY_HASH (1)
28
+ # \A backward-compatible version of the modern address. It works with older wallets and
29
+ # services but has slightly higher fees. Addresses usually start with "3" (mainnet)
30
+ # or "2" (testnet).
31
+
32
+ # UNUSED_WITNESS_PUBKEY_HASH (2)
33
+ # An unused version of the modern address. It's like having a reserved spot for a future
34
+ # address, but it's not being used right now.
35
+
36
+ # UNUSED_NESTED_PUBKEY_HASH (3)
37
+ # An unused version of the backward-compatible address. Similar to the unused modern
38
+ # address, it's a reserved spot for a future address but not in use currently.
39
+
40
+ # TAPROOT_PUBKEY (4): A new type of Bitcoin address that comes with the Taproot upgrade.
41
+ # It improves privacy and supports more complex transactions (like smart contracts).
42
+ # Addresses usually start with "bc1p" (mainnet) or "tb1p" (testnet).
43
+
44
+ # UNUSED_TAPROOT_PUBKEY (5)
45
+ # An unused version of the Taproot address. Like the other unused addresses, it's a
46
+ # reserved spot for a future address but not in use at the moment.
47
+ {
48
+ service: :lightning,
49
+ method: :new_address,
50
+ params: {
51
+ type: :WITNESS_PUBKEY_HASH
52
+ }
53
+ }
54
+ end
55
+
56
+ def self.dispatch(components, grpc_request, &vcr)
57
+ if vcr.nil?
58
+ call(components, grpc_request)
59
+ else
60
+ vcr.call(-> { call(components, grpc_request) }, :dispatch)
61
+ end
62
+ end
63
+
64
+ def self.adapt(response)
65
+ Lighstorm::Adapter::Bitcoin::Address.new_address(response[:new_address]).merge(
66
+ created_at: response[:at]
67
+ )
68
+ end
69
+
70
+ def self.model(data, components)
71
+ Model::Bitcoin::Address.new(data, components)
72
+ end
73
+
74
+ def self.perform(components, preview: false, &vcr)
75
+ grpc_request = prepare
76
+
77
+ return grpc_request if preview
78
+
79
+ response = dispatch(components, grpc_request, &vcr)
80
+
81
+ adapted = adapt(response)
82
+
83
+ model = self.model(adapted, components)
84
+
85
+ Action::Output.new({ request: grpc_request, response: response[:new_address], result: model })
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../ports/grpc'
4
+ require_relative '../../../../models/errors'
5
+ require_relative '../../../../adapters/bitcoin/transaction'
6
+ require_relative '../../../../controllers/bitcoin/transaction/all'
7
+ require_relative '../../../action'
8
+
9
+ module Lighstorm
10
+ module Controller
11
+ module Bitcoin
12
+ module Address
13
+ module Pay
14
+ def self.call(components, grpc_request)
15
+ response = components[:grpc].send(grpc_request[:service]).send(
16
+ grpc_request[:method], grpc_request[:params]
17
+ ).to_h
18
+
19
+ { response: response, exception: nil }
20
+ rescue StandardError => e
21
+ { exception: e }
22
+ end
23
+
24
+ def self.prepare(address_code:, amount:, fee:, description:, required_confirmations:)
25
+ # required_confirmations:
26
+ # 1: Fast and low-value transactions, such as buying a cup of coffee
27
+ # 2: Low-value transactions that require faster processing, such as online purchases
28
+ # 3: Moderate-value transactions, such as purchasing a computer or paying a bill
29
+ # 6: High-value transactions, such as buying a car or real estate
30
+ # 12+: Extremely high-value transactions, such as purchasing a private island or artwork
31
+
32
+ # GRPC::Unknown: 2:transaction output is dust.
33
+ # dust_limit = 3 * 25 * 31 = 2325 satoshis
34
+ # This formula doesn't seem to be working...
35
+ request = {
36
+ service: :lightning,
37
+ method: :send_coins,
38
+ params: {
39
+ addr: address_code,
40
+ amount: (amount[:millisatoshis].to_f / 1000.0).to_i,
41
+ sat_per_vbyte: fee[:satoshis_per_vitual_byte],
42
+ min_confs: required_confirmations
43
+ }
44
+ }
45
+
46
+ request[:params][:label] = description if !description.nil? && !description.empty?
47
+
48
+ request
49
+ end
50
+
51
+ def self.dispatch(components, grpc_request, &vcr)
52
+ if vcr.nil?
53
+ call(components, grpc_request)
54
+ else
55
+ vcr.call(-> { call(components, grpc_request) }, :dispatch)
56
+ end
57
+ end
58
+
59
+ def self.adapt(response)
60
+ Adapter::Bitcoin::Transaction.send_coins(response[:response])
61
+ end
62
+
63
+ def self.fetch(components, adapted, &vcr)
64
+ Controller::Bitcoin::Transaction::All.data(
65
+ components, hash: adapted[:hash], limit: 1, &vcr
66
+ )
67
+ end
68
+
69
+ def self.model(data)
70
+ Controller::Bitcoin::Transaction::All.model(data)
71
+ end
72
+
73
+ def self.perform(
74
+ components,
75
+ address_code:, amount:, fee:, description: nil, required_confirmations: 6,
76
+ preview: false, &vcr
77
+ )
78
+ grpc_request = prepare(
79
+ address_code: address_code,
80
+ amount: amount,
81
+ fee: fee,
82
+ description: description,
83
+ required_confirmations: required_confirmations
84
+ )
85
+
86
+ return grpc_request if preview
87
+
88
+ response = dispatch(components, grpc_request, &vcr)
89
+
90
+ raise_error_if_exists!(grpc_request, response)
91
+
92
+ adapted = adapt(response)
93
+
94
+ data = fetch(components, adapted, &vcr)
95
+ model = self.model(data).first
96
+
97
+ Action::Output.new({ request: grpc_request, response: response[:response], result: model })
98
+ end
99
+
100
+ def self.raise_error_if_exists!(request, response)
101
+ return if response[:exception].nil?
102
+
103
+ if response[:exception].message =~ /transaction output is dust/
104
+ raise AmountBelowDustLimitError.new(
105
+ "Amount is too low and considered dust (#{request[:params][:amount]} satoshis).",
106
+ request: request, grpc: response[:exception]
107
+ )
108
+ end
109
+
110
+ raise RequestError.new(
111
+ response[:exception].message,
112
+ request: request, grpc: response[:exception]
113
+ )
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../concerns/impersonatable'
4
+ require_relative './address/actions/create'
5
+
6
+ module Lighstorm
7
+ module Controller
8
+ module Bitcoin
9
+ module Address
10
+ extend Impersonatable
11
+
12
+ class DSL < Impersonatable::DSL
13
+ def new(code:)
14
+ Model::Bitcoin::Address.new({ code: code }, components)
15
+ end
16
+
17
+ def create(preview: false, &vcr)
18
+ Create.perform(components, preview: preview, &vcr)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../ports/grpc'
4
+ require_relative '../../../adapters/bitcoin/transaction'
5
+ require_relative '../../../models/bitcoin/transaction'
6
+
7
+ module Lighstorm
8
+ module Controller
9
+ module Bitcoin
10
+ module Transaction
11
+ module All
12
+ def self.fetch(components, hash: nil, limit: nil)
13
+ at = Time.now
14
+
15
+ transactions = []
16
+
17
+ response = components[:grpc].lightning.get_transactions
18
+
19
+ response.transactions.each do |transaction|
20
+ next unless hash.nil? || transaction.to_h[:tx_hash] == hash
21
+
22
+ transactions << transaction.to_h
23
+ end
24
+
25
+ transactions = transactions.sort_by { |raw_transaction| -raw_transaction[:time_stamp] }
26
+
27
+ transactions = transactions[0..limit - 1] unless limit.nil?
28
+
29
+ { at: at, get_transactions: transactions }
30
+ end
31
+
32
+ def self.adapt(raw)
33
+ {
34
+ get_transactions: raw[:get_transactions].map do |raw_transaction|
35
+ Adapter::Bitcoin::Transaction.get_transactions(raw_transaction)
36
+ end
37
+ }
38
+ end
39
+
40
+ def self.transform(adapted)
41
+ adapted[:get_transactions]
42
+ end
43
+
44
+ def self.data(components, hash: nil, limit: nil, &vcr)
45
+ raw = if vcr.nil?
46
+ fetch(components, hash: hash, limit: limit)
47
+ else
48
+ vcr.call(-> { fetch(components, hash: hash, limit: limit) })
49
+ end
50
+
51
+ adapted = adapt(raw)
52
+
53
+ transform(adapted)
54
+ end
55
+
56
+ def self.model(data)
57
+ data.map do |transaction_data|
58
+ Model::Bitcoin::Transaction.new(transaction_data)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../concerns/impersonatable'
4
+
5
+ require_relative './transaction/all'
6
+
7
+ module Lighstorm
8
+ module Controller
9
+ module Bitcoin
10
+ module Transaction
11
+ extend Impersonatable
12
+
13
+ class DSL < Impersonatable::DSL
14
+ def all(limit: nil)
15
+ All.model(All.data(components, limit: limit))
16
+ end
17
+
18
+ def find_by_hash(hash, &vcr)
19
+ All.model(All.data(components, hash: hash, limit: 1, &vcr)).first
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,9 +3,11 @@
3
3
  require_relative '../../ports/grpc'
4
4
 
5
5
  module Lighstorm
6
- module Controllers
6
+ module Controller
7
7
  module Impersonatable
8
8
  class DSL
9
+ alias _send send
10
+
9
11
  attr_reader :components
10
12
 
11
13
  def initialize(components = nil)
@@ -19,9 +21,9 @@ module Lighstorm
19
21
 
20
22
  def method_missing(method_name, *args, &block)
21
23
  if args.size == 1 && args.first.is_a?(Hash)
22
- self::DSL.new.send(method_name, **args.first, &block)
24
+ self::DSL.new._send(method_name, **args.first, &block)
23
25
  else
24
- self::DSL.new.send(method_name, *args, &block)
26
+ self::DSL.new._send(method_name, *args, &block)
25
27
  end
26
28
  end
27
29
 
@@ -3,7 +3,7 @@
3
3
  require_relative './concerns/impersonatable'
4
4
 
5
5
  module Lighstorm
6
- module Controllers
6
+ module Controller
7
7
  module Connection
8
8
  def self.connect!(...)
9
9
  LND.instance.connect!(...)