bsv-sdk 0.20.0 → 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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
  4. data/lib/bsv/network/protocols/arc.rb +4 -30
  5. data/lib/bsv/network/protocols/arcade.rb +163 -0
  6. data/lib/bsv/network/protocols/chaintracks.rb +6 -3
  7. data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
  8. data/lib/bsv/network/protocols.rb +1 -0
  9. data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
  10. data/lib/bsv/network/util.rb +44 -0
  11. data/lib/bsv/network.rb +1 -0
  12. data/lib/bsv/transaction/chain_tracker.rb +74 -13
  13. data/lib/bsv/transaction/chain_trackers.rb +0 -10
  14. data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
  15. data/lib/bsv/version.rb +1 -1
  16. data/lib/bsv/wallet/errors.rb +65 -21
  17. data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
  18. data/lib/bsv/wallet/proto_wallet.rb +14 -1
  19. data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
  20. data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
  21. data/lib/bsv/wallet/serializer/certificate.rb +184 -0
  22. data/lib/bsv/wallet/serializer/common.rb +207 -0
  23. data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
  24. data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
  25. data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
  26. data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
  27. data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
  28. data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
  29. data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
  30. data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
  31. data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
  32. data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
  33. data/lib/bsv/wallet/serializer/get_height.rb +46 -0
  34. data/lib/bsv/wallet/serializer/get_network.rb +65 -0
  35. data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
  36. data/lib/bsv/wallet/serializer/get_version.rb +44 -0
  37. data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
  38. data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
  39. data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
  40. data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
  41. data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
  42. data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
  43. data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
  44. data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
  45. data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
  46. data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
  47. data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
  48. data/lib/bsv/wallet/serializer/status.rb +85 -0
  49. data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
  50. data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
  51. data/lib/bsv/wallet/serializer.rb +180 -0
  52. data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
  53. data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
  54. data/lib/bsv/wallet/wallet_wire.rb +20 -0
  55. data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
  56. data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
  57. data/lib/bsv/wallet/wire/calls.rb +79 -0
  58. data/lib/bsv/wallet/wire/frame.rb +181 -0
  59. data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
  60. data/lib/bsv/wallet/wire/validation.rb +213 -0
  61. data/lib/bsv/wallet/wire.rb +13 -0
  62. data/lib/bsv/wallet.rb +17 -0
  63. metadata +46 -2
  64. data/lib/bsv/transaction/chain_trackers/chaintracks.rb +0 -83
