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
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ module Providers
6
+ # TAAL returns pre-configured Provider instances using the TAAL infrastructure.
7
+ #
8
+ # Mainnet composes two protocols:
9
+ # - ARC at +https://arc.taal.com+ for standard ARC operations
10
+ # - TAALBinary at +https://api.taal.com+ for binary broadcast
11
+ #
12
+ # ARC is registered first, so +:broadcast+ is served by ARC (first-registered wins).
13
+ # TAALBinary registers its own +:broadcast+ command but will not win the index.
14
+ # To use TAALBinary directly, call +provider.protocol_for(:broadcast)+ on the
15
+ # TAALBinary instance via +provider.protocols.last+, or build a custom Provider.
16
+ #
17
+ # There is no TAAL testnet default — TAAL does not publish a supported testnet ARC URL.
18
+ #
19
+ # == Example
20
+ #
21
+ # provider = BSV::Network::Providers::TAAL.mainnet(api_key: 'mainnet_...')
22
+ # provider.call(:broadcast, tx)
23
+ class TAAL
24
+ # Returns a mainnet Provider configured with ARC and TAALBinary.
25
+ #
26
+ # @param opts [Hash] keyword arguments forwarded to each protocol constructor
27
+ # @return [Provider]
28
+ def self.mainnet(**opts)
29
+ common = opts.slice(:api_key, :http_client)
30
+ Provider.new('TAAL') do |p|
31
+ p.protocol Protocols::ARC, base_url: 'https://arc.taal.com', **opts
32
+ p.protocol Protocols::TAALBinary, base_url: 'https://api.taal.com', **common
33
+ end
34
+ end
35
+
36
+ # Returns a testnet Provider configured with ARC only.
37
+ #
38
+ # @param opts [Hash] keyword arguments forwarded to each protocol constructor
39
+ # @return [Provider]
40
+ def self.testnet(**opts)
41
+ Provider.new('TAAL') do |p|
42
+ p.protocol Protocols::ARC, base_url: 'https://arc-test.taal.com', **opts
43
+ end
44
+ end
45
+
46
+ # Returns a mainnet or testnet Provider depending on the +testnet:+ flag.
47
+ #
48
+ # @param testnet [Boolean] when true, returns the testnet Provider
49
+ # @param opts [Hash] keyword arguments forwarded to each protocol constructor
50
+ # @return [Provider]
51
+ def self.default(testnet: false, **opts)
52
+ testnet ? testnet(**opts) : mainnet(**opts)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ module Providers
6
+ # WhatsOnChain returns pre-configured Provider instances using the
7
+ # WhatsOnChain REST API (WoCREST protocol).
8
+ #
9
+ # The base URL is fully resolved per network — no +{network}+ template
10
+ # is used in provider defaults. The WoCREST protocol's +network:+ param
11
+ # is omitted since the URL already encodes the network segment.
12
+ #
13
+ # == Example
14
+ #
15
+ # provider = BSV::Network::Providers::WhatsOnChain.mainnet
16
+ # provider.call(:get_tx, 'abc123...')
17
+ #
18
+ # provider = BSV::Network::Providers::WhatsOnChain.testnet(api_key: 'my-key')
19
+ # provider.call(:broadcast, tx)
20
+ class WhatsOnChain
21
+ # Returns a mainnet Provider configured with WoCREST.
22
+ #
23
+ # @param opts [Hash] keyword arguments forwarded to each protocol constructor
24
+ # @return [Provider]
25
+ def self.mainnet(**opts)
26
+ Provider.new('WhatsOnChain') do |p|
27
+ p.protocol Protocols::WoCREST, base_url: 'https://api.whatsonchain.com/v1/bsv/main', **opts
28
+ end
29
+ end
30
+
31
+ # Returns a testnet Provider configured with WoCREST.
32
+ #
33
+ # @param opts [Hash] keyword arguments forwarded to each protocol constructor
34
+ # @return [Provider]
35
+ def self.testnet(**opts)
36
+ Provider.new('WhatsOnChain') do |p|
37
+ p.protocol Protocols::WoCREST, base_url: 'https://api.whatsonchain.com/v1/bsv/test', **opts
38
+ end
39
+ end
40
+
41
+ # Returns a Provider for the BSV Scaling Test Network (STN).
42
+ #
43
+ # @param opts [Hash] keyword arguments forwarded to each protocol constructor
44
+ # @return [Provider]
45
+ def self.stn(**opts)
46
+ Provider.new('WhatsOnChain') do |p|
47
+ p.protocol Protocols::WoCREST, base_url: 'https://api.whatsonchain.com/v1/bsv/stn', **opts
48
+ end
49
+ end
50
+
51
+ # Returns a Provider for the given network.
52
+ #
53
+ # @param testnet [Boolean] when true, returns the testnet Provider
54
+ # @param network [Symbol, nil] explicit network (:main, :test, :stn) — overrides +testnet:+
55
+ # @param opts [Hash] keyword arguments forwarded to each protocol constructor
56
+ # @return [Provider]
57
+ def self.default(testnet: false, network: nil, **opts)
58
+ if network
59
+ case network.to_sym
60
+ when :main, :mainnet then mainnet(**opts)
61
+ when :test, :testnet then testnet(**opts)
62
+ when :stn then stn(**opts)
63
+ else raise ArgumentError, "unknown network: #{network}"
64
+ end
65
+ else
66
+ testnet ? testnet(**opts) : mainnet(**opts)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ # Providers is a namespace module for pre-configured Provider factory classes.
6
+ #
7
+ # Each provider class has +.mainnet+ and +.testnet+ class methods that return
8
+ # a fully-configured +Provider+ instance with the appropriate protocol set.
9
+ # Both methods accept +**opts+ which are forwarded to each protocol constructor
10
+ # (e.g. +api_key:+, +http_client:+).
11
+ #
12
+ # == Example
13
+ #
14
+ # provider = BSV::Network::Providers::GorillaPool.mainnet
15
+ # result = provider.call(:broadcast, tx)
16
+ #
17
+ # provider = BSV::Network::Providers::TAAL.mainnet(api_key: 'my-key')
18
+ # result = provider.call(:broadcast, tx)
19
+ module Providers
20
+ autoload :GorillaPool, 'bsv/network/providers/gorilla_pool'
21
+ autoload :WhatsOnChain, 'bsv/network/providers/whats_on_chain'
22
+ autoload :TAAL, 'bsv/network/providers/taal'
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ # Result module provides three immutable value types for Protocol dispatch outcomes.
6
+ #
7
+ # All three types share a Predicates mixin with default false implementations.
8
+ # Each type overrides only the predicate that returns true for that type.
9
+ module Result
10
+ # Mixin providing default false implementations for all query predicates.
11
+ module Predicates
12
+ def success?
13
+ false
14
+ end
15
+
16
+ def error?
17
+ false
18
+ end
19
+
20
+ def not_found?
21
+ false
22
+ end
23
+ end
24
+
25
+ # Represents a successful outcome. Carries the response payload in +data+
26
+ # and optional protocol-specific extras in +metadata+.
27
+ class Success
28
+ include Predicates
29
+
30
+ attr_reader :data, :metadata
31
+
32
+ def initialize(data:, metadata: {})
33
+ @data = data
34
+ @metadata = metadata.freeze
35
+ freeze
36
+ end
37
+
38
+ def success?
39
+ true
40
+ end
41
+
42
+ def ==(other)
43
+ other.is_a?(Success) && data == other.data && metadata == other.metadata
44
+ end
45
+
46
+ alias eql? ==
47
+
48
+ def hash
49
+ [self.class, data, metadata].hash
50
+ end
51
+ end
52
+
53
+ # Represents a failed outcome. Carries a human-readable +message+, a boolean
54
+ # +retryable+ flag indicating whether the caller should retry, and optional
55
+ # +metadata+ for structured protocol-specific details (e.g. +arc_status+).
56
+ class Error
57
+ include Predicates
58
+
59
+ attr_reader :message, :retryable, :metadata
60
+
61
+ def initialize(message:, retryable: false, metadata: {})
62
+ @message = message
63
+ @retryable = retryable
64
+ @metadata = metadata.freeze
65
+ freeze
66
+ end
67
+
68
+ def error?
69
+ true
70
+ end
71
+
72
+ def retryable?
73
+ @retryable
74
+ end
75
+
76
+ def ==(other)
77
+ other.is_a?(Error) &&
78
+ message == other.message &&
79
+ retryable == other.retryable &&
80
+ metadata == other.metadata
81
+ end
82
+
83
+ alias eql? ==
84
+
85
+ def hash
86
+ [self.class, message, retryable, metadata].hash
87
+ end
88
+ end
89
+
90
+ # Represents a resource-not-found outcome. Carries an optional human-readable
91
+ # +message+ and optional +metadata+.
92
+ class NotFound
93
+ include Predicates
94
+
95
+ attr_reader :message, :metadata
96
+
97
+ def initialize(message: nil, metadata: {})
98
+ @message = message
99
+ @metadata = metadata.freeze
100
+ freeze
101
+ end
102
+
103
+ def not_found?
104
+ true
105
+ end
106
+
107
+ def ==(other)
108
+ other.is_a?(NotFound) && message == other.message && metadata == other.metadata
109
+ end
110
+
111
+ alias eql? ==
112
+
113
+ def hash
114
+ [self.class, message, metadata].hash
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,41 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
-
7
3
  module BSV
