bsv-sdk 0.12.1 → 0.14.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -0
  3. data/lib/bsv/auth/certificate.rb +4 -4
  4. data/lib/bsv/auth/verifiable_certificate.rb +1 -1
  5. data/lib/bsv/network/arc.rb +95 -224
  6. data/lib/bsv/network/protocol.rb +321 -0
  7. data/lib/bsv/network/protocols/arc.rb +351 -0
  8. data/lib/bsv/network/protocols/chaintracks.rb +39 -0
  9. data/lib/bsv/network/protocols/ordinals.rb +32 -0
  10. data/lib/bsv/network/protocols/taal_binary.rb +99 -0
  11. data/lib/bsv/network/protocols/woc_rest.rb +301 -0
  12. data/lib/bsv/network/protocols.rb +17 -0
  13. data/lib/bsv/network/provider.rb +123 -0
  14. data/lib/bsv/network/providers/gorilla_pool.rb +61 -0
  15. data/lib/bsv/network/providers/taal.rb +57 -0
  16. data/lib/bsv/network/providers/whats_on_chain.rb +72 -0
  17. data/lib/bsv/network/providers.rb +25 -0
  18. data/lib/bsv/network/result.rb +119 -0
  19. data/lib/bsv/network/whats_on_chain.rb +78 -40
  20. data/lib/bsv/network.rb +5 -0
  21. data/lib/bsv/overlay/admin_token_template.rb +2 -2
  22. data/lib/bsv/script/push_drop_template.rb +1 -1
  23. data/lib/bsv/transaction/chain_trackers/chaintracks.rb +45 -49
  24. data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +57 -50
  25. data/lib/bsv/transaction/chain_trackers.rb +3 -4
  26. data/lib/bsv/transaction/fee_models/live_policy.rb +3 -2
  27. data/lib/bsv/transaction/transaction.rb +52 -7
  28. data/lib/bsv/transaction/verification_error.rb +11 -0
  29. data/lib/bsv/version.rb +1 -1
  30. data/lib/bsv-sdk.rb +1 -5
  31. metadata +14 -5
  32. data/lib/bsv/messages.rb +0 -16
  33. data/lib/bsv/wallet/insufficient_funds_error.rb +0 -15
  34. data/lib/bsv/wallet/wallet.rb +0 -120
  35. data/lib/bsv/wallet.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1a0e79613d6d5f390c6af210d0e0ad5fe92925b684c5bbe1a6ddffa5ebcf3f5
4
- data.tar.gz: ee4726af0c27f6ecbe11f3483a4c2a9e4195b9f46a15b4c68270b614372759ca
3
+ metadata.gz: 4532a163439721455da80290d8b02342e597a01bb1e00b259d13343f8f9bd592
4
+ data.tar.gz: b7cfda4a8084b96c28f34afb6829fcba9f5be0dabb01fca536147bfe2c66bad8
5
5
  SHA512:
6
- metadata.gz: e9e60d630f8342707559a591f9e26c28e331fe52e4a5a044c2ca20064bc29ecfd261e674772e38bdd15dc28db276bb6669de4950abfe0035e3aca35bba308f37
7
- data.tar.gz: 1263e9a72388a55792ed26437fbc192c4f3cad40fcfaae86b2b5543d021a506413ffb81fbaf918542992caaaa508aa9e77edec2053c6f4a4e8a7d3d1f3acea91
6
+ metadata.gz: ed0b97db3cec8503eba509f232a463d8566373b34ccaf5fe7cd130a72e712cde17b083b4ce94ef64fa84786d6e6aec8965423ca6b8a7471ada70ebd522956e2a
7
+ data.tar.gz: 0747a93bef059a8eccf0f504e703d20ddc875f8c562db8f01d3af4050bf7562deb262eb4494db4d98b78665f91274ed77754fa76e99254b8592b7e0f1526aa13
data/CHANGELOG.md CHANGED
@@ -5,6 +5,55 @@ All notable changes to the `bsv-sdk` gem are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
  and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.14.0 — 2026-04-22