@@ -0,0 +1,402 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module BSV
6
+ module Wallet
7
+ module Wire
8
+ # Binary read/write helpers with BRC-103 idioms.
9
+ #
10
+ # Thin wrappers over StringIO providing the specific encodings used across
11
+ # BRC-103 wire frames: varint strings, optional bools, satoshi LE uint64,
12
+ # and outpoints. Reuses BSV::Transaction::VarInt for the varint codec.
13
+
14
+ # Writer accumulates bytes into a binary string buffer.
15
+ class Writer
16
+ attr_reader :buf
17
+
18
+ def initialize
19
+ @buf = String.new(encoding: 'BINARY')
20
+ end
21
+
22
+ # Write a raw byte.
23
+ def write_byte(byte)
24
+ @buf << [byte].pack('C')
25
+ end
26
+
27
+ # Write raw bytes (binary string).
28
+ def write_bytes(bytes)
29
+ @buf << bytes.b
30
+ end
31
+
32
+ # Write a Bitcoin varint.
33
+ def write_varint(n)
34
+ @buf << BSV::Transaction::VarInt.encode(n)
35
+ end
36
+
37
+ # Write a UTF-8 string prefixed by its byte length as a varint.
38
+ def write_str_with_varint_len(str)
39
+ bytes = str.to_s.b
40
+ write_varint(bytes.bytesize)
41
+ write_bytes(bytes)
42
+ end
43
+
44
+ # Write an optional boolean as a single byte (Go/BRC-103 convention).
45
+ # nil → 0xFF, false → 0x00, true → 0x01
46
+ def write_optional_bool(value)
47
+ byte = case value
48
+ when nil then 0xFF
49
+ when false then 0x00
50
+ else 0x01
51
+ end
52
+ write_byte(byte)
53
+ end
54
+
55
+ # Write a satoshi amount as 8-byte little-endian uint64.
56
+ def write_satoshis(n)
57
+ @buf << [n].pack('Q<')
58
+ end
59
+
60
+ # Write an outpoint: 32-byte display-order txid followed by varint vout.
61
+ #
62
+ # Go encodeOutpoint calls WriteBytesReverse(Txid[:]) which writes the
63
+ # wire-order (chainhash) bytes reversed — i.e. display order on the wire.
64
+ # The vout is a varint, not a fixed 4-byte LE integer.
65
+ #
66
+ # @param txid_hex [String] 64-char display-order hex txid
67
+ # @param vout [Integer] output index
68
+ def write_outpoint(txid_hex, vout)
69
+ BSV::Primitives::Hex.validate_dtxid_hex!(txid_hex)
70
+ @buf << [txid_hex].pack('H*')
71
+ write_varint(vout)
72
+ end
73
+
74
+ # Write the NegativeOne sentinel (MaxUint64 as a 9-byte varint: 9 × 0xFF).
75
+ # Used as a nil sentinel for optional fields in Go-compatible encoding.
76
+ def write_negative_one
77
+ write_varint(0xFFFF_FFFF_FFFF_FFFF)
78
+ end
79
+
80
+ # Write a varint length-prefixed byte array (WriteIntBytes in Go).
81
+ # @param bytes [String, nil] binary string; nil or empty → write varint 0
82
+ def write_int_bytes(bytes)
83
+ raw = bytes ? bytes.b : ''.b
84
+ write_varint(raw.bytesize)
85
+ write_bytes(raw)
86
+ end
87
+
88
+ # Write an optional uint32: nil → NegativeOne; else varint.
89
+ # @param n [Integer, nil]
90
+ def write_optional_uint32(n)
91
+ if n.nil?
92
+ write_negative_one
93
+ else
94
+ write_varint(n)
95
+ end
96
+ end
97
+
98
+ # Write an optional string: nil or empty → NegativeOne; else varint len + bytes.
99
+ # Matches Go WriteOptionalString.
100
+ # @param str [String, nil]
101
+ def write_optional_string(str)
102
+ if str.nil? || str.empty?
103
+ write_negative_one
104
+ else
105
+ write_str_with_varint_len(str)
106
+ end
107
+ end
108
+
109
+ # Write an array of strings: nil → NegativeOne; else varint count + each as optional string.
110
+ # Matches Go WriteStringSlice.
111
+ # @param arr [Array<String>, nil]
112
+ def write_string_slice(arr)
113
+ if arr.nil?
114
+ write_negative_one
115
+ else
116
+ write_varint(arr.length)
117
+ arr.each { |s| write_optional_string(s) }
118
+ end
119
+ end
120
+
121
+ # Write a string map (Hash<String,String>) sorted by key.
122
+ # Matches Go WriteStringMap.
123
+ # @param map [Hash, nil]
124
+ def write_string_map(map)
125
+ m = map || {}
126
+ write_varint(m.length)
127
+ m.keys.sort.each do |k|
128
+ write_str_with_varint_len(k)
129
+ write_str_with_varint_len(m[k])
130
+ end
131
+ end
132
+
133
+ # Write a binary value encoded as a Base64 string on the wire.
134
+ # Decodes the Base64 string and writes the raw bytes prefixed by varint length.
135
+ # @param base64_str [String] standard Base64-encoded string
136
+ def write_int_from_base64(base64_str)
137
+ raw = Base64.strict_decode64(base64_str)
138
+ write_int_bytes(raw)
139
+ end
140
+
141
+ # Write the privileged flag and reason (Go encodePrivilegedParams).
142
+ # privileged: nil → NegativeOneByte (0xFF), false → 0x00, true → 0x01.
143
+ # reason: nil or empty → NegativeOneByte; else varint len + bytes.
144
+ def write_privileged_params(privileged, reason)
145
+ # Go OptionalBool: nil=0xFF, false=0x00, true=0x01
146
+ byte = case privileged
147
+ when nil then 0xFF
148
+ when false then 0x00
149
+ else 0x01
150
+ end
151
+ write_byte(byte)
152
+ if reason.nil? || reason.empty?
153
+ write_byte(0xFF)
154
+ else
155
+ write_str_with_varint_len(reason)
156
+ end
157
+ end
158
+
159
+ # Write a txid slice (array of 32-byte txids in wire order).
160
+ # nil → NegativeOne; else varint count + 32 raw bytes per txid.
161
+ # Txids are passed as 64-char display-order hex; written as-is (no byte reversal)
162
+ # to match Go WriteTxidSlice which writes txID[:] (wire-order bytes directly).
163
+ # @param txids [Array<String>, nil] 64-char display-order hex strings
164
+ def write_txid_slice(txids)
165
+ if txids.nil?
166
+ write_negative_one
167
+ else
168
+ write_varint(txids.length)
169
+ txids.each { |hex| write_bytes([hex].pack('H*')) }
170
+ end
171
+ end
172
+
173
+ # Write optional bytes with a 1-byte flag prefix (Go BytesOptionWithFlag).
174
+ # nil/empty → 0x00; else → 0x01 + varint_len + bytes (or fixed_size bytes).
175
+ # @param bytes [String, nil] binary data
176
+ # @param fixed_size [Integer, nil] if set, omit the varint length prefix
177
+ def write_optional_bytes_with_flag(bytes, fixed_size: nil)
178
+ raw = bytes ? bytes.b : ''.b
179
+ if raw.empty?
180
+ write_byte(0)
181
+ else
182
+ write_byte(1)
183
+ if fixed_size
184
+ write_bytes(raw)
185
+ else
186
+ write_int_bytes(raw)
187
+ end
188
+ end
189
+ end
190
+
191
+ # Write a varint-len string (always present, 0-length for nil/empty).
192
+ # Matches Go WriteString which always writes the length prefix.
193
+ # @param str [String, nil]
194
+ def write_string(str)
195
+ write_str_with_varint_len(str.to_s)
196
+ end
197
+ end
198
+
199
+ # Reader reads sequentially from a binary string.
200
+ class Reader
201
+ # @param data [String] binary data
202
+ def initialize(data)
203
+ @data = data.b
204
+ @pos = 0
205
+ end
206
+
207
+ # Read a single byte.
208
+ # @return [Integer]
209
+ def read_byte
210
+ raise ArgumentError, 'unexpected end of data reading byte' if @pos >= @data.bytesize
211
+
212
+ byte = @data.getbyte(@pos)
213
+ @pos += 1
214
+ byte
215
+ end
216
+
217
+ # Look at the next byte without consuming it.
218
+ # @return [Integer]
219
+ def peek_byte
220
+ raise ArgumentError, 'unexpected end of data peeking byte' if @pos >= @data.bytesize
221
+
222
+ @data.getbyte(@pos)
223
+ end
224
+
225
+ # Read +n+ raw bytes.
226
+ # @return [String] binary string
227
+ def read_bytes(n)
228
+ raise ArgumentError, "need #{n} bytes at offset #{@pos}, got #{remaining}" if remaining < n
229
+
230
+ slice = @data.byteslice(@pos, n)
231
+ @pos += n
232
+ slice
233
+ end
234
+
235
+ # Read a Bitcoin varint.
236
+ # @return [Integer]
237
+ def read_varint
238
+ value, consumed = BSV::Transaction::VarInt.decode(@data, @pos)
239
+ @pos += consumed
240
+ value
241
+ end
242
+
243
+ # Read a varint-prefixed UTF-8 string.
244
+ # @return [String]
245
+ # @raise [ArgumentError] if the bytes are not valid UTF-8
246
+ def read_str_with_varint_len
247
+ len = read_varint
248
+ str = read_bytes(len).force_encoding('UTF-8')
249
+ raise ArgumentError, 'varint-prefixed string is not valid UTF-8' unless str.valid_encoding?
250
+
251
+ str
252
+ end
253
+
254
+ # Read an optional boolean byte (Go/BRC-103 convention).
255
+ # 0xFF → nil, 0x00 → false, 0x01 → true
256
+ # @return [Boolean, nil]
257
+ def read_optional_bool
258
+ byte = read_byte
259
+ return nil if byte == 0xFF
260
+
261
+ byte == 0x01
262
+ end
263
+
264
+ # Read 8-byte little-endian uint64 satoshi amount.
265
+ # @return [Integer]
266
+ def read_satoshis
267
+ read_bytes(8).unpack1('Q<')
268
+ end
269
+
270
+ # Read an outpoint: 32-byte display-order txid + varint vout.
271
+ # @return [Hash] { txid_hex: String, vout: Integer }
272
+ def read_outpoint
273
+ txid_bytes = read_bytes(32)
274
+ vout = read_varint
275
+ { txid_hex: txid_bytes.unpack1('H*'), vout: vout }
276
+ end
277
+
278
+ # Remaining bytes.
279
+ def remaining
280
+ @data.bytesize - @pos
281
+ end
282
+
283
+ # Read all remaining bytes.
284
+ # @return [String] binary string
285
+ def read_remaining
286
+ slice = @data.byteslice(@pos, @data.bytesize - @pos) || ''.b
287
+ @pos = @data.bytesize
288
+ slice
289
+ end
290
+
291
+ # Whether the next varint is the NegativeOne sentinel (0xFFFF…).
292
+ # Peeks at the next byte without consuming it.
293
+ def next_negative_one?
294
+ @pos < @data.bytesize && @data.getbyte(@pos) == 0xFF
295
+ end
296
+
297
+ # Read a varint-length-prefixed byte array (ReadIntBytes in Go).
298
+ # @return [String] binary string
299
+ def read_int_bytes
300
+ len = read_varint
301
+ return ''.b if len == 0xFFFF_FFFF_FFFF_FFFF || len.zero?
302
+
303
+ read_bytes(len)
304
+ end
305
+
306
+ # Read an optional uint32: NegativeOne sentinel → nil; else varint → Integer.
307
+ # @return [Integer, nil]
308
+ def read_optional_uint32
309
+ val = read_varint
310
+ val == 0xFFFF_FFFF_FFFF_FFFF ? nil : val
311
+ end
312
+
313
+ # Read an optional string: NegativeOne sentinel → nil; else varint len + bytes.
314
+ # @return [String, nil]
315
+ def read_optional_string
316
+ val = read_varint
317
+ return nil if val == 0xFFFF_FFFF_FFFF_FFFF
318
+
319
+ read_bytes(val).force_encoding('UTF-8')
320
+ end
321
+
322
+ # Read an array of strings encoded as varint count + each optional string.
323
+ # NegativeOne sentinel count → nil.
324
+ # @return [Array<String>, nil]
325
+ def read_string_slice
326
+ count = read_varint
327
+ return nil if count == 0xFFFF_FFFF_FFFF_FFFF
328
+
329
+ count.times.map { read_optional_string || '' }
330
+ end
331
+
332
+ # Read a string map: varint count + key/value pairs (each varint-len-prefixed).
333
+ # @return [Hash<String,String>]
334
+ def read_string_map
335
+ count = read_varint
336
+ count.times.each_with_object({}) do |_, h|
337
+ k = read_str_with_varint_len
338
+ v = read_str_with_varint_len
339
+ h[k] = v
340
+ end
341
+ end
342
+
343
+ # Read a binary value and return it Base64-encoded.
344
+ # @return [String] Base64-encoded string
345
+ def read_base64_int
346
+ raw = read_int_bytes
347
+ Base64.strict_encode64(raw)
348
+ end
349
+
350
+ # Read privileged params (Go decodePrivilegedParams).
351
+ # @return [Array(Boolean|nil, String|nil)]
352
+ def read_privileged_params
353
+ privileged = read_optional_bool
354
+ first_byte = read_byte
355
+ if first_byte == 0xFF
356
+ [privileged, nil]
357
+ else
358
+ # Back up one byte and read as varint-prefixed string
359
+ @pos -= 1
360
+ reason = read_str_with_varint_len
361
+ [privileged, reason]
362
+ end
363
+ end
364
+
365
+ # Read a txid slice: NegativeOne → nil; else varint count + 32 bytes per txid.
366
+ # Go stores txids in wire order (txID[:]) — returned as hex without reversal.
367
+ # @return [Array<String>, nil]
368
+ def read_txid_slice
369
+ count = read_varint
370
+ return nil if count == 0xFFFF_FFFF_FFFF_FFFF
371
+
372
+ count.times.map { read_bytes(32).unpack1('H*') }
373
+ end
374
+
375
+ # Read optional bytes with a 1-byte flag prefix (Go BytesOptionWithFlag).
376
+ # 0x00 → nil; 0x01 → read varint_len + bytes (or fixed_size bytes).
377
+ # @param fixed_size [Integer, nil] if set, read exactly this many bytes (no varint)
378
+ # @return [String, nil] binary string or nil
379
+ def read_optional_bytes_with_flag(fixed_size: nil)
380
+ flag = read_byte
381
+ return nil if flag.zero?
382
+
383
+ if fixed_size
384
+ read_bytes(fixed_size)
385
+ else
386
+ read_int_bytes
387
+ end
388
+ end
389
+
390
+ # Read a varint-len string (always present, 0-length = empty string).
391
+ # Matches Go ReadString which returns "" for length 0 or NegativeOne.
392
+ # @return [String]
393
+ def read_string
394
+ len = read_varint
395
+ return '' if len.zero? || len == 0xFFFF_FFFF_FFFF_FFFF
396
+
397
+ read_bytes(len).force_encoding('UTF-8')
398
+ end
399
+ end
400
+ end
401
+ end
402
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Wire
6
+ # Branded-type validators for BRC-100 parameters.
7
+ #
8
+ # Each method raises InvalidParameterError on failure and returns nil
9
+ # on success. Port of ts-sdk/src/wallet/validationHelpers.ts.
10
+ #
11
+ # The existing ProtoWallet::Validators module delegates here so there
12
+ # is a single source of truth for all parameter validation logic.
13
+ module Validation
14
+ module_function
15
+
16
+ MAX_SATOSHIS = 21_000_000 * (10**8)
17
+
18
+ # Validates that +value+ is a hex string (even length, hex chars only).
19
+ #
20
+ # @param name [String] parameter name for error messages
21
+ # @param value [Object] the value to validate
22
+ # @param length [Integer, nil] expected number of hex chars (nil = any)
23
+ def hex_string!(name, value, length: nil)
24
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
25
+
26
+ raise InvalidParameterError.new(name, 'a hex string (characters 0-9, a-f, A-F only)') unless value.match?(/\A[0-9a-fA-F]*\z/)
27
+
28
+ raise InvalidParameterError.new(name, 'a hex string with even number of characters') if value.length.odd?
29
+
30
+ return unless length && value.length != length
31
+
32
+ raise InvalidParameterError.new(name, "a #{length}-character hex string, got #{value.length}")
33
+ end
34
+
35
+ # Validates that +value+ is a Base64-encoded string.
36
+ #
37
+ # @param name [String] parameter name for error messages
38
+ # @param value [Object] the value to validate
39
+ def base64_string!(name, value)
40
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
41
+
42
+ return if value.match?(%r{\A[A-Za-z0-9+/]*={0,2}\z})
43
+
44
+ raise InvalidParameterError.new(name, 'a valid Base64 string')
45
+ end
46
+
47
+ # Validates an outpoint string in the form "<64-hex-txid>.<vout>".
48
+ #
49
+ # @param name [String] parameter name for error messages
50
+ # @param value [Object] the value to validate
51
+ def outpoint_string!(name, value)
52
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
53
+
54
+ parts = value.split('.', 2)
55
+ raise InvalidParameterError.new(name, 'an outpoint string in the format <64-hex-txid>.<vout>') unless parts.length == 2
56
+
57
+ txid_hex, vout_str = parts
58
+
59
+ raise InvalidParameterError.new(name, 'an outpoint with a 64-character hex transaction ID') unless txid_hex.match?(/\A[0-9a-fA-F]{64}\z/)
60
+
61
+ raise InvalidParameterError.new(name, 'an outpoint with a non-negative integer output index') unless vout_str.match?(/\A\d+\z/)
62
+
63
+ vout = vout_str.to_i
64
+ return unless vout > 0xFFFFFFFF
65
+
66
+ raise InvalidParameterError.new(name, 'an outpoint with a vout that fits in a uint32 (0..4294967295)')
67
+ end
68
+
69
+ # Validates a compressed public key in hex form (66 chars, 02/03 prefix).
70
+ #
71
+ # @param name [String] parameter name for error messages
72
+ # @param value [Object] the value to validate
73
+ def pub_key_hex!(name, value)
74
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
75
+
76
+ return if value.match?(/\A0[23][0-9a-fA-F]{64}\z/)
77
+
78
+ raise InvalidParameterError.new(name, 'a 66-character compressed public key hex string (02 or 03 prefix)')
79
+ end
80
+
81
+ # Validates a description string (5..50 printable characters).
82
+ #
83
+ # @param name [String] parameter name for error messages
84
+ # @param value [Object] the value to validate
85
+ def description_5_to_50!(name, value)
86
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
87
+
88
+ len = value.length
89
+ raise InvalidParameterError.new(name, 'between 5 and 50 characters') if len < 5 || len > 50
90
+ end
91
+
92
+ # Validates a label string (1..150 characters, no spaces).
93
+ #
94
+ # @param name [String] parameter name for error messages
95
+ # @param value [Object] the value to validate
96
+ def label_string!(name, value)
97
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
98
+
99
+ len = value.length
100
+ raise InvalidParameterError.new(name, 'between 1 and 150 characters') if len < 1 || len > 150
101
+ end
102
+
103
+ # Validates a basket name (1..300 characters).
104
+ #
105
+ # @param name [String] parameter name for error messages
106
+ # @param value [Object] the value to validate
107
+ def basket_string!(name, value)
108
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
109
+
110
+ len = value.length
111
+ raise InvalidParameterError.new(name, 'between 1 and 300 characters') if len < 1 || len > 300
112
+ end
113
+
114
+ # Validates an originator domain (1..250 bytes UTF-8).
115
+ # The originator must fit in a single-byte length field in the wire frame.
116
+ #
117
+ # @param name [String] parameter name for error messages
118
+ # @param value [Object] the value to validate
119
+ def originator_domain!(name, value)
120
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
121
+
122
+ byte_len = value.bytesize
123
+ raise InvalidParameterError.new(name, 'at most 250 bytes') if byte_len > 250
124
+ end
125
+
126
+ # Validates a satoshi amount (0..21_000_000 * 10^8).
127
+ #
128
+ # @param name [String] parameter name for error messages
129
+ # @param value [Object] the value to validate
130
+ def satoshi_value!(name, value)
131
+ raise InvalidParameterError.new(name, 'a non-negative integer') unless value.is_a?(Integer)
132
+
133
+ return unless value.negative? || value > MAX_SATOSHIS
134
+
135
+ raise InvalidParameterError.new(name, "between 0 and #{MAX_SATOSHIS} satoshis")
136
+ end
137
+
138
+ # Validates a non-negative integer.
139
+ #
140
+ # @param name [String] parameter name for error messages
141
+ # @param value [Object] the value to validate
142
+ def positive_integer_or_zero!(name, value)
143
+ return if value.is_a?(Integer) && !value.negative?
144
+
145
+ raise InvalidParameterError.new(name, 'a non-negative integer')
146
+ end
147
+
148
+ # Validates a BRC-43 protocol name string (5..400 chars, lowercase).
149
+ #
150
+ # @param name [String] parameter name for error messages
151
+ # @param value [Object] the value to validate
152
+ def protocol_string_5_to_400!(name, value)
153
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
154
+
155
+ normalized = value.strip.downcase
156
+ max_length = normalized.start_with?('specific linkage revelation') ? 430 : 400
157
+
158
+ raise InvalidParameterError.new(name, "between 5 and #{max_length} characters") if normalized.length < 5 || normalized.length > max_length
159
+
160
+ raise InvalidParameterError.new(name, 'lowercase letters, numbers, and spaces only') unless normalized.match?(/\A[a-z0-9 ]+\z/)
161
+
162
+ return unless normalized.include?(' ')
163
+
164
+ raise InvalidParameterError.new(name, 'free of consecutive spaces')
165
+ end
166
+
167
+ # Validates a BRC-43 key ID string (1..800 bytes).
168
+ #
169
+ # @param name [String] parameter name for error messages
170
+ # @param value [Object] the value to validate
171
+ def key_id_string_1_to_800!(name, value)
172
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
173
+
174
+ byte_length = value.bytesize
175
+ raise InvalidParameterError.new(name, 'between 1 and 800 bytes') if byte_length < 1 || byte_length > 800
176
+ end
177
+
178
+ # Validates a wallet counterparty: 'self', 'anyone', or a 66-char hex pubkey.
179
+ #
180
+ # @param name [String] parameter name for error messages
181
+ # @param value [Object] the value to validate
182
+ def wallet_counterparty!(name, value)
183
+ return if %w[self anyone].include?(value)
184
+
185
+ pub_key_hex!(name, value)
186
+ end
187
+
188
+ # Validates a BRC-43 protocol ID: [security_level (0-2), protocol_name (5-400 chars)].
189
+ #
190
+ # @param name [String] parameter name for error messages
191
+ # @param value [Object] the value to validate
192
+ def wallet_protocol!(name, value)
193
+ raise InvalidParameterError.new(name, 'an Array of [security_level, protocol_name]') unless value.is_a?(Array) && value.length == 2
194
+
195
+ level, proto_name = value
196
+ raise InvalidParameterError.new(name, 'a security level of 0, 1, or 2') unless [0, 1, 2].include?(level)
197
+
198
+ protocol_string_5_to_400!("#{name} protocol name", proto_name)
199
+ end
200
+
201
+ # Validates a certificate acquisition protocol: 'direct' or 'issuance'.
202
+ #
203
+ # @param name [String] parameter name for error messages
204
+ # @param value [Object] the value to validate
205
+ def acquisition_protocol!(name, value)
206
+ return if %w[direct issuance].include?(value)
207
+
208
+ raise InvalidParameterError.new(name, "'direct' or 'issuance'")
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Wire
6
+ autoload :Frame, 'bsv/wallet/wire/frame'
7
+ autoload :Calls, 'bsv/wallet/wire/calls'
8
+ autoload :Validation, 'bsv/wallet/wire/validation'
9
+ autoload :Writer, 'bsv/wallet/wire/reader_writer'
10
+ autoload :Reader, 'bsv/wallet/wire/reader_writer'
11
+ end
12
+ end
13
+ end
data/lib/bsv/wallet.rb CHANGED
@@ -8,9 +8,26 @@ module BSV
8
8
  require_relative 'wallet/errors'
