bsv-sdk 0.2.1 → 0.3.1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bsv/network/arc.rb +11 -2
  3. data/lib/bsv/network/broadcast_response.rb +1 -2
  4. data/lib/bsv/primitives/bsm.rb +2 -6
  5. data/lib/bsv/primitives/curve.rb +1 -2
  6. data/lib/bsv/primitives/encrypted_message.rb +100 -0
  7. data/lib/bsv/primitives/extended_key.rb +1 -2
  8. data/lib/bsv/primitives/key_shares.rb +83 -0
  9. data/lib/bsv/primitives/mnemonic.rb +1 -3
  10. data/lib/bsv/primitives/point_in_finite_field.rb +72 -0
  11. data/lib/bsv/primitives/polynomial.rb +95 -0
  12. data/lib/bsv/primitives/private_key.rb +100 -3
  13. data/lib/bsv/primitives/signed_message.rb +104 -0
  14. data/lib/bsv/primitives/symmetric_key.rb +128 -0
  15. data/lib/bsv/primitives.rb +18 -12
  16. data/lib/bsv/script/interpreter/interpreter.rb +1 -3
  17. data/lib/bsv/script/interpreter/operations/bitwise.rb +1 -3
  18. data/lib/bsv/script/interpreter/operations/crypto.rb +3 -9
  19. data/lib/bsv/script/interpreter/operations/flow_control.rb +2 -6
  20. data/lib/bsv/script/interpreter/operations/splice.rb +1 -3
  21. data/lib/bsv/script/interpreter/script_number.rb +2 -7
  22. data/lib/bsv/script/script.rb +252 -1
  23. data/lib/bsv/transaction/beef.rb +1 -4
  24. data/lib/bsv/transaction/transaction.rb +123 -45
  25. data/lib/bsv/transaction/transaction_input.rb +1 -2
  26. data/lib/bsv/transaction/transaction_output.rb +1 -2
  27. data/lib/bsv/transaction/var_int.rb +4 -16
  28. data/lib/bsv/transaction.rb +14 -14
  29. data/lib/bsv/version.rb +1 -1
  30. data/lib/bsv/wallet_interface/chain_provider.rb +37 -0
  31. data/lib/bsv/wallet_interface/errors/invalid_hmac_error.rb +11 -0
  32. data/lib/bsv/wallet_interface/errors/invalid_parameter_error.rb +14 -0
  33. data/lib/bsv/wallet_interface/errors/invalid_signature_error.rb +11 -0
  34. data/lib/bsv/wallet_interface/errors/unsupported_action_error.rb +11 -0
  35. data/lib/bsv/wallet_interface/errors/wallet_error.rb +14 -0
  36. data/lib/bsv/wallet_interface/interface.rb +384 -0
  37. data/lib/bsv/wallet_interface/key_deriver.rb +144 -0
  38. data/lib/bsv/wallet_interface/memory_store.rb +130 -0
  39. data/lib/bsv/wallet_interface/null_chain_provider.rb +22 -0
  40. data/lib/bsv/wallet_interface/proto_wallet.rb +361 -0
  41. data/lib/bsv/wallet_interface/storage_adapter.rb +55 -0
  42. data/lib/bsv/wallet_interface/validators.rb +126 -0
  43. data/lib/bsv/wallet_interface/version.rb +7 -0
  44. data/lib/bsv/wallet_interface/wallet_client.rb +748 -0
  45. data/lib/bsv/wallet_interface.rb +27 -0
  46. data/lib/bsv-wallet.rb +4 -0
  47. metadata +26 -3
  48. /data/{LICENCE → LICENSE} +0 -0
@@ -19,6 +19,19 @@ module BSV
19
19
  # Estimated size of an unsigned P2PKH input in bytes.
20
20
  UNSIGNED_P2PKH_INPUT_SIZE = 148
21
21
 
