bsv-sdk 0.15.0 → 0.16.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.
@@ -2,164 +2,24 @@
2
2
 
3
3
  module BSV
4
4
  module Network
5
- # ARC broadcaster for submitting transactions to the BSV network.
6
- #
7
- # This facade preserves the legacy public API contract while delegating all
8
- # HTTP logic to +Protocols::ARC+. It translates +Result+ objects returned by
9
- # the protocol into +BroadcastResponse+ instances or raised +BroadcastError+
10
- # exceptions as required by consumer code.
11
- #
12
- # Any object responding to #broadcast(tx) can serve as a broadcaster;
13
- # this class implements that contract using the ARC API.
5
+ # @deprecated Use {BSV::Network::Protocols::ARC} directly instead.
6
+ # The facade converted clean Result objects into exceptions — every
7
+ # consumer immediately caught them and converted back to data.
8
+ # Use the protocol layer, which returns Result objects natively.
14
9
  class ARC
15
- # Returns an ARC instance pointed at the GorillaPool public ARC endpoint.
16
- #
17
- # @param testnet [Boolean] when true, uses the GorillaPool testnet endpoint
18
- # @param api_key [String, nil] optional bearer token for Authorization
19
- # @param http_client [#request, nil] injectable HTTP client for testing
20
- # @param opts [Hash] ARC-specific options forwarded to the protocol
21
- # (e.g. +deployment_id:+, +callback_url:+, +callback_token:+)
22
- # @return [ARC]
23
- def self.default(testnet: false, api_key: nil, http_client: nil, **opts)
24
- provider = Providers::GorillaPool.default(testnet: testnet)
25
- arc_protocol = provider.protocol_for(:broadcast)
26
- base_url = arc_protocol.base_url
27
- new(
28
- base_url,
29
- api_key: api_key,
30
- http_client: http_client,
31
- **opts
32
- )
33
- end
34
-
35
- # ARC response statuses that indicate the transaction was NOT accepted.
36
- REJECTED_STATUSES = %w[
37
- REJECTED
38
- DOUBLE_SPEND_ATTEMPTED
39
- INVALID
40
- MALFORMED
41
- MINED_IN_STALE_BLOCK
42
- ].freeze
43
-
44
- # @param url_or_protocol [String, Protocols::ARC] ARC base URL (without
45
- # trailing slash) or a pre-configured +Protocols::ARC+ instance. When a
46
- # URL string is supplied, the remaining keyword arguments are forwarded to
47
- # the underlying protocol constructor. When a protocol instance is supplied,
48
- # all keyword arguments are ignored.
49
- # @param api_key [String, nil] optional bearer token for Authorization
50
- # @param deployment_id [String, nil] optional deployment identifier for
51
- # the +XDeployment-ID+ header; defaults to a per-instance random value
52
- # @param callback_url [String, nil] optional +X-CallbackUrl+ for ARC
53
- # status callbacks
54
- # @param callback_token [String, nil] optional +X-CallbackToken+ for
55
- # ARC status callback authentication
56
- # @param http_client [#request, nil] injectable HTTP client for testing
57
- def initialize(url_or_protocol, api_key: nil, deployment_id: nil, callback_url: nil,
58
- callback_token: nil, http_client: nil)
59
- @protocol =
60
- if url_or_protocol.is_a?(String)
61
- Protocols::ARC.new(
62
- base_url: url_or_protocol,
63
- api_key: api_key,
64
- deployment_id: deployment_id,
65
- callback_url: callback_url,
66
- callback_token: callback_token,
67
- http_client: http_client
68
- )
69
- else
70
- url_or_protocol
71
- end
72
- end
73
-
74
- # Submit a transaction to ARC.
75
- #
76
- # @param tx [Transaction] the transaction to broadcast
77
- # @param wait_for [String, nil] ARC wait condition
78
- # @param skip_fee_validation [Boolean, nil] when truthy, sends X-SkipFeeValidation header
79
- # @param skip_script_validation [Boolean, nil] when truthy, sends X-SkipScriptValidation header
80
- # @return [BroadcastResponse]
81
- # @raise [BroadcastError] when ARC returns a non-2xx HTTP status or a
82
- # rejected/orphan +txStatus+
83
- def broadcast(tx, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)
84
- result = @protocol.call(
85
- :broadcast, tx,
86
- wait_for: wait_for,
87
- skip_fee_validation: skip_fee_validation,
88
- skip_script_validation: skip_script_validation
89
- )
90
- result_to_response!(result)
91
- end
92
-
93
- # Submit multiple transactions to ARC in a single batch request.
94
- #
95
- # Returns a mixed array of {BroadcastResponse} and {BroadcastError} objects.
96
- # Per-transaction rejections are returned as {BroadcastError} values rather
97
- # than raised. Only HTTP-level errors raise a {BroadcastError} for the whole batch.
98
- #
99
- # @param txs [Array<Transaction>] transactions to broadcast
100
- # @param wait_for [String, nil] ARC wait condition
101
- # @param skip_fee_validation [Boolean, nil]
102
- # @param skip_script_validation [Boolean, nil]
103
- # @return [Array<BroadcastResponse, BroadcastError>]
104
- # @raise [BroadcastError] when ARC returns a non-2xx HTTP status
105
- def broadcast_many(txs, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)
106
- result = @protocol.call(
107
- :broadcast_many, txs,
108
- wait_for: wait_for,
109
- skip_fee_validation: skip_fee_validation,
110
- skip_script_validation: skip_script_validation
111
- )
10
+ # Raised when deprecated facade classes are instantiated.
11
+ class DeprecationError < StandardError; end
112
12
 