9
9
  require_relative 'wallet/interface'
10
10
 
11
+ # Wire layer — BRC-103 frame codec, call enum, validators, reader/writer.
12
+ require_relative 'wallet/wire'
13
+
14
+ # Per-call BRC-103 serialisers and dispatch tables.
15
+ autoload :Serializer, 'bsv/wallet/serializer'
16
+
11
17
  # ProtoWallet — minimal crypto-only BRC-100 implementation.
12
18
  # Its internals (KeyDeriver, Validators) are scoped under ProtoWallet::
13
19
  # to avoid collision with bsv-wallet's own KeyDeriver.
14
20
  require_relative 'wallet/proto_wallet'
21
+
22
+ # BRC-103 transport layer.
23
+ autoload :WalletWire, 'bsv/wallet/wallet_wire'
24
+ autoload :WalletWireTransceiver, 'bsv/wallet/wallet_wire_transceiver'
25
+ autoload :WalletWireProcessor, 'bsv/wallet/wallet_wire_processor'
26
+
27
+ # BRC-103 transport substrates.
28
+ module Substrates
29
+ autoload :HTTPWalletWire, 'bsv/wallet/substrates/http_wallet_wire'
30
+ autoload :HTTPWalletJSON, 'bsv/wallet/substrates/http_wallet_json'
31
+ end
15
32
  end
16
33
  end