22
+ # Lookup table for benford_number calculation.
23
+ # we want float values for log10(1 + (1.0 / i)) for the 9 integers 0 < i < 10
24
+ # in ruby this becomes: (1..9).to_a.collect{|d| Math.log10(1 + (1.0 / d)) }
25
+ LOG10_RECIPROCAL_D_VALUES_1TO9 = [0.3010299956639812,
26
+ 0.17609125905568124,
27
+ 0.12493873660829993,
28
+ 0.09691001300805642,
29
+ 0.07918124604762482,
30
+ 0.06694678963061322,
31
+ 0.05799194697768673,
32
+ 0.05115252244738129,
33
+ 0.04575749056067514].freeze
34
+
22
35
  # @return [Integer] transaction version number
23
36
  attr_reader :version
24
37
 
@@ -149,8 +162,7 @@ module BSV
149
162
  end
150
163
 
151
164
  if data.bytesize < offset + 4
152
- raise ArgumentError,
153
- "truncated transaction: need 4 bytes for lock_time at offset #{offset}, got #{data.bytesize - offset}"
165
+ raise ArgumentError, "truncated transaction: need 4 bytes for lock_time at offset #{offset}, got #{data.bytesize - offset}"
154
166
  end
155
167
 
156
168
  tx.instance_variable_set(:@lock_time, data.byteslice(offset, 4).unpack1('V'))
@@ -171,10 +183,7 @@ module BSV
171
183
  # @return [Transaction] the parsed transaction with source data on inputs
172
184
  # @raise [ArgumentError] if the EF marker is invalid
173
185
  def self.from_ef(data)
174
- if data.bytesize < 10
175
- raise ArgumentError,
176
- "truncated EF transaction: need at least 10 bytes, got #{data.bytesize}"
177
- end
186
+ raise ArgumentError, "truncated EF transaction: need at least 10 bytes, got #{data.bytesize}" if data.bytesize < 10
178
187
 
179
188
  offset = 0
180
189
 
@@ -197,8 +206,7 @@ module BSV
197
206
 
198
207
  if data.bytesize < offset + 8
199
208
  remaining = data.bytesize - offset
200
- raise ArgumentError,
201
- "truncated EF input: need 8 bytes for source_satoshis at offset #{offset}, got #{remaining}"
209
+ raise ArgumentError, "truncated EF input: need 8 bytes for source_satoshis at offset #{offset}, got #{remaining}"
202
210
  end
203
211
 
204
212
  input.source_satoshis = data.byteslice(offset, 8).unpack1('Q<')
@@ -222,8 +230,7 @@ module BSV
222
230
 
223
231
  if data.bytesize < offset + 4
224
232
  remaining = data.bytesize - offset
225
- raise ArgumentError,
226
- "truncated EF transaction: need 4 bytes for lock_time at offset #{offset}, got #{remaining}"
233
+ raise ArgumentError, "truncated EF transaction: need 4 bytes for lock_time at offset #{offset}, got #{remaining}"
227
234
  end
228
235
 
229
236
  tx.instance_variable_set(:@lock_time, data.byteslice(offset, 4).unpack1('V'))
@@ -246,8 +253,7 @@ module BSV
246
253
  # @return [Array(Transaction, Integer)] the transaction and bytes consumed
247
254
  def self.from_binary_with_offset(data, offset = 0)
248
255
  if data.bytesize < offset + 10
249
- raise ArgumentError,
250
- "truncated transaction: need at least 10 bytes at offset #{offset}, got #{data.bytesize - offset}"
256
+ raise ArgumentError, "truncated transaction: need at least 10 bytes at offset #{offset}, got #{data.bytesize - offset}"
251
257
  end
252
258
 
253
259
  start = offset
@@ -274,8 +280,7 @@ module BSV
274
280
  end
275
281
 
276
282
  if data.bytesize < offset + 4
277
- raise ArgumentError,
278
- "truncated transaction: need 4 bytes for lock_time at offset #{offset}, got #{data.bytesize - offset}"
283
+ raise ArgumentError, "truncated transaction: need 4 bytes for lock_time at offset #{offset}, got #{data.bytesize - offset}"
279
284
  end
280
285
 
281
286
  tx.instance_variable_set(:@lock_time, data.byteslice(offset, 4).unpack1('V'))
@@ -557,10 +562,7 @@ module BSV
557
562
  # @return [Integer] total input value in satoshis
558
563
  def total_input_satoshis