9
+
10
+ ### Added
11
+ - `BSV::Network::WhatsOnChain` expanded with `current_height`,
12
+ `get_block_header(height)`, and `valid_root_for_height?(root, height)` —
13
+ a single WhatsOnChain instance now serves as a complete chain data source
14
+ (#596)
15
+ - BEEF-based SPV verification conformance tests (#607)
16
+
17
+ ### Changed
18
+ - `Transaction#verify` now raises `VerificationError` for all failure modes:
19
+ `:script_failure` (wraps `ScriptError` with cause chaining),
20
+ `:missing_source` (replaces `ArgumentError`), `:invalid_merkle_proof`,
21
+ `:insufficient_fee`, and `:output_overflow`. Code rescuing `ArgumentError`
22
+ or `ScriptError` from `verify` must switch to `VerificationError` (#608)
23
+ - Removed dead code: `BSV::Wallet::Wallet` (superseded by bsv-wallet gem's
24
+ `Client`), `BSV::Messages` (unused re-export alias), and duplicate
25
+ `BSV::Wallet::InsufficientFundsError` (#594)
26
+
27
+ ### Fixed
28
+ - WhatsOnChain `valid_root_for_height?` YARD doc now correctly documents
29
+ that 404 returns `false` rather than raising
30
+
31
+ ## 0.13.0 — 2026-04-21
32
+
33
+ ### Added
34
+ - Protocol layer: base class with declarative DSL, HTTP dispatch, and Result value types
35
+ - WoCREST protocol with transaction detail, script query, exchange rate, fee, mempool, SPV, wallet, and address commands
36
+ - ARC protocol with broadcast escape hatches and enriched response data
37
+ - TAALBinary protocol with binary broadcast support
38
+ - Chaintracks and Ordinals protocols with base_url override
39
+ - Provider configuration container with block DSL and `.default(testnet:)` pattern
40
+ - Provider introspection and capability matrix
41
+ - Concrete provider defaults for GorillaPool, WhatsOnChain, and TAAL
42
+
43
+ ### Changed
44
+ - Facades (ARC, WhatsOnChain, ChainTracker) hollowed out to delegate via Protocol
45
+ - Removed `BSV::MAINNET_URL`/`TESTNET_URL` constants and ENV var reading — provider defaults are the single source of truth
46
+ - Removed `BASE_URL` from protocols — `base_url:` is now mandatory
47
+ - Wallet namespace reorganisation: `Store` and `BroadcastQueue` collaborators namespaced under `Client`
48
+ - `ProtoWallet` and `WalletClient` replaced with `Client`
49
+
50
+ ### Fixed
51
+ - Ruby 2.7 kwargs compatibility in `Protocol#call` and TAALBinary spec doubles
52
+ - Nil-guard on WoCREST broadcast and ARC status metadata
53
+ - ARC status rejection checks and TAALBinary nil-txid guard
54
+ - `BSV::Wallet::Wallet` autoload failure in multi-gem setup
55
+ - SimpleCov coverage gate now requires `COVERAGE=true` explicitly
56
+
8
57
  ## 0.12.1 — 2026-04-16
9
58
 
10
59
  ### Fixed
@@ -21,7 +21,7 @@ module BSV
21
21
  #
22
22
  # Wallet parameters are duck-typed — any object responding to
23
23
  # +create_signature+, +verify_signature+, and +get_public_key+ is accepted.
24
- # No direct dependency on +BSV::Wallet::ProtoWallet+ is introduced here.
24
+ # No direct dependency on +BSV::Wallet::Client+ is introduced here.
25
25
  #
26
26
  # @see https://hub.bsvblockchain.org/brc/wallet/0052 BRC-52
27
27
  class Certificate
@@ -166,17 +166,17 @@ module BSV
166
166
 
167
167
  # Verify the certificate's signature.
168
168
  #
169
- # Uses a fresh +'anyone'+ ProtoWallet as the verifier, which matches the
169
+ # Uses a fresh +'anyone'+ Client as the verifier, which matches the
170
170
  # TS SDK behaviour. If no signature is present, raises +ArgumentError+.
171
171
  #
172
172
  # @param verifier_wallet [#verify_signature, nil] wallet to verify with;
173
- # defaults to +BSV::Wallet::ProtoWallet.new('anyone')+
173
+ # defaults to +BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new)+
174
174
  # @return [Boolean] +true+ if the signature is valid
175
175
  # @raise [ArgumentError] if the certificate has no signature
176
176
  def verify(verifier_wallet = nil)
177
177
  raise ArgumentError, 'certificate has no signature to verify' if @signature.nil? || @signature.empty?
178
178
 
179
- verifier_wallet ||= BSV::Wallet::ProtoWallet.new('anyone')
179
+ verifier_wallet ||= BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new, allow_memory_store: true)
180
180
  preimage = to_binary(include_signature: false)
181
181
  sig_bytes = [@signature].pack('H*').unpack('C*')
182
182
 
@@ -21,7 +21,7 @@ module BSV
21
21
  # - Counterparty: the certificate +subject+ public key
22
22
  #
23
23
  # Wallet parameters are duck-typed — any object responding to +:decrypt+
24
- # is accepted. No direct dependency on +BSV::Wallet::ProtoWallet+ is
24
+ # is accepted. No direct dependency on +BSV::Wallet::Client+ is
25
25
  # introduced here.
26
26
  #
27
27
  # @see BSV::Auth::Certificate base class
@@ -1,35 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
- require 'securerandom'
7
-
8
3
  module BSV
9
4
  module Network
10
5
  # ARC broadcaster for submitting transactions to the BSV network.
11
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
12
  # Any object responding to #broadcast(tx) can serve as a broadcaster;
13
13
  # this class implements that contract using the ARC API.
14
- #
15
- # The HTTP client is injectable for testability. It must respond to
16
- # #request(uri, request) and return an object with #code and #body.
17
14
  class ARC
18
15
  # Returns an ARC instance pointed at the GorillaPool public ARC endpoint.
19
16
  #
20
17
  # @param testnet [Boolean] when true, uses the GorillaPool testnet endpoint
21
- # @param opts [Hash] forwarded to {#initialize} (e.g. +api_key:+, +callback_url:+)
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
22
  # @return [ARC]
23
- def self.default(testnet: false, **opts)
24
- url = testnet ? TESTNET_URL : MAINNET_URL
25
- new(url, **opts)
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
+ )
26
33
  end
27
34
 
28
35
  # ARC response statuses that indicate the transaction was NOT accepted.
29
- # Matches the TypeScript SDK's ARC broadcaster failure set (issue #305,
30
- # finding F5.13). Prior to this fix, Ruby only recognised REJECTED and
31
- # DOUBLE_SPEND_ATTEMPTED, silently treating INVALID / MALFORMED /
32
- # MINED_IN_STALE_BLOCK responses as successful broadcasts.
33
36
  REJECTED_STATUSES = %w[
34
37
  REJECTED
35
38
  DOUBLE_SPEND_ATTEMPTED
@@ -38,10 +41,11 @@ module BSV
38
41
  MINED_IN_STALE_BLOCK
39
42
  ].freeze
40
43
 
41
- # Substring match for orphan detection in txStatus or extraInfo fields.
42
- ORPHAN_MARKER = 'ORPHAN'
43
-
44
- # @param url [String] ARC base URL (without trailing slash)
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.
45
49
  # @param api_key [String, nil] optional bearer token for Authorization
46
50
  # @param deployment_id [String, nil] optional deployment identifier for
47
51
  # the +XDeployment-ID+ header; defaults to a per-instance random value
@@ -50,246 +54,113 @@ module BSV
50
54
  # @param callback_token [String, nil] optional +X-CallbackToken+ for
51
55
  # ARC status callback authentication
52
56
  # @param http_client [#request, nil] injectable HTTP client for testing
53
- def initialize(url, api_key: nil, deployment_id: nil, callback_url: nil,
57
+ def initialize(url_or_protocol, api_key: nil, deployment_id: nil, callback_url: nil,
54
58
  callback_token: nil, http_client: nil)
55
- @url = url.chomp('/')
56
- @api_key = api_key
57
- @deployment_id = deployment_id || "bsv-ruby-sdk-#{SecureRandom.hex(8)}"
58
- @callback_url = callback_url
59
- @callback_token = callback_token
60
- @http_client = http_client
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
61
72
  end
62
73
 
63
74
  # Submit a transaction to ARC.
64
75
  #
65
- # The transaction is encoded as Extended Format (BRC-30) hex when every
66
- # input has +source_satoshis+ and +source_locking_script+ populated,
67
- # which lets ARC validate sighashes without fetching parents. Falls back
68
- # to plain raw-tx hex when EF is unavailable.
69
- #
70
76
  # @param tx [Transaction] the transaction to broadcast
71
- # @param wait_for [String, nil] ARC wait condition — one of
72
- # 'RECEIVED', 'STORED', 'ANNOUNCED_TO_NETWORK',
73
- # 'SEEN_ON_NETWORK', or 'MINED'. When set, ARC holds the
74
- # connection open until the transaction reaches the requested
75
- # state (or times out). Defaults to nil (no wait).
76
- # @param skip_fee_validation [Boolean, nil] when truthy, sends the
77
- # +X-SkipFeeValidation: true+ header, asking ARC to bypass its
78
- # minimum-fee check. Useful for zero-fee data transactions or
79
- # during local testing. Defaults to nil (fee validation applies).
80
- # @param skip_script_validation [Boolean, nil] when truthy, sends the
81
- # +X-SkipScriptValidation: true+ header, asking ARC to bypass
82
- # script correctness checks. Defaults to nil (script validation
83
- # applies).
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
84
80
  # @return [BroadcastResponse]
85
81
  # @raise [BroadcastError] when ARC returns a non-2xx HTTP status or a
86
82
  # rejected/orphan +txStatus+
87
83
  def broadcast(tx, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)
88
- uri = URI("#{@url}/v1/tx")
89
- request = build_post_request(uri, wait_for: wait_for,
90
- skip_fee_validation: skip_fee_validation,
91
- skip_script_validation: skip_script_validation)
92
- request.body = JSON.generate(rawTx: raw_tx_hex(tx))
93
-
94
- response = execute(uri, request)
95
- handle_broadcast_response(response)
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)
96
91
  end
97
92
 
98
93
  # Submit multiple transactions to ARC in a single batch request.
99
94
  #
100
- # Each transaction is encoded as Extended Format (BRC-30) hex where
101
- # possible, falling back to plain raw-tx hex per transaction independently.
102
- #
103
- # Returns a mixed array of {BroadcastResponse} and {BroadcastError} objects
104
- # — one element per submitted transaction in the same order. Per-transaction
105
- # rejections are returned as {BroadcastError} values rather than raised, so
106
- # callers can inspect the full result set even when some transactions fail.
107
- # Only HTTP-level errors (non-2xx) raise a {BroadcastError} for the whole
108
- # batch.
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.
109
98
  #
110
99
  # @param txs [Array<Transaction>] transactions to broadcast
111
- # @param wait_for [String, nil] ARC wait condition (see {#broadcast})
112
- # @param skip_fee_validation [Boolean, nil] when truthy, sends
113
- # +X-SkipFeeValidation: true+ for the batch request
114
- # @param skip_script_validation [Boolean, nil] when truthy, sends
115
- # +X-SkipScriptValidation: true+ for the batch request
100
+ # @param wait_for [String, nil] ARC wait condition
101
+ # @param skip_fee_validation [Boolean, nil]
102
+ # @param skip_script_validation [Boolean, nil]
116
103
  # @return [Array<BroadcastResponse, BroadcastError>]
117
- # @raise [BroadcastError] when ARC returns a non-2xx HTTP status or a
118
- # malformed (non-array) response body
104
+ # @raise [BroadcastError] when ARC returns a non-2xx HTTP status
119
105
  def broadcast_many(txs, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)
120
- return [] if txs.empty?
121
-
122
- uri = URI("#{@url}/v1/txs")
123
- request = build_post_request(uri, wait_for: wait_for,
124
- skip_fee_validation: skip_fee_validation,
125
- skip_script_validation: skip_script_validation)
126
- request.body = JSON.generate(txs.map { |tx| { rawTx: raw_tx_hex(tx) } })
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
+ )
127
112
 
128
- response = execute(uri, request)
129
- handle_batch_response(response)
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
130
119
  end
131
120
 
132
121
  # Query the status of a previously submitted transaction.
133
- # Returns BroadcastResponse on success, raises BroadcastError on failure.
122
+ #
123
+ # @param txid [String] transaction ID to query
124
+ # @return [BroadcastResponse]
125
+ # @raise [BroadcastError] on failure
134
126
  def status(txid)
135
- uri = URI("#{@url}/v1/tx/#{txid}")
136
- request = Net::HTTP::Get.new(uri)
137
- request['XDeployment-ID'] = @deployment_id
138
- apply_auth_header(request)
139
-
140
- response = execute(uri, request)
141
- handle_broadcast_response(response)
127
+ result = @protocol.call(:get_tx_status, txid)
128
+ result_to_response!(result)
142
129
  end
143
130
 
144
131
  private
145
132
 
146
- # Prefer Extended Format (BRC-30) hex so ARC can validate sighashes
147
- # without fetching parent transactions. Falls back to plain raw-tx hex
148
- # when any input lacks source_satoshis / source_locking_script.
149
- def raw_tx_hex(tx)
150
- tx.to_ef_hex
151
- rescue ArgumentError
152
- tx.to_hex
153
- end
154
-
155
- def build_post_request(uri, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)
156
- request = Net::HTTP::Post.new(uri)
157
- request['Content-Type'] = 'application/json'
158
- request['XDeployment-ID'] = @deployment_id
159
- request['X-WaitFor'] = wait_for if wait_for
160
- request['X-CallbackUrl'] = @callback_url if @callback_url
161
- request['X-CallbackToken'] = @callback_token if @callback_token
162
- request['X-SkipFeeValidation'] = 'true' if skip_fee_validation
163
- request['X-SkipScriptValidation'] = 'true' if skip_script_validation
164
- apply_auth_header(request)
165
- request
166
- end
167
-
168
- def apply_auth_header(request)
169
- request['Authorization'] = "Bearer #{@api_key}" if @api_key
170
- end
171
-
172
- def execute(uri, request)
173
- if @http_client
174
- @http_client.request(uri, request)
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)
175
138
  else