8
4
  module Network
9
5
  # WhatsOnChain chain data provider for reading transactions and UTXOs
10
6
  # from the BSV network.
11
7
  #
12
- # Any object responding to #fetch_utxos(address) and
13
- # #fetch_transaction(txid) can serve as a chain data provider;
14
- # this class implements that contract using the WhatsOnChain API.
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
15
  #
16
16
  # The HTTP client is injectable for testability. It must respond to
17
17
  # #request(uri, request) and return an object with #code and #body.
18
18
  class WhatsOnChain
19
- BASE_URL = 'https://api.whatsonchain.com'
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
20
28
 
21
- def initialize(network: :mainnet, http_client: nil)
22
- @network = network == :mainnet ? 'main' : 'test'
23
- @http_client = http_client
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
24
39
  end
25
40
 
26
41
  # Fetch unspent transaction outputs for an address.
27
42
  # @param address [String] BSV address
28
43
  # @return [Array<UTXO>]
29
44
  def fetch_utxos(address)
30
- response = get("/v1/bsv/#{@network}/address/#{address}/unspent")
31
- body = JSON.parse(response.body)
45
+ result = @protocol.call(:get_utxos_all, address)
46
+ raise_on_error(result)
32
47
 
33
- body.map do |entry|
48
+ result.data.map do |entry|
34
49
  UTXO.new(
35
- tx_hash: entry['tx_hash'],
36
- tx_pos: entry['tx_pos'],
37
- satoshis: entry['value'],
38
- height: entry['height']
50
+ tx_hash: entry[:tx_hash],
51
+ tx_pos: entry[:tx_pos],
52
+ satoshis: entry[:satoshis],
53
+ height: entry[:height]
39
54
  )