559
564
  @inputs.each_with_index do |input, idx|
560
- if input.source_satoshis.nil?
561
- raise ArgumentError,
562
- "input #{idx} has nil source_satoshis — set it before computing totals"
563
- end
565
+ raise ArgumentError, "input #{idx} has nil source_satoshis — set it before computing totals" if input.source_satoshis.nil?
564
566
  end
565
567
  @inputs.sum(&:source_satoshis)
566
568
  end
@@ -611,15 +613,29 @@ module BSV
611
613
  # Accepts a {FeeModel} instance, a numeric fee in satoshis, or nil
612
614
  # (defaults to {FeeModels::SatoshisPerKilobyte} at 50 sat/kB).
613
615
  #
614
- # After computing the fee, distributes remaining satoshis equally
615
- # across outputs marked as change. If insufficient change, removes
616
- # all change outputs (excess goes to miners).
616
+ # After computing the fee, distributes remaining satoshis across outputs
617
+ # marked as change. The distribution strategy is controlled by the
618
+ # +change_distribution:+ keyword argument:
619
+ #
620
+ # - +:equal+ (default) — divides change equally across all change outputs,
621
+ # matching TS SDK default behaviour.
622
+ # - +:random+ — Benford-inspired distribution that biases amounts towards
623
+ # the lower end of the available range, improving privacy by producing
624
+ # varied change amounts.
625
+ #
626
+ # If insufficient change remains, all change outputs are removed.
617
627
  #
618
628
  # @param model_or_fee [FeeModel, Integer, nil] fee model, fixed fee, or nil for default
629
+ # @param change_distribution [Symbol] +:equal+ or +:random+ (default: +:equal+)
619
630
  # @return [self] for chaining
620
- def fee(model_or_fee = nil)
631
+ # @raise [ArgumentError] if +change_distribution+ is not +:random+ or +:equal+
632
+ def fee(model_or_fee = nil, change_distribution: :equal)
633
+ unless %i[random equal].include?(change_distribution)
634
+ raise ArgumentError, "invalid change_distribution #{change_distribution.inspect}; expected :random or :equal"
635
+ end
636
+
621
637
  fee_sats = compute_fee_sats(model_or_fee)
622
- distribute_change(fee_sats)
638
+ distribute_change(fee_sats, change_distribution)
623
639
  self
624
640
  end
625
641
 
@@ -627,18 +643,9 @@ module BSV
627
643
 
628
644
  def verify_input_requirements(tx, input, index)
629
645
  tx_id = tx.txid_hex
630
- if input.unlocking_script.nil?
631
- raise ArgumentError,
632
- "input #{index} of transaction #{tx_id} has no unlocking script"
633
- end
634
- if input.source_locking_script.nil?
635
- raise ArgumentError,
636
- "input #{index} of transaction #{tx_id} has no source locking script"
637
- end
638
- return unless input.source_satoshis.nil?
639
-
640
- raise ArgumentError,
641
- "input #{index} of transaction #{tx_id} has no source satoshis"
646
+ raise ArgumentError, "input #{index} of transaction #{tx_id} has no unlocking script" if input.unlocking_script.nil?
647
+ raise ArgumentError, "input #{index} of transaction #{tx_id} has no source locking script" if input.source_locking_script.nil?
648
+ raise ArgumentError, "input #{index} of transaction #{tx_id} has no source satoshis" if input.source_satoshis.nil?
642
649
  end
643
650
 
644
651
  def verify_fee(fee_model)
@@ -647,8 +654,7 @@ module BSV
647
654
  return if actual_fee >= required_fee
648
655
 
649
656
  raise VerificationError.new(:insufficient_fee,
650
- "insufficient fee: transaction pays #{actual_fee} sat " \
651
- "but fee model requires #{required_fee} sat")
657
+ "insufficient fee: transaction pays #{actual_fee} sat but fee model requires #{required_fee} sat")
652
658
  end
653
659
 
654
660
  def verify_output_constraint(tx)
@@ -657,8 +663,7 @@ module BSV
657
663
  return if output_total <= input_total
658
664
 