176
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
177
- http.request(request)
178
- end
139
+ raise broadcast_error_from_result(result)
179
140
  end
180
141
  end
181
142
 
182
- def handle_broadcast_response(response)
183
- body = parse_json(response.body)
184
- code = response.code.to_i
185
-
186
- unless (200..299).cover?(code)
187
- raise BroadcastError.new(
188
- body['detail'] || body['title'] || "HTTP #{code}",
189
- status_code: code,
190
- txid: body['txid']
191
- )
192
- end
193
-
194
- if rejected_status?(body)
195
- raise BroadcastError.new(
196
- body['detail'] || body['title'] || body['txStatus'],
197
- status_code: code,
198
- txid: body['txid'],
199
- arc_status: body['txStatus'].to_s.upcase
200
- )
201
- end
202
-
203
- # A 2xx response without a txid is a malformed ARC reply —
204
- # `parse_json` falls back to `{'detail' => raw}` on non-JSON,
205
- # which would otherwise produce a `BroadcastResponse` full of
206
- # `nil`s and `success? => true`. That's the same silent
207
- # success-as-failure class of bug F5.13 closed for explicit
208
- # error statuses; closing it here for shape corruption too.
209
- unless body['txid']
210
- raise BroadcastError.new(
211
- 'ARC returned a malformed 2xx response',
212
- status_code: code
213
- )
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)
214
151
  end