113
- case result
114
- when Result::Success
115
- result.data.map { |item| item_to_response_or_error(item) }
116
- else
117
- raise broadcast_error_from_result(result)
118
- end
119
- end
120
-
121
- # Query the status of a previously submitted transaction.
122
- #
123
- # @param txid [String] transaction ID to query
124
- # @return [BroadcastResponse]
125
- # @raise [BroadcastError] on failure
126
- def status(txid)
127
- result = @protocol.call(:get_tx_status, txid)
128
- result_to_response!(result)
129
- end
130
-
131
- private
132
-
133
- # Translate a single-tx Result into a BroadcastResponse or raise BroadcastError.
134
- def result_to_response!(result)
135
- case result
136
- when Result::Success
137
- BroadcastResponse.new(result.data)
138
- else
139
- raise broadcast_error_from_result(result)
140
- end
141
- end
13
+ MESSAGE = 'BSV::Network::ARC is deprecated. ' \
14
+ 'Use BSV::Network::Protocols::ARC directly — it returns Result objects ' \
15
+ 'instead of raising exceptions. See BSV::Network::Protocols::ARC for usage.'
142
16
 
143
- # Translate a per-item batch Result into a BroadcastResponse or BroadcastError
144
- # (returned, not raised).
145
- def item_to_response_or_error(item)
146
- case item
147
- when Result::Success
148
- BroadcastResponse.new(item.data)
149
- else
150
- broadcast_error_from_result(item)
151
- end
17
+ def self.default(**)
18
+ raise DeprecationError, MESSAGE
152
19
  end
153
20
 
154
- # Build a BroadcastError from a Result::Error or Result::NotFound.
155
- def broadcast_error_from_result(result)
156
- meta = result.metadata || {}
157
- BroadcastError.new(
158
- result.message,
159
- status_code: meta[:status_code],
160
- txid: meta[:txid],
161
- arc_status: meta[:arc_status]
162
- )
21
+ def initialize(*)
22
+ raise DeprecationError, MESSAGE
163
23
  end
164
24
  end
165
25
  end
@@ -2,118 +2,24 @@
2
2
 
3
3
  module BSV
4
4
  module Network
5
- # WhatsOnChain chain data provider for reading transactions and UTXOs
6
- # from the BSV network.
7
- #
8
- # Any object responding to #fetch_utxos(address),
9
- # #fetch_transaction(txid), #current_height,
10
- # #get_block_header(height), and optionally
11
- # #valid_root_for_height?(root_hex, height) can serve as a chain
12
- # data source;
13
- # this class implements that contract by delegating to
14
- # Protocols::WoCREST.
15
- #
16
- # The HTTP client is injectable for testability. It must respond to
17
- # #request(uri, request) and return an object with #code and #body.
5
+ # @deprecated Use {BSV::Network::Protocols::WoCREST} directly instead.
6
+ # The facade converted clean Result objects into exceptions — every
7
+ # consumer immediately caught them and converted back to data.
8
+ # Use the protocol layer, which returns Result objects natively.
18
9
  class WhatsOnChain