659
665
  raise VerificationError.new(:output_overflow,
660
- "outputs (#{output_total}) exceed inputs (#{input_total}) " \
661
- "for transaction #{tx.txid_hex}")
666
+ "outputs (#{output_total}) exceed inputs (#{input_total}) for transaction #{tx.txid_hex}")
662
667
  end
663
668
 
664
669
  ZERO_HASH = "\x00".b * 32
@@ -731,7 +736,7 @@ module BSV
731
736
  end
732
737
  end
733
738
 
734
- def distribute_change(fee_sats)
739
+ def distribute_change(fee_sats, change_distribution)
735
740
  change_outputs = @outputs.select(&:change)
736
741
  return if change_outputs.empty?
737
742
 
@@ -741,14 +746,87 @@ module BSV
741
746
 
742
747
  if available <= change_outputs.length
743
748
  @outputs.reject!(&:change)
749
+ return
750
+ end
751
+
752
+ if change_distribution == :random
753
+ distribute_random_change(available, change_outputs)
744
754
  else
745
- per_output = available / change_outputs.length
746
- remainder = available % change_outputs.length
747
- change_outputs.each_with_index do |output, i|
748
- output.satoshis = per_output + (i < remainder ? 1 : 0)
749
- end
755
+ distribute_equal_change(available, change_outputs)
750
756
  end
751
757
  end
758
+
759
+ def distribute_equal_change(available, change_outputs)
760
+ per_output = available / change_outputs.length
761
+ remainder = available % change_outputs.length
762
+ change_outputs.each_with_index do |output, i|
763
+ output.satoshis = per_output + (i < remainder ? 1 : 0)
764
+ end
765
+ end
766
+
767
+ # Distribute change using a Benford-inspired algorithm that biases amounts
768
+ # towards the lower end of the available range.
769
+ #
770
+ # Algorithm:
771
+ # 1. Reserve 1 satoshi per change output as a minimum guarantee.
772
+ # 2. For each output except the last, take a Benford-scaled portion of the
773
+ # remaining pool and add it to that output's base amount.
774
+ # 3. The last change output receives only its 1 sat base.
775
+ # 4. Any remainder (from floor rounding) is assigned to the last transaction
776
+ # output, matching TS SDK behaviour.
777
+ #
778
+ # @param available [Integer] total satoshis to distribute
779
+ # @param change_outputs [Array<TransactionOutput>] outputs flagged as change
780
+ # @return [void]
781
+ def distribute_random_change(available, change_outputs)
782
+ n = change_outputs.length
783
+
784
+ # Initialise each output with a 1-sat base and reserve that pool
785
+ amounts = Array.new(n, 1)
786
+ pool = available - n
787
+ distributed = n
788
+
789
+ # Allocate Benford-scaled portions to all outputs except the last
790
+ (n - 1).times do |i|
791
+ portion = benford_number(0, pool)
792
+ amounts[i] += portion
793
+ distributed += portion
794
+ pool -= portion
795
+ end
796
+
797
+ # Assign computed amounts to change outputs
798
+ change_outputs.each_with_index do |output, i|
799
+ output.satoshis = amounts[i]
800
+ end
801
+
802
+ # Assign any remainder (floor-rounding loss) to the last transaction output
803
+ remainder = available - distributed
804
+ return unless remainder.positive?
805
+
806
+ last_output = @outputs.last
807
+ last_output.satoshis = (last_output.satoshis || 0) + remainder
808
+ end
809
+
810
+ # Generate a Benford-inspired integer in the range [min, max).
811
+ #
812
+ # Randomly selects a scale factor that biases the result towards the lower end of
813
+ # the range, based on the Benford distribution of leading digits.
814
+ #
815
+ # Reference ts SDK implementation
816
+ # Math.floor(min + ((max - min) * Math.log10(1 + 1 / d)) / Math.log10(10))
817
+ #
818
+ # We simplify:
819
+ # - log10(10) = 1, divide by 1 is a no-op, removed it
820
+ # - pre-calculated the 9 possible values to a look-up table LOG10_RECIPROCAL_D_VALUES_1TO9
821
+ # - rearrange to "start at min, add a scaled portion of the range"
822
+ #
823
+ # @param min [Integer] lower bound (inclusive)
824
+ # @param max [Integer] upper bound (exclusive)
825
+ # @return [Integer] Benford-distributed integer
826
+ def benford_number(min, max)
827
+ scale_factor = LOG10_RECIPROCAL_D_VALUES_1TO9[Random.rand(9)] # Array indexing starts at 0
828
+ (min + (scale_factor * (max - min))).floor
829
+ end
752
830
  end