40
55
  end
41
56
  end
@@ -44,37 +59,60 @@ module BSV
44
59
  # @param txid [String] transaction ID (hex)
45
60
  # @return [BSV::Transaction::Transaction]
46
61
  def fetch_transaction(txid)
47
- response = get("/v1/bsv/#{@network}/tx/#{txid}/hex")
48
- BSV::Transaction::Transaction.from_hex(response.body)
62
+ result = @protocol.call(:get_tx, txid)
63
+ raise_on_error(result)
64
+
65
+ BSV::Transaction::Transaction.from_hex(result.data)
49
66
  end
50
67
 
51
- private
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)
52
74
 
53
- def get(path)
54
- uri = URI("#{BASE_URL}#{path}")
55
- request = Net::HTTP::Get.new(uri)
56
- response = execute(uri, request)
57
- handle_response(response)
58
- response
75
+ result.data
59
76
  end
60
77
 
61
- def execute(uri, request)
62
- if @http_client
63
- @http_client.request(uri, request)
64
- else
65
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
66
- http.request(request)
67
- end
68
- end
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
69
87
  end
70
88
 
71
- def handle_response(response)
72
- code = response.code.to_i
73
- return if (200..299).cover?(code)
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?
74
112
 
75
113
  raise ChainProviderError.new(
76
- response.body || "HTTP #{code}",
77
- status_code: code
114
+ result.message || 'Request failed',
115
+ status_code: result.metadata[:status_code]
78
116
  )