19
- # Returns a WhatsOnChain instance using the provider default.
20
- #
21
- # @param testnet [Boolean] when true, uses the testnet endpoint
22
- # @param opts [Hash] forwarded to the underlying protocol (e.g. +api_key:+, +http_client:+)
23
- # @return [WhatsOnChain]
24
- def self.default(testnet: false, **opts)
25
- provider = Providers::WhatsOnChain.default(testnet: testnet, **opts)
26
- new(protocol: provider.protocol_for(:get_tx))
27
- end
28
-
29
- # @param network [Symbol] :main, :mainnet, :test, :testnet, :stn (legacy compat)
30
- # @param http_client [#request, nil] injectable HTTP client
31
- # @param protocol [BSV::Network::Protocols::WoCREST, nil] pre-configured protocol
32
- def initialize(network: :mainnet, http_client: nil, protocol: nil)
33
- if protocol
34
- @protocol = protocol
35
- else
36
- provider = Providers::WhatsOnChain.default(network: network, http_client: http_client)
37
- @protocol = provider.protocol_for(:get_tx)
38
- end
39
- end
40
-
41
- # Fetch unspent transaction outputs for an address.
42
- # @param address [String] BSV address
43
- # @return [Array<UTXO>]
44
- def fetch_utxos(address)
45
- result = @protocol.call(:get_utxos_all, address)
46
- raise_on_error(result)
47
-
48
- result.data.map do |entry|
49
- UTXO.new(
50
- tx_hash: entry[:tx_hash],
51
- tx_pos: entry[:tx_pos],
52
- satoshis: entry[:satoshis],
53
- height: entry[:height]
54
- )
55
- end
56
- end
57
-
58
- # Fetch a raw transaction by its txid and parse it.
59
- # @param txid [String] transaction ID (hex)
60
- # @return [BSV::Transaction::Transaction]
61
- def fetch_transaction(txid)
62
- result = @protocol.call(:get_tx, txid)
63
- raise_on_error(result)
64
-
65
- BSV::Transaction::Transaction.from_hex(result.data)
66
- end
10
+ # Raised when deprecated facade classes are instantiated.
11
+ class DeprecationError < StandardError; end
67
12
 
68
- # Return the current blockchain height.
69
- # @return [Integer]
70
- # @raise [BSV::Network::ChainProviderError] on network or API error
71
- def current_height
72
- result = @protocol.call(:current_height)
73
- raise_on_error(result)
13
+ MESSAGE = 'BSV::Network::WhatsOnChain is deprecated. ' \
14
+ 'Use BSV::Network::Protocols::WoCREST directly — it returns Result objects ' \
15
+ 'instead of raising exceptions. See BSV::Network::Protocols::WoCREST for usage.'
74
16
 
75
- result.data
17
+ def self.default(**)
18
+ raise DeprecationError, MESSAGE
76
19
  end
77
20
 
78
- # Fetch the block header for a given height.
79
- # @param height [Integer] block height
80
- # @return [Hash] parsed block header JSON
81
- # @raise [BSV::Network::ChainProviderError] on network or API error
82
- def get_block_header(height)
83
- result = @protocol.call(:get_block_header, height)
84
- raise_on_error(result)
85
-
86
- result.data
87
- end
88
-
89
- # Verify that a merkle root is valid for the given block height.
90
- # Returns +false+ when the block is not found (404); raises on other errors.
91
- # @param root [String] expected merkle root as hex
92
- # @param height [Integer] block height
93
- # @return [Boolean]
94
- # @raise [BSV::Network::ChainProviderError] on network or non-404 API error
95
- def valid_root_for_height?(root, height)
96
- result = @protocol.call(:valid_root, root, height)
97
- return false if result.not_found?
98
-
99
- raise_on_error(result)
100
-
101
- result.data == true
102
- end
103
-
104
- private
105
-
106
- # Translates a non-success Protocol result into a raised ChainProviderError.
107
- #
108
- # @param result [Result::Error, Result::NotFound]
109
- # @raise [ChainProviderError]
110
- def raise_on_error(result)
111
- return if result.success?
112
-
113
- raise ChainProviderError.new(
114
- result.message || 'Request failed',
115
- status_code: result.metadata[:status_code]
116
- )
21
+ def initialize(*)
22
+ raise DeprecationError, MESSAGE
117
23
  end
118
24
  end
119
25
  end
@@ -90,7 +90,7 @@ module BSV
90
90
 
91
91
  sig_args = { hash_to_directly_sign: hash_bytes, protocol_id: @protocol_id, key_id: '1', counterparty: 'self' }
92
92
  sig_args[:originator] = @originator if @originator
93
- result = @wallet.create_signature(sig_args)
93
+ result = @wallet.create_signature(**sig_args)
94
94
 