753
831
  end
754
832
  end
@@ -74,9 +74,8 @@ module BSV
74
74
  offset += vi_size
75
75
 
76
76
  if data.bytesize < offset + script_len
77
- remaining = data.bytesize - offset
78
77
  raise ArgumentError,
79
- "truncated input: need #{script_len} bytes for script at offset #{offset}, got #{remaining}"
78
+ "truncated input: need #{script_len} bytes for script at offset #{offset}, got #{data.bytesize - offset}"
80
79
  end
81
80
 
82
81
  unlocking_script = (BSV::Script::Script.from_binary(data.byteslice(offset, script_len)) if script_len.positive?)
@@ -52,9 +52,8 @@ module BSV
52
52
  offset += vi_size
53
53
 
54
54
  if data.bytesize < offset + script_len
55
- remaining = data.bytesize - offset
56
55
  raise ArgumentError,
57
- "truncated output: need #{script_len} bytes for script at offset #{offset}, got #{remaining}"
56
+ "truncated output: need #{script_len} bytes for script at offset #{offset}, got #{data.bytesize - offset}"
58
57
  end
59
58
 
60
59
  script_bytes = data.byteslice(offset, script_len)
@@ -32,10 +32,7 @@ module BSV
32
32
  # @param offset [Integer] byte offset to start reading from
33
33
  # @return [Array(Integer, Integer)] the decoded value and number of bytes consumed
34
34
  def decode(data, offset = 0)
35
- if offset >= data.bytesize
36
- raise ArgumentError,
37
- "truncated varint: need 1 byte at offset #{offset}, got end of data"
38
- end
35
+ raise ArgumentError, "truncated varint: need 1 byte at offset #{offset}, got end of data" if offset >= data.bytesize
39
36
 
40
37
  first = data.getbyte(offset)
41
38
 
@@ -43,24 +40,15 @@ module BSV
43
40
  when 0..0xFC
44
41
  [first, 1]
45
42
  when 0xFD
46
- if data.bytesize < offset + 3
47
- raise ArgumentError,
48
- "truncated varint: need 3 bytes at offset #{offset}, got #{data.bytesize - offset}"
49
- end
43
+ raise ArgumentError, "truncated varint: need 3 bytes at offset #{offset}, got #{data.bytesize - offset}" if data.bytesize < offset + 3
50
44
 
51
45
  [data.byteslice(offset + 1, 2).unpack1('v'), 3]
52
46
  when 0xFE
53
- if data.bytesize < offset + 5
54
- raise ArgumentError,
55
- "truncated varint: need 5 bytes at offset #{offset}, got #{data.bytesize - offset}"
56
- end
47
+ raise ArgumentError, "truncated varint: need 5 bytes at offset #{offset}, got #{data.bytesize - offset}" if data.bytesize < offset + 5
57
48
 
58
49
  [data.byteslice(offset + 1, 4).unpack1('V'), 5]
59
50
  when 0xFF
60
- if data.bytesize < offset + 9
61
- raise ArgumentError,
62
- "truncated varint: need 9 bytes at offset #{offset}, got #{data.bytesize - offset}"
63
- end
51
+ raise ArgumentError, "truncated varint: need 9 bytes at offset #{offset}, got #{data.bytesize - offset}" if data.bytesize < offset + 9
64
52
 
65
53
  [data.byteslice(offset + 1, 8).unpack1('Q<'), 9]
66
54
  end
@@ -8,19 +8,19 @@ module BSV
8
8
  # {Transaction::MerklePath} for BRC-74 merkle proofs, and
9
9
  # {Transaction::Sighash} constants for BIP-143 sighash computation.
10
10
  module Transaction
