bsv-sdk 0.19.1 → 0.22.0

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -0
  3. data/README.md +2 -2
  4. data/lib/bsv/auth/transport.rb +1 -1
  5. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
  6. data/lib/bsv/network/protocol.rb +4 -5
  7. data/lib/bsv/network/protocols/arc.rb +4 -30
  8. data/lib/bsv/network/protocols/arcade.rb +163 -0
  9. data/lib/bsv/network/protocols/chaintracks.rb +6 -3
  10. data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
  11. data/lib/bsv/network/protocols.rb +1 -0
  12. data/lib/bsv/network/provider.rb +7 -9
  13. data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
  14. data/lib/bsv/network/util.rb +44 -0
  15. data/lib/bsv/network.rb +1 -0
  16. data/lib/bsv/overlay/lookup_resolver.rb +0 -1
  17. data/lib/bsv/overlay/topic_broadcaster.rb +0 -1
  18. data/lib/bsv/primitives/curve.rb +1 -11
  19. data/lib/bsv/primitives/ecies.rb +1 -8
  20. data/lib/bsv/primitives/hex.rb +1 -1
  21. data/lib/bsv/script/script.rb +1 -1
  22. data/lib/bsv/transaction/beef.rb +0 -2
  23. data/lib/bsv/transaction/chain_tracker.rb +74 -13
  24. data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +3 -3
  25. data/lib/bsv/transaction/chain_trackers.rb +0 -10
  26. data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
  27. data/lib/bsv/transaction/merkle_path.rb +0 -2
  28. data/lib/bsv/version.rb +1 -1
  29. data/lib/bsv/wallet/errors.rb +65 -21
  30. data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
  31. data/lib/bsv/wallet/proto_wallet.rb +15 -8
  32. data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
  33. data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
  34. data/lib/bsv/wallet/serializer/certificate.rb +184 -0
  35. data/lib/bsv/wallet/serializer/common.rb +207 -0
  36. data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
  37. data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
  38. data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
  39. data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
  40. data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
  41. data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
  42. data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
  43. data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
  44. data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
  45. data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
  46. data/lib/bsv/wallet/serializer/get_height.rb +46 -0
  47. data/lib/bsv/wallet/serializer/get_network.rb +65 -0
  48. data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
  49. data/lib/bsv/wallet/serializer/get_version.rb +44 -0
  50. data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
  51. data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
  52. data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
  53. data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
  54. data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
  55. data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
  56. data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
  57. data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
  58. data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
  59. data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
  60. data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
  61. data/lib/bsv/wallet/serializer/status.rb +85 -0
  62. data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
  63. data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
  64. data/lib/bsv/wallet/serializer.rb +180 -0
  65. data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
  66. data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
  67. data/lib/bsv/wallet/wallet_wire.rb +20 -0
  68. data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
  69. data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
  70. data/lib/bsv/wallet/wire/calls.rb +79 -0
  71. data/lib/bsv/wallet/wire/frame.rb +181 -0
  72. data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
  73. data/lib/bsv/wallet/wire/validation.rb +213 -0
  74. data/lib/bsv/wallet/wire.rb +13 -0
  75. data/lib/bsv/wallet.rb +17 -0
  76. metadata +47 -3
  77. data/lib/bsv/transaction/chain_trackers/chaintracks.rb +0 -83
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Serializer
6
+ # BRC-103 serialiser for list_actions (call byte 4).
7
+ #
8
+ # Wire layout (port of go-sdk/wallet/serializer/list_actions.go):
9
+ #
10
+ # Args:
11
+ # [string_slice] labels
12
+ # [1 byte] label_query_mode: 0x01=any, 0x02=all, 0xFF=absent
13
+ # [optional_bool] include_labels
14
+ # [optional_bool] include_inputs
15
+ # [optional_bool] include_input_source_locking_scripts
16
+ # [optional_bool] include_input_unlocking_scripts
17
+ # [optional_bool] include_outputs
18
+ # [optional_bool] include_output_locking_scripts
19
+ # [optional_uint32] limit
20
+ # [optional_uint32] offset
21
+ # [optional_bool] seek_permission
22
+ #
23
+ # Result:
24
+ # [varint] total_actions
25
+ # For each action:
26
+ # [32 bytes] txid (display order — reversed from wire storage)
27
+ # [varint] satoshis (int64 as varint)
28
+ # [1 byte] status code
29
+ # [optional_bool ptr] is_outgoing (written as optional_bool pointer in Go)
30
+ # [string] description
31
+ # [string_slice] labels
32
+ # [varint] version
33
+ # [varint] lock_time
34
+ # [inputs] NegativeOne | varint count + input_record…
35
+ # [outputs] NegativeOne | varint count + output_record…
36
+ #
37
+ # Input record:
38
+ # [36 bytes] source_outpoint (32-byte wire txid + varint vout)
39
+ # [varint] source_satoshis
40
+ # [int_bytes_optional] source_locking_script
41
+ # [int_bytes_optional] unlocking_script
42
+ # [string] input_description
43
+ # [varint] sequence_number
44
+ #
45
+ # Output record:
46
+ # [varint] output_index
47
+ # [varint] satoshis
48
+ # [int_bytes_optional] locking_script
49
+ # [optional_bool ptr] spendable
50
+ # [string] output_description
51
+ # [string] basket
52
+ # [string_slice] tags
53
+ # [optional_string] custom_instructions
54
+ module ListActionsArgs
55
+ LABEL_QUERY_MODE_ANY = 1
56
+ LABEL_QUERY_MODE_ALL = 2
57
+
58
+ module_function
59
+
60
+ # @param args [Hash]
61
+ # @return [String] binary
62
+ def serialize(args)
63
+ w = Wire::Writer.new
64
+
65
+ w.write_string_slice(args[:labels])
66
+
67
+ case args[:label_query_mode]
68
+ when :any
69
+ w.write_byte(LABEL_QUERY_MODE_ANY)
70
+ when :all
71
+ w.write_byte(LABEL_QUERY_MODE_ALL)
72
+ else
73
+ w.write_byte(0xFF)
74
+ end
75
+
76
+ w.write_optional_bool(args[:include_labels])
77
+ w.write_optional_bool(args[:include_inputs])
78
+ w.write_optional_bool(args[:include_input_source_locking_scripts])
79
+ w.write_optional_bool(args[:include_input_unlocking_scripts])
80
+ w.write_optional_bool(args[:include_outputs])
81
+ w.write_optional_bool(args[:include_output_locking_scripts])
82
+
83
+ w.write_optional_uint32(args[:limit])
84
+ w.write_optional_uint32(args[:offset])
85
+ w.write_optional_bool(args[:seek_permission])
86
+
87
+ w.buf
88
+ end
89
+
90
+ # @param bytes [String] binary
91
+ # @return [Hash]
92
+ def deserialize(bytes)
93
+ r = Wire::Reader.new(bytes)
94
+
95
+ labels = r.read_string_slice
96
+
97
+ mode_byte = r.read_byte
98
+ label_query_mode = case mode_byte
99
+ when LABEL_QUERY_MODE_ANY then :any
100
+ when LABEL_QUERY_MODE_ALL then :all
101
+ end
102
+
103
+ result = {}
104
+ result[:labels] = labels unless labels.nil?
105
+ result[:label_query_mode] = label_query_mode unless label_query_mode.nil?
106
+
107
+ v = r.read_optional_bool
108
+ result[:include_labels] = v unless v.nil?
109
+ v = r.read_optional_bool
110
+ result[:include_inputs] = v unless v.nil?
111
+ v = r.read_optional_bool
112
+ result[:include_input_source_locking_scripts] = v unless v.nil?
113
+ v = r.read_optional_bool
114
+ result[:include_input_unlocking_scripts] = v unless v.nil?
115
+ v = r.read_optional_bool
116
+ result[:include_outputs] = v unless v.nil?
117
+ v = r.read_optional_bool
118
+ result[:include_output_locking_scripts] = v unless v.nil?
119
+
120
+ lim = r.read_optional_uint32
121
+ result[:limit] = lim unless lim.nil?
122
+ off = r.read_optional_uint32
123
+ result[:offset] = off unless off.nil?
124
+
125
+ v = r.read_optional_bool
126
+ result[:seek_permission] = v unless v.nil?
127
+
128
+ result
129
+ end
130
+ end
131
+
132
+ module ListActionsResult
133
+ ACTION_STATUS_CODES = {
134
+ completed: 1,
135
+ unprocessed: 2,
136
+ sending: 3,
137
+ unproven: 4,
138
+ unsigned: 5,
139
+ no_send: 6,
140
+ non_final: 7
141
+ }.freeze
142
+
143
+ ACTION_CODE_STATUSES = ACTION_STATUS_CODES.invert.freeze
144
+
145
+ module_function
146
+
147
+ # @param result [Hash] { total_actions: Integer, actions: Array<Hash> }
148
+ # @return [String] binary
149
+ def serialize(result)
150
+ w = Wire::Writer.new
151
+
152
+ actions = result[:actions] || []
153
+ w.write_varint(actions.length)
154
+
155
+ actions.each do |action|
156
+ txid_hex = action[:txid].to_s
157
+ w.write_bytes([txid_hex].pack('H*'))
158
+ w.write_varint(action[:satoshis].to_i)
159
+
160
+ code = ACTION_STATUS_CODES.fetch(action[:status]) do
161
+ raise ArgumentError, "invalid action status: #{action[:status]}"
162
+ end
163
+ w.write_byte(code)
164
+
165
+ # is_outgoing: non-optional bool written as optional_bool pointer in Go
166
+ w.write_byte(action[:is_outgoing] ? 1 : 0)
167
+
168
+ w.write_string(action[:description].to_s)
169
+ w.write_string_slice(action[:labels])
170
+ w.write_varint(action[:version].to_i)
171
+ w.write_varint(action[:lock_time].to_i)
172
+
173
+ serialize_action_inputs(w, action[:inputs])
174
+ serialize_action_outputs(w, action[:outputs])
175
+ end
176
+
177
+ w.buf
178
+ end
179
+
180
+ # @param bytes [String] binary
181
+ # @return [Hash]
182
+ def deserialize(bytes)
183
+ r = Wire::Reader.new(bytes)
184
+
185
+ total = r.read_varint
186
+ actions = total.times.map do
187
+ txid_hex = r.read_bytes(32).unpack1('H*')
188
+ satoshis = r.read_varint
189
+ code = r.read_byte
190
+ status = ACTION_CODE_STATUSES.fetch(code) do
191
+ raise ArgumentError, "invalid action status code: #{code}"
192
+ end
193
+
194
+ is_outgoing = r.read_byte == 1
195
+ description = r.read_string
196
+ labels = r.read_string_slice
197
+ version = r.read_varint
198
+ lock_time = r.read_varint
199
+
200
+ inputs = deserialize_action_inputs(r)
201
+ outputs = deserialize_action_outputs(r)
202
+
203
+ action = {
204
+ txid: txid_hex,
205
+ satoshis: satoshis,
206
+ status: status,
207
+ is_outgoing: is_outgoing,
208
+ description: description,
209
+ version: version,
210
+ lock_time: lock_time
211
+ }
212
+ action[:labels] = labels unless labels.nil?
213
+ action[:inputs] = inputs unless inputs.nil?
214
+ action[:outputs] = outputs unless outputs.nil?
215
+ action
216
+ end
217
+
218
+ { total_actions: total, actions: actions }
219
+ end
220
+
221
+ def serialize_action_inputs(writer, inputs)
222
+ if inputs.nil? || inputs.empty?
223
+ writer.write_negative_one
224
+ return
225
+ end
226
+
227
+ writer.write_varint(inputs.length)
228
+ inputs.each do |inp|
229
+ txid_hex, vout = inp[:source_outpoint].split('.')
230
+ writer.write_bytes([txid_hex].pack('H*'))
231
+ writer.write_varint(vout.to_i)
232
+
233
+ writer.write_varint(inp[:source_satoshis].to_i)
234
+
235
+ script = inp[:source_locking_script]
236
+ if script && !script.b.empty?
237
+ writer.write_int_bytes(script)
238
+ else
239
+ writer.write_negative_one
240
+ end
241
+
242
+ unlock_script = inp[:unlocking_script]
243
+ if unlock_script && !unlock_script.b.empty?
244
+ writer.write_int_bytes(unlock_script)
245
+ else
246
+ writer.write_negative_one
247
+ end
248
+
249
+ writer.write_string(inp[:input_description].to_s)
250
+ writer.write_varint(inp[:sequence_number].to_i)
251
+ end
252
+ end
253
+
254
+ def serialize_action_outputs(writer, outputs)
255
+ if outputs.nil? || outputs.empty?
256
+ writer.write_negative_one
257
+ return
258
+ end
259
+
260
+ writer.write_varint(outputs.length)
261
+ outputs.each do |out|
262
+ writer.write_varint(out[:output_index].to_i)
263
+ writer.write_varint(out[:satoshis].to_i)
264
+
265
+ script = out[:locking_script]
266
+ if script && !script.b.empty?
267
+ writer.write_int_bytes(script)
268
+ else
269
+ writer.write_negative_one
270
+ end
271
+
272
+ writer.write_byte(out[:spendable] ? 1 : 0)
273
+ writer.write_string(out[:output_description].to_s)
274
+ writer.write_string(out[:basket].to_s)
275
+ writer.write_string_slice(out[:tags])
276
+ writer.write_optional_string(out[:custom_instructions])
277
+ end
278
+ end
279
+
280
+ def deserialize_action_inputs(reader)
281
+ count = reader.read_varint
282
+ return nil if count == 0xFFFF_FFFF_FFFF_FFFF
283
+
284
+ count.times.map do
285
+ txid_hex = reader.read_bytes(32).unpack1('H*')
286
+ vout = reader.read_varint
287
+ source_outpoint = "#{txid_hex}.#{vout}"
288
+
289
+ source_satoshis = reader.read_varint
290
+
291
+ source_locking_script = reader.read_int_bytes
292
+ source_locking_script = nil if source_locking_script.empty?
293
+
294
+ unlocking_script = reader.read_int_bytes
295
+ unlocking_script = nil if unlocking_script.empty?
296
+
297
+ input_description = reader.read_string
298
+ sequence_number = reader.read_varint
299
+
300
+ inp = {
301
+ source_outpoint: source_outpoint,
302
+ source_satoshis: source_satoshis,
303
+ input_description: input_description,
304
+ sequence_number: sequence_number
305
+ }
306
+ inp[:source_locking_script] = source_locking_script unless source_locking_script.nil?
307
+ inp[:unlocking_script] = unlocking_script unless unlocking_script.nil?
308
+ inp
309
+ end
310
+ end
311
+
312
+ def deserialize_action_outputs(reader)
313
+ count = reader.read_varint
314
+ return nil if count == 0xFFFF_FFFF_FFFF_FFFF
315
+
316
+ count.times.map do
317
+ output_index = reader.read_varint
318
+ satoshis = reader.read_varint
319
+
320
+ locking_script = reader.read_int_bytes
321
+ locking_script = nil if locking_script.empty?
322
+
323
+ spendable = reader.read_byte == 1
324
+ output_description = reader.read_string
325
+ basket = reader.read_string
326
+ tags = reader.read_string_slice
327
+ custom = reader.read_optional_string
328
+
329
+ out = {
330
+ output_index: output_index,
331
+ satoshis: satoshis,
332
+ spendable: spendable,
333
+ output_description: output_description,
334
+ basket: basket
335
+ }
336
+ out[:locking_script] = locking_script unless locking_script.nil?
337
+ out[:tags] = tags unless tags.nil?
338
+ out[:custom_instructions] = custom unless custom.nil?
339
+ out
340
+ end
341
+ end
342
+
343
+ private_class_method :serialize_action_inputs, :serialize_action_outputs,
344
+ :deserialize_action_inputs, :deserialize_action_outputs
345
+ end
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module BSV
6
+ module Wallet
7
+ module Serializer
8
+ # BRC-103 wire codec for the +list_certificates+ call (call byte 18).
9
+ #
10
+ # Args wire layout:
11
+ # [varint: certifier_count] per certifier: [33-byte pubkey]
12
+ # [varint: type_count] per type: [32-byte raw type]
13
+ # [optional_uint32: limit]
14
+ # [optional_uint32: offset]
15
+ # [privileged params]
16
+ #
17
+ # Result wire layout:
18
+ # [varint: total_certificates]
19
+ # per certificate:
20
+ # [varint-int: serialised Certificate bytes]
21
+ # [1 byte: keyring present flag (0/1)]
22
+ # If keyring present:
23
+ # [varint: keyring_count] per entry: [varint-str key][varint-int base64 bytes]
24
+ # [varint-int: verifier bytes]
25
+ module ListCertificates
26
+ CERT_TYPE_SIZE = 32
27
+ PUBKEY_SIZE = 33
28
+
29
+ module_function
30
+
31
+ def serialize_args(args)
32
+ w = Wire::Writer.new
33
+
34
+ certifiers = args[:certifiers] || []
35
+ w.write_varint(certifiers.length)
36
+ certifiers.each { |c| w.write_bytes([c.to_s].pack('H*')) }
37
+
38
+ types = args[:types] || []
39
+ w.write_varint(types.length)
40
+ types.each do |t|
41
+ type_bytes = Base64.strict_decode64(t.to_s)
42
+ w.write_bytes(type_bytes.ljust(CERT_TYPE_SIZE, "\x00").byteslice(0, CERT_TYPE_SIZE))
43
+ end
44
+
45
+ w.write_optional_uint32(args[:limit])
46
+ w.write_optional_uint32(args[:offset])
47
+ Common.write_privileged_params(w, args[:privileged], args[:privileged_reason])
48
+ w.buf
49
+ end
50
+
51
+ def deserialize_args(bytes)
52
+ r = Wire::Reader.new(bytes)
53
+
54
+ certifier_count = r.read_varint
55
+ certifiers = certifier_count.times.map { r.read_bytes(PUBKEY_SIZE).unpack1('H*') }
56
+
57
+ type_count = r.read_varint
58
+ types = type_count.times.map { Base64.strict_encode64(r.read_bytes(CERT_TYPE_SIZE)) }
59
+
60
+ limit = r.read_optional_uint32
61
+ offset = r.read_optional_uint32
62
+ privileged, privileged_reason = Common.read_privileged_params(r)
63
+
64
+ { certifiers: certifiers, types: types, limit: limit, offset: offset,
65
+ privileged: privileged, privileged_reason: privileged_reason }
66
+ end
67
+
68
+ def serialize_result(result)
69
+ w = Wire::Writer.new
70
+ certs = result[:certificates] || []
71
+ w.write_varint(certs.length)
72
+
73
+ certs.each do |cert_result|
74
+ cert = cert_result[:certificate] || cert_result
75
+ cert_bytes = Certificate.serialize_certificate(cert, include_signature: true)
76
+ w.write_int_bytes(cert_bytes)
77
+
78
+ keyring = cert_result[:keyring]
79
+ if keyring
80
+ w.write_byte(1)
81
+ w.write_varint(keyring.length)
82
+ keyring.keys.sort.each do |k|
83
+ w.write_str_with_varint_len(k)
84
+ w.write_int_from_base64(keyring[k].to_s)
85
+ end
86
+ else
87
+ w.write_byte(0)
88
+ end
89
+
90
+ verifier = cert_result[:verifier]
91
+ w.write_int_bytes(verifier ? verifier.b : ''.b)
92
+ end
93
+
94
+ w.buf
95
+ end
96
+
97
+ def deserialize_result(bytes)
98
+ r = Wire::Reader.new(bytes)
99
+ total = r.read_varint
100
+
101
+ certificates = total.times.map do
102
+ cert_bytes = r.read_int_bytes
103
+ cert = Certificate.deserialize_certificate(cert_bytes)
104
+
105
+ keyring = nil
106
+ if r.read_byte == 1
107
+ keyring_len = r.read_varint
108
+ keyring = {}
109
+ keyring_len.times do
110
+ k = r.read_str_with_varint_len
111
+ keyring[k] = r.read_base64_int
112
+ end
113
+ end
114
+
115
+ verifier = r.read_int_bytes
116
+ { certificate: cert, keyring: keyring, verifier: verifier.empty? ? nil : verifier }
117
+ end
118
+
119
+ { total_certificates: total, certificates: certificates }
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Serializer
6
+ # BRC-103 wire codec for the +list_outputs+ call (call byte 6).
7
+ #
8
+ # Args wire layout:
9
+ # [varint-str: basket]
10
+ # [string-slice: tags] nil → NegativeOne
11
+ # [1 byte: tag_query_mode] 1=all, 2=any, 0xFF=nil
12
+ # [1 byte: include] 1=locking_scripts, 2=entire_transactions, 0xFF=nil
13
+ # [optional_bool: include_custom_instructions]
14
+ # [optional_bool: include_tags]
15
+ # [optional_bool: include_labels]
16
+ # [optional_uint32: limit]
17
+ # [optional_uint32: offset]
18
+ # [optional_bool: seek_permission]
19
+ #
20
+ # Result wire layout:
21
+ # [varint: total_outputs]
22
+ # [varint: beef_len, or NegativeOne if nil][beef bytes]
23
+ # per output:
24
+ # [32-byte wire txid][varint vout]
25
+ # [varint: satoshis]
26
+ # [varint: locking_script_len, or NegativeOne][script bytes]
27
+ # [varint-str: custom_instructions] 0-length string if nil
28
+ # [string-slice: tags]
29
+ # [string-slice: labels]
30
+ module ListOutputs
31
+ TAG_QUERY_MODE_ALL = 1
32
+ TAG_QUERY_MODE_ANY = 2
33
+ INCLUDE_LOCKING_SCRIPTS = 1
34
+ INCLUDE_ENTIRE_TRANSACTIONS = 2
35
+ NEGATIVE_ONE_BYTE = 0xFF
36
+
37
+ module_function
38
+
39
+ def serialize_args(args)
40
+ w = Wire::Writer.new
41
+ w.write_str_with_varint_len(args.fetch(:basket, ''))
42
+ w.write_string_slice(args[:tags])
43
+
44
+ mode_byte = case args[:tag_query_mode]
45
+ when :all then TAG_QUERY_MODE_ALL
46
+ when :any then TAG_QUERY_MODE_ANY
47
+ else NEGATIVE_ONE_BYTE
48
+ end
49
+ w.write_byte(mode_byte)
50
+
51
+ include_byte = case args[:include]
52
+ when :locking_scripts then INCLUDE_LOCKING_SCRIPTS
53
+ when :entire_transactions then INCLUDE_ENTIRE_TRANSACTIONS
54
+ else NEGATIVE_ONE_BYTE
55
+ end
56
+ w.write_byte(include_byte)
57
+
58
+ w.write_optional_bool(args[:include_custom_instructions])
59
+ w.write_optional_bool(args[:include_tags])
60
+ w.write_optional_bool(args[:include_labels])
61
+ w.write_optional_uint32(args[:limit])
62
+ w.write_optional_uint32(args[:offset])
63
+ w.write_optional_bool(args[:seek_permission])
64
+ w.buf
65
+ end
66
+
67
+ def deserialize_args(bytes)
68
+ r = Wire::Reader.new(bytes)
69
+ basket = r.read_str_with_varint_len
70
+ tags = r.read_string_slice
71
+
72
+ mode = case r.read_byte
73
+ when TAG_QUERY_MODE_ALL then :all
74
+ when TAG_QUERY_MODE_ANY then :any
75
+ end
76
+
77
+ inc = case r.read_byte
78
+ when INCLUDE_LOCKING_SCRIPTS then :locking_scripts
79
+ when INCLUDE_ENTIRE_TRANSACTIONS then :entire_transactions
80
+ end
81
+
82
+ {
83
+ basket: basket,
84
+ tags: tags,
85
+ tag_query_mode: mode,
86
+ include: inc,
87
+ include_custom_instructions: r.read_optional_bool,
88
+ include_tags: r.read_optional_bool,
89
+ include_labels: r.read_optional_bool,
90
+ limit: r.read_optional_uint32,
91
+ offset: r.read_optional_uint32,
92
+ seek_permission: r.read_optional_bool
93
+ }
94
+ end
95
+
96
+ def serialize_result(result)
97
+ w = Wire::Writer.new
98
+ outputs = result[:outputs] || []
99
+ w.write_varint(outputs.length)
100
+
101
+ beef = result[:beef]
102
+ if beef
103
+ w.write_int_bytes(beef.b)
104
+ else
105
+ w.write_negative_one
106
+ end
107
+
108
+ outputs.each do |output|
109
+ outpoint = output[:outpoint] || ''
110
+ txid_hex, vout = outpoint.to_s.split('.', 2)
111
+ w.write_outpoint(txid_hex.to_s, vout.to_i)
112
+ w.write_varint(output[:satoshis].to_i)
113
+ locking_script = output[:locking_script]
114
+ if locking_script && !locking_script.empty?
115
+ w.write_int_bytes(locking_script.b)
116
+ else
117
+ w.write_negative_one
118
+ end
119
+ w.write_optional_string(output[:custom_instructions])
120
+ w.write_string_slice(output[:tags])
121
+ w.write_string_slice(output[:labels])
122
+ end
123
+ w.buf
124
+ end
125
+
126
+ def deserialize_result(bytes)
127
+ r = Wire::Reader.new(bytes)
128
+ total_outputs = r.read_varint
129
+
130
+ beef_len = r.read_varint
131
+ beef = if beef_len == 0xFFFF_FFFF_FFFF_FFFF
132
+ nil
133
+ else
134
+ r.read_bytes(beef_len)
135
+ end
136
+
137
+ outputs = total_outputs.times.map do
138
+ outpoint_data = r.read_outpoint
139
+ outpoint = "#{outpoint_data[:txid_hex]}.#{outpoint_data[:vout]}"
140
+ satoshis = r.read_varint
141
+ locking_script_len = r.read_varint
142
+ locking_script = if locking_script_len == 0xFFFF_FFFF_FFFF_FFFF
143
+ nil
144
+ else
145
+ r.read_bytes(locking_script_len)
146
+ end
147
+ custom_instructions = r.read_optional_string
148
+ tags = r.read_string_slice
149
+ labels = r.read_string_slice
150
+
151
+ {
152
+ outpoint: outpoint,
153
+ satoshis: satoshis,
154
+ locking_script: locking_script,
155
+ custom_instructions: custom_instructions,
156
+ tags: tags,
157
+ labels: labels,
158
+ spendable: true
159
+ }
160
+ end
161
+
162
+ { total_outputs: total_outputs, beef: beef, outputs: outputs }
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end