95
95
  sig_bytes = result[:signature].pack('C*')
96
96
  sig_with_hashtype = sig_bytes + [sighash_type].pack('C')
@@ -171,14 +171,14 @@ module BSV
171
171
  # Fetch the wallet's identity key (compressed public key hex)
172
172
  id_args = { identity_key: true }
173
173
  id_args[:originator] = @originator if @originator
174
- identity_result = @wallet.get_public_key(id_args)
174
+ identity_result = @wallet.get_public_key(**id_args)
175
175
  identity_key_hex = identity_result[:public_key]
176
176
  identity_key_bytes = [identity_key_hex].pack('H*')
177
177
 
178
178
  # Derive the locking public key for this protocol
179
179
  lock_args = { protocol_id: protocol_id, key_id: '1', counterparty: 'self' }
180
180
  lock_args[:originator] = @originator if @originator
181
- locking_result = @wallet.get_public_key(lock_args)
181
+ locking_result = @wallet.get_public_key(**lock_args)
182
182
  locking_pubkey_hex = locking_result[:public_key]
183
183
  locking_pubkey_bytes = [locking_pubkey_hex].pack('H*')
184
184
 
@@ -192,7 +192,7 @@ module BSV
192
192
  data_to_sign = (field_protocol + field_identity + field_domain + field_topic).unpack('C*')
193
193
  sig_args = { data: data_to_sign, protocol_id: protocol_id, key_id: '1', counterparty: 'self' }
194
194
  sig_args[:originator] = @originator if @originator
195
- sig_result = @wallet.create_signature(sig_args)
195
+ sig_result = @wallet.create_signature(**sig_args)
196
196
  field_sig = sig_result[:signature].pack('C*')
197
197
 
198
198
  fields = [field_protocol, field_identity, field_domain, field_topic, field_sig]
@@ -66,18 +66,16 @@ module BSV
66
66
 
67
67
  basket_name = basket_name_for(definition_type)
68
68
  create_result = @wallet.create_action(
69
- {
70
- description: "Register a new #{definition_type} definition",
71
- outputs: [
72
- {
73
- satoshis: Constants::TOKEN_AMOUNT,
74
- locking_script: locking_script.to_hex,
75
- output_description: "New #{definition_type} registration token",
76
- basket: basket_name
77
- }
78
- ],
79
- options: { randomize_outputs: false }
80
- },
69
+ description: "Register a new #{definition_type} definition",
70
+ outputs: [
71
+ {
72
+ satoshis: Constants::TOKEN_AMOUNT,
73
+ locking_script: locking_script.to_hex,
74
+ output_description: "New #{definition_type} registration token",
75
+ basket: basket_name
76
+ }
77
+ ],
78
+ options: { randomize_outputs: false },
81
79
  originator: @originator
82
80
  )
83
81
 
@@ -122,7 +120,7 @@ module BSV
122
120
  def list_own_registry_entries(definition_type)
123
121
  basket_name = basket_name_for(definition_type)
124
122
  list_result = @wallet.list_outputs(
125
- { basket: basket_name, include: 'entire transactions' },
123
+ basket: basket_name, include: 'entire transactions',
126
124
  originator: @originator
127
125
  )
128
126
 
@@ -160,18 +158,16 @@ module BSV
160
158
  outpoint = "#{registered_definition.txid}.#{registered_definition.output_index}"
161
159
 
162
160
  create_result = @wallet.create_action(
163
- {
164
- description: "Revoke #{definition_type} definition: #{item_identifier(registered_definition)}",
165
- input_beef: registered_definition.beef,
166
- inputs: [
167
- {
168
- outpoint: outpoint,
169
- unlocking_script_length: BSV::Script::PushDropTemplate::Unlocker::ESTIMATED_LENGTH,
170
- input_description: "Revoking #{definition_type} token"
171
- }
172
- ],
173
- options: { randomize_outputs: false, no_send: true }
174
- },
161
+ description: "Revoke #{definition_type} definition: #{item_identifier(registered_definition)}",
162
+ input_beef: registered_definition.beef,
163
+ inputs: [
164
+ {
165
+ outpoint: outpoint,
166
+ unlocking_script_length: BSV::Script::PushDropTemplate::Unlocker::ESTIMATED_LENGTH,
167
+ input_description: "Revoking #{definition_type} token"
168
+ }
169
+ ],
170
+ options: { randomize_outputs: false, no_send: true },
175
171
  originator: @originator
176
172
  )
177
173
 