11
- autoload :VarInt, 'bsv/transaction/var_int'
12
- autoload :TransactionOutput, 'bsv/transaction/transaction_output'
13
- autoload :TransactionInput, 'bsv/transaction/transaction_input'
14
- autoload :Sighash, 'bsv/transaction/sighash'
15
- autoload :MerklePath, 'bsv/transaction/merkle_path'
16
- autoload :FeeModel, 'bsv/transaction/fee_model'
17
- autoload :FeeModels, 'bsv/transaction/fee_models'
18
- autoload :VerificationError, 'bsv/transaction/verification_error'
19
- autoload :ChainTracker, 'bsv/transaction/chain_tracker'
20
- autoload :ChainTrackers, 'bsv/transaction/chain_trackers'
21
- autoload :Beef, 'bsv/transaction/beef'
22
- autoload :UnlockingScriptTemplate, 'bsv/transaction/unlocking_script_template'
23
- autoload :P2PKH, 'bsv/transaction/p2pkh'
24
- autoload :Transaction, 'bsv/transaction/transaction'
11
+ autoload :VarInt, 'bsv/transaction/var_int'
12
+ autoload :TransactionOutput, 'bsv/transaction/transaction_output'
13
+ autoload :TransactionInput, 'bsv/transaction/transaction_input'
14
+ autoload :Sighash, 'bsv/transaction/sighash'
15
+ autoload :MerklePath, 'bsv/transaction/merkle_path'
16
+ autoload :FeeModel, 'bsv/transaction/fee_model'
17
+ autoload :FeeModels, 'bsv/transaction/fee_models'
18
+ autoload :VerificationError, 'bsv/transaction/verification_error'
19
+ autoload :ChainTracker, 'bsv/transaction/chain_tracker'
20
+ autoload :ChainTrackers, 'bsv/transaction/chain_trackers'
21
+ autoload :Beef, 'bsv/transaction/beef'
22
+ autoload :UnlockingScriptTemplate, 'bsv/transaction/unlocking_script_template'
23
+ autoload :P2PKH, 'bsv/transaction/p2pkh'
24
+ autoload :Transaction, 'bsv/transaction/transaction'
25
25
  end
26
26
  end
data/lib/bsv/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BSV
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.1'
5
5
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Duck-typed interface for blockchain data providers.
6
+ #
7
+ # Include this module in chain provider adapters and override all methods.
8
+ # The default implementations raise NotImplementedError.
9
+ #
10
+ # @example Custom provider
11
+ # class MyChainProvider
12
+ # include BSV::Wallet::ChainProvider
13
+ #
14
+ # def get_height
15
+ # # query your node/API
16
+ # end
17
+ #
18
+ # def get_header(height)
19
+ # # return 80-byte hex block header
20
+ # end
21
+ # end
22
+ module ChainProvider
23
+ # Returns the current blockchain height.
24
+ # @return [Integer]
25
+ def get_height
26
+ raise NotImplementedError, "#{self.class}#get_height not implemented"
27
+ end
28
+
29
+ # Returns the block header at the given height.
30
+ # @param _height [Integer] block height
31
+ # @return [String] 80-byte hex-encoded block header
32
+ def get_header(_height)
33
+ raise NotImplementedError, "#{self.class}#get_header not implemented"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ class InvalidHmacError < WalletError
6
+ def initialize(message = 'The provided HMAC is invalid')
7
+ super(message, 3)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ class InvalidParameterError < WalletError
6
+ attr_reader :parameter
7
+
8
+ def initialize(parameter, must_be = 'valid')
9
+ @parameter = parameter
10
+ super("The #{parameter} parameter must be #{must_be}", 6)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ class InvalidSignatureError < WalletError
6
+ def initialize(message = 'The provided signature is invalid')
7
+ super(message, 4)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ class UnsupportedActionError < WalletError
6
+ def initialize(method_name = 'this method')
7
+ super("#{method_name} is not supported by this wallet implementation", 2)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ class WalletError < StandardError
6
+ attr_reader :code
7
+
8
+ def initialize(message, code = 1)
9
+ @code = code
10
+ super(message)
11
+ end
12
+ end
13
+ end
14
+ end