79
117
  end
80
118
  end
data/lib/bsv/network.rb CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  module BSV
4
4
  module Network
5
+ autoload :Result, 'bsv/network/result'
6
+ autoload :Protocol, 'bsv/network/protocol'
7
+ autoload :Protocols, 'bsv/network/protocols'
8
+ autoload :Providers, 'bsv/network/providers'
9
+ autoload :Provider, 'bsv/network/provider'
5
10
  autoload :BroadcastError, 'bsv/network/broadcast_error'
6
11
  autoload :BroadcastResponse, 'bsv/network/broadcast_response'
7
12
  autoload :ChainProviderError, 'bsv/network/chain_provider_error'
@@ -24,7 +24,7 @@ module BSV
24
24
  # <derived_pubkey> OP_CHECKSIG
25
25
  #
26
26
  # @example Lock a SHIP advertisement
27
- # wallet = BSV::Wallet::ProtoWallet.new(private_key)
27
+ # wallet = BSV::Wallet::Client.new(private_key, storage: BSV::Wallet::Store::Memory.new)
28
28
  # template = BSV::Overlay::AdminTokenTemplate.new(wallet)
29
29
  # locking_script = template.lock('SHIP', 'myhost.example.com', 'tm_payments')
30
30
  # decoded = BSV::Overlay::AdminTokenTemplate.decode(locking_script)
@@ -144,7 +144,7 @@ module BSV
144
144
  # Construct a new AdminTokenTemplate instance.
145
145
  #
146
146
  # @param wallet [#get_public_key, #create_signature] any object implementing
147
- # the BRC-100 wallet interface (e.g. {BSV::Wallet::ProtoWallet})
147
+ # the BRC-100 wallet interface (e.g. {BSV::Wallet::Client})
148
148
  # @param originator [String, nil] optional FQDN of the originating application
149
149
  def initialize(wallet, originator: nil)
150
150
  @wallet = wallet
@@ -39,7 +39,7 @@ module BSV
39
39
  # keyrings from +prove_certificate+) for field-level integrity.
40
40
  #
41
41
  # @example Lock a token
42
- # wallet = BSV::Wallet::ProtoWallet.new(private_key)
42
+ # wallet = BSV::Wallet::Client.new(private_key, storage: BSV::Wallet::Store::Memory.new)
43
43
  # template = BSV::Script::PushDropTemplate.new(wallet:)
44
44
  # script = template.lock(
45
45
  # fields: ['hello'.b, 'world'.b],
@@ -1,16 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
-
7
3
  module BSV
8
4
  module Transaction
9
5
  module ChainTrackers
10
6
  # Chain tracker that verifies merkle roots using the Chaintracks API (Arcade/GorillaPool).
11
7
  #
12
- # Queries the Chaintracks v2 block header endpoint to retrieve the merkle root for a
13
- # given block height and compares it with the provided root.
8
+ # Delegates all HTTP communication to {BSV::Network::Protocols::Chaintracks}.
9
+ # The constructor signature and {ChainTracker} contract are preserved.
14
10
  #
15
11
  # @example
16
12
  # tracker = BSV::Transaction::ChainTrackers::Chaintracks.new
@@ -20,17 +16,37 @@ module BSV
20
16
  # tracker = BSV::Transaction::ChainTrackers::Chaintracks.new(api_key: 'my-key')
21
17
  # tracker.current_height
22
18
  class Chaintracks < ChainTracker
23
- MAINNET_URL = BSV::MAINNET_URL
24
- TESTNET_URL = BSV::TESTNET_URL
19
+ # Returns a Chaintracks instance using the GorillaPool 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 [Chaintracks]
24
+ def self.default(testnet: false, **opts)
25
+ provider = BSV::Network::Providers::GorillaPool.default(testnet: testnet, **opts)
26
+ protocol = provider.protocol_for(:current_height)
27
+ new(protocol: protocol)
28
+ end
25
29
 