@@ -210,7 +206,7 @@ module BSV
210
206
  def identity_key
211
207
  return @identity_key if defined?(@identity_key)
212
208
 
213
- result = @wallet.get_public_key({ identity_key: true }, originator: @originator)
209
+ result = @wallet.get_public_key(identity_key: true, originator: @originator)
214
210
  @identity_key = result[:public_key] || result['public_key'] || result['publicKey'] || result[:publicKey]
215
211
  end
216
212
 
@@ -561,7 +557,7 @@ module BSV
561
557
  #
562
558
  # @return [Symbol] :mainnet or :testnet
563
559
  def wallet_network
564
- result = @wallet.get_network({}, originator: @originator)
560
+ result = @wallet.get_network(originator: @originator)
565
561
  net_str = result[:network] || result['network'] || 'mainnet'
566
562
  net_str.to_sym
567
563
  end
@@ -108,14 +108,14 @@ module BSV
108
108
  counterparty: @counterparty
109
109
  }
110
110
  orig_kw = @originator ? { originator: @originator } : {}
111
- result = @wallet.create_signature(sig_args, **orig_kw)
111
+ result = @wallet.create_signature(**sig_args, **orig_kw)
112
112
 
113
113
  sig_bytes = result[:signature].pack('C*')
114
114
  sig_with_hashtype = sig_bytes + [sighash_type].pack('C')
115
115
 
116
116
  # Fetch the derived public key so the P2PKH unlock can include it
117
117
  pub_args = { protocol_id: @protocol_id, key_id: @key_id, counterparty: @counterparty }
118
- pub_result = @wallet.get_public_key(pub_args, **orig_kw)
118
+ pub_result = @wallet.get_public_key(**pub_args, **orig_kw)
119
119
  pubkey_bytes = [pub_result[:public_key]].pack('H*')
120
120
 
121
121
  BSV::Script::Script.pushdrop_unlock(
@@ -176,7 +176,7 @@ module BSV
176
176
  else
177
177
  pub_args = { protocol_id: protocol_id, key_id: key_id, counterparty: counterparty }
178
178
  orig_kw = @originator ? { originator: @originator } : {}
179
- pub_result = @wallet.get_public_key(pub_args, **orig_kw)
179
+ pub_result = @wallet.get_public_key(**pub_args, **orig_kw)
180
180
  [pub_result[:public_key]].pack('H*')
181
181
  end
182
182
 
@@ -187,7 +187,7 @@ module BSV
187
187
  data_to_sign = all_fields.reduce(''.b) { |acc, f| acc + f }.unpack('C*')
188
188
  sig_args = { data: data_to_sign, protocol_id: protocol_id, key_id: key_id, counterparty: counterparty }
189
189
  orig_kw = @originator ? { originator: @originator } : {}
190
- sig_result = @wallet.create_signature(sig_args, **orig_kw)
190
+ sig_result = @wallet.create_signature(**sig_args, **orig_kw)
191
191
  all_fields << sig_result[:signature].pack('C*')
192
192
  end
193
193
 
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.15.0'
4
+ VERSION = '0.16.0'
5
5
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Base error for all wallet operations. Carries a machine-readable code
6
+ # per the BRC-100 error structure.
7
+ class Error < StandardError
8
+ attr_reader :code
9
+
10
+ def initialize(message, code = 1)
11
+ @code = code
12
+ super(message)
13
+ end
14
+ end
15
+
16
+ # Raised when a required parameter is missing or invalid.
17
+ class InvalidParameterError < Error
18
+ attr_reader :parameter
19
+
20
+ def initialize(parameter, must_be = 'valid')
21
+ @parameter = parameter
22
+ super("the #{parameter} parameter must be #{must_be}", 6)
23
+ end
24
+ end
25
+
26
+ # Raised when an HMAC fails to verify.
27
+ class InvalidHmacError < Error
28
+ def initialize(message = 'the provided HMAC is invalid')
29
+ super(message, 3)
30
+ end
31
+ end
32
+
33
+ # Raised when a signature fails to verify.
34
+ class InvalidSignatureError < Error
35
+ def initialize(message = 'the provided signature is invalid')
36
+ super(message, 4)
37
+ end
38
+ end
39
+
40
+ # Raised when an operation is not supported by this wallet implementation.
41
+ class UnsupportedActionError < Error
42
+ def initialize(method_name = 'this method')
43
+ super("#{method_name} is not supported by this wallet implementation", 2)
44
+ end
45
+ end
46
+ end
47
+ end