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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -0
- data/lib/bsv/auth/certificate.rb +4 -4
- data/lib/bsv/auth/verifiable_certificate.rb +1 -1
- data/lib/bsv/network/arc.rb +95 -224
- data/lib/bsv/network/protocol.rb +321 -0
- data/lib/bsv/network/protocols/arc.rb +351 -0
- data/lib/bsv/network/protocols/chaintracks.rb +39 -0
- data/lib/bsv/network/protocols/ordinals.rb +32 -0
- data/lib/bsv/network/protocols/taal_binary.rb +99 -0
- data/lib/bsv/network/protocols/woc_rest.rb +301 -0
- data/lib/bsv/network/protocols.rb +17 -0
- data/lib/bsv/network/provider.rb +123 -0
- data/lib/bsv/network/providers/gorilla_pool.rb +61 -0
- data/lib/bsv/network/providers/taal.rb +57 -0
- data/lib/bsv/network/providers/whats_on_chain.rb +72 -0
- data/lib/bsv/network/providers.rb +25 -0
- data/lib/bsv/network/result.rb +119 -0
- data/lib/bsv/network/whats_on_chain.rb +78 -40
- data/lib/bsv/network.rb +5 -0
- data/lib/bsv/overlay/admin_token_template.rb +2 -2
- data/lib/bsv/script/push_drop_template.rb +1 -1
- data/lib/bsv/transaction/chain_trackers/chaintracks.rb +45 -49
- data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +57 -50
- data/lib/bsv/transaction/chain_trackers.rb +3 -4
- data/lib/bsv/transaction/fee_models/live_policy.rb +3 -2
- data/lib/bsv/transaction/transaction.rb +52 -7
- data/lib/bsv/transaction/verification_error.rb +11 -0
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv-sdk.rb +1 -5
- metadata +14 -5
- data/lib/bsv/messages.rb +0 -16
- data/lib/bsv/wallet/insufficient_funds_error.rb +0 -15
- data/lib/bsv/wallet/wallet.rb +0 -120
- 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)
|
|
13
|
-
# #fetch_transaction(txid)
|
|
14
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
31
|
-
|
|
45
|
+
result = @protocol.call(:get_utxos_all, address)
|
|
46
|
+
raise_on_error(result)
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
result.data.map do |entry|
|
|
34
49
|
UTXO.new(
|
|
35
|
-
tx_hash: entry[
|
|
36
|
-
tx_pos: entry[
|
|
37
|
-
satoshis: entry[
|
|
38
|
-
height: entry[
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
status_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::
|
|
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::
|
|
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::
|
|
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
|
-
#
|
|
13
|
-
#
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
43
|
-
return false if
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
data
|
|
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
|
-
|
|
79
|
-
status_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
|