26
- # @param url [String] base URL for the Chaintracks API
30
+ # @param url [String, nil] base URL (legacy compat prefer .default or protocol:)
27
31
  # @param api_key [String, nil] optional Bearer API key
28
32
  # @param http_client [#request, nil] injectable HTTP client for testing
29
- def initialize(url: MAINNET_URL, api_key: nil, http_client: nil)
33
+ # @param protocol [BSV::Network::Protocols::Chaintracks, nil] pre-configured protocol
34
+ def initialize(url: nil, api_key: nil, http_client: nil, protocol: nil)
30
35
  super()
31
- @url = url.chomp('/')
32
- @api_key = api_key
33
- @http_client = http_client
36
+ if protocol
37
+ @protocol = protocol
38
+ elsif url
39
+ @url = url.chomp('/')
40
+ @api_key = api_key
41
+ @protocol = BSV::Network::Protocols::Chaintracks.new(
42
+ base_url: @url,
43
+ api_key: api_key,
44
+ http_client: http_client
45
+ )
46
+ else
47
+ provider = BSV::Network::Providers::GorillaPool.default(api_key: api_key, http_client: http_client)
48
+ @protocol = provider.protocol_for(:current_height)
49
+ end
34
50
  end
35
51
 
36
52
  # Verify that a merkle root is valid for the given block height.
@@ -38,12 +54,19 @@ module BSV
38
54
  # @param root [String] merkle root as a hex string
39
55
  # @param height [Integer] block height
40
56
  # @return [Boolean]
57
+ # @raise [BSV::Network::ChainProviderError] on network or API error
41
58
  def valid_root_for_height?(root, height)
42
- response = get("/chaintracks/v2/header/height/#{height}")
43
- return false if response.nil?
59
+ result = @protocol.call(:get_block_header, height)
60
+ return false if result.not_found?
61
+
62
+ if result.error?
63
+ raise BSV::Network::ChainProviderError.new(
64
+ result.message.to_s,
65
+ status_code: result.metadata[:status_code]
66
+ )
67
+ end
44
68
 
45
- data = JSON.parse(response.body)
46
- merkle_root = data['merkleRoot']
69
+ merkle_root = result.data['merkleRoot']
47
70
  return false unless merkle_root
48
71
 
49
72
  merkle_root.downcase == root.downcase
@@ -52,43 +75,16 @@ module BSV
52
75
  # Return the current blockchain height.
53
76
  #
54
77
  # @return [Integer]
78
+ # @raise [BSV::Network::ChainProviderError] on network or API error
55
79
  def current_height
56
- response = get('/chaintracks/v2/tip', not_found_returns_nil: false)
57
- data = JSON.parse(response.body)
58
- data['height']
59
- end
60
-
61
- private
62
-
63
- # @param path [String] API path
64
- # @param not_found_returns_nil [Boolean] if true, return nil on 404 instead of raising
65
- # @return [Net::HTTPResponse, nil]
66
- def get(path, not_found_returns_nil: true)
67
- uri = URI("#{@url}#{path}")
68
- request = Net::HTTP::Get.new(uri)
69
- request['Authorization'] = "Bearer #{@api_key}" if @api_key
70
-
71
- response = execute(uri, request)
72
- code = response.code.to_i
73
-
74
- return nil if not_found_returns_nil && code == 404
75
- return response if (200..299).cover?(code)
80
+ result = @protocol.call(:current_height)
81
+ return result.data if result.success?
76
82
 
77
83
  raise BSV::Network::ChainProviderError.new(
78
- response.body || "HTTP #{code}",
79
- status_code: code
84
+ result.message.to_s,
85
+ status_code: result.metadata[:status_code]
80
86
  )
81
87
  end
82
-
83
- def execute(uri, request)
84
- if @http_client
85
- @http_client.request(uri, request)
86
- else
87
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
88
- http.request(request)
89
- end
90
- end
91
- end
92
88
  end
93
89
  end
94
90
  end