215
-
216
- build_response(body)
217
- end
218
-
219
- def rejected_status?(body)
220
- # Case-insensitive match — the TypeScript reference
221
- # (`ts-sdk/src/transaction/broadcasters/ARC.ts:155-166`) explicitly
222
- # `.toUpperCase()`s both fields before membership / substring checks.
223
- # ARC has a documented history of emitting values outside its own
224
- # OpenAPI enum (e.g. `txStatus: "success"` for orphans in TS issue
225
- # #105), so case normalisation is the defensive choice.
226
- tx_status = body['txStatus'].to_s.upcase
227
- return true if REJECTED_STATUSES.include?(tx_status)
228
- return true if tx_status.include?(ORPHAN_MARKER)
229
-
230
- extra_info = body['extraInfo'].to_s.upcase
231
- return true if extra_info.include?(ORPHAN_MARKER)
232
-
233
- false
234
152
  end
235
153
 
236
- def parse_json(raw)
237
- JSON.parse(raw)
238
- rescue JSON::ParserError
239
- { 'detail' => raw }
240
- end
241
-
242
- def build_response(body)
243
- BroadcastResponse.new(
244
- txid: body['txid'],
245
- tx_status: body['txStatus'],
246
- message: body['title'],
247
- extra_info: body['extraInfo'],
248
- block_hash: body['blockHash'],
249
- block_height: body['blockHeight'],
250
- timestamp: body['timestamp'],
251
- competing_txs: body['competingTxs']
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]
252
162
  )
253
163
  end
254
-
255
- def handle_batch_response(response)
256
- code = response.code.to_i
257
- body = parse_json(response.body)
258
-
259
- unless (200..299).cover?(code)
260
- raise BroadcastError.new(
261
- body['detail'] || body['title'] || "HTTP #{code}",
262
- status_code: code
263
- )
264
- end
265
-
266
- unless body.is_a?(Array)
267
- raise BroadcastError.new(
268
- 'ARC returned a malformed batch response',
269
- status_code: code
270
- )
271
- end
272
-
273
- body.map { |item| build_response_or_error(item) }
274
- end
275
-
276
- def build_response_or_error(body)
277
- if rejected_status?(body)
278
- BroadcastError.new(
279
- body['detail'] || body['title'] || body['txStatus'],
280
- status_code: 200,
281
- txid: body['txid'],
282
- arc_status: body['txStatus'].to_s.upcase
283
- )
284
- elsif !body['txid']
285
- BroadcastError.new(
286
- 'ARC returned a malformed 2xx response',
287
- status_code: 200
288
- )
289
- else
290
- build_response(body)
291
- end
292
- end
293
164
  end
294
165
  end
295
166
  end