bsv-sdk 0.15.0 → 0.17.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 +38 -0
- data/lib/bsv/auth/auth_middleware.rb +6 -6
- data/lib/bsv/auth/certificate.rb +22 -18
- data/lib/bsv/auth/master_certificate.rb +5 -5
- data/lib/bsv/auth/nonce.rb +13 -13
- data/lib/bsv/auth/peer.rb +53 -53
- data/lib/bsv/auth/verifiable_certificate.rb +1 -1
- data/lib/bsv/identity/client.rb +27 -32
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +18 -12
- data/lib/bsv/mcp/tools/check_balance.rb +16 -4
- data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
- data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
- data/lib/bsv/mcp/tools/helpers.rb +2 -2
- data/lib/bsv/network/arc.rb +13 -153
- data/lib/bsv/network/broadcast_error.rb +1 -0
- data/lib/bsv/network/broadcast_response.rb +1 -0
- data/lib/bsv/network/protocols/arc.rb +4 -3
- data/lib/bsv/network/protocols/taal_binary.rb +1 -0
- data/lib/bsv/network/protocols/woc_rest.rb +2 -1
- data/lib/bsv/network/whats_on_chain.rb +13 -107
- data/lib/bsv/overlay/admin_token_template.rb +4 -4
- data/lib/bsv/overlay/lookup_resolver.rb +1 -0
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
- data/lib/bsv/overlay/types.rb +1 -0
- data/lib/bsv/primitives/hex.rb +64 -0
- data/lib/bsv/registry/client.rb +26 -28
- data/lib/bsv/registry/types.rb +1 -0
- data/lib/bsv/script/interpreter/interpreter.rb +7 -0
- data/lib/bsv/script/interpreter/operations/crypto.rb +7 -1
- data/lib/bsv/script/push_drop_template.rb +4 -4
- data/lib/bsv/transaction/beef.rb +122 -83
- data/lib/bsv/transaction/merkle_path.rb +54 -38
- data/lib/bsv/transaction/transaction.rb +81 -30
- data/lib/bsv/transaction/transaction_input.rb +23 -18
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +47 -0
- data/lib/bsv/wallet/interface/brc100.rb +270 -0
- data/lib/bsv/wallet/interface.rb +9 -0
- data/lib/bsv/wallet/proto_wallet/key_deriver.rb +152 -0
- data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
- data/lib/bsv/wallet/proto_wallet.rb +327 -0
- data/lib/bsv/wallet.rb +16 -0
- data/lib/bsv-sdk.rb +18 -1
- metadata +22 -1
|
@@ -49,8 +49,17 @@ module BSV
|
|
|
49
49
|
|
|
50
50
|
def self.call(txid:, network: nil, server_context: nil)
|
|
51
51
|
net_sym = Helpers.resolve_network_sym(network, server_context)
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
53
|
+
fetch_result = provider.call(:get_tx, txid)
|
|
54
|
+
|
|
55
|
+
unless fetch_result.success?
|
|
56
|
+
code = fetch_result.metadata[:status_code]
|
|
57
|
+
msg = fetch_result.message
|
|
58
|
+
msg = "#{msg} (HTTP #{code})" if code
|
|
59
|
+
return Helpers.error_response(msg)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
tx = BSV::Transaction::Transaction.from_hex(fetch_result.data)
|
|
54
63
|
|
|
55
64
|
result = {
|
|
56
65
|
hex: tx.to_hex,
|
|
@@ -61,8 +70,6 @@ module BSV
|
|
|
61
70
|
[::MCP::Content::Text.new(result.to_json)],
|
|
62
71
|
structured_content: result
|
|
63
72
|
)
|
|
64
|
-
rescue BSV::Network::ChainProviderError => e
|
|
65
|
-
Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
|
|
66
73
|
rescue ArgumentError => e
|
|
67
74
|
Helpers.error_response(e.message)
|
|
68
75
|
end
|
|
@@ -50,8 +50,22 @@ module BSV
|
|
|
50
50
|
|
|
51
51
|
def self.call(address:, network: nil, server_context: nil)
|
|
52
52
|
net_sym = Helpers.resolve_network_sym(network, server_context)
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
54
|
+
utxo_result = provider.call(:get_utxos_all, address)
|
|
55
|
+
|
|
56
|
+
unless utxo_result.success?
|
|
57
|
+
code = utxo_result.metadata[:status_code]
|
|
58
|
+
msg = utxo_result.message
|
|
59
|
+
msg = "#{msg} (HTTP #{code})" if code
|
|
60
|
+
return Helpers.error_response(msg)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
utxos = utxo_result.data.map do |entry|
|
|
64
|
+
BSV::Network::UTXO.new(
|
|
65
|
+
tx_hash: entry[:tx_hash], tx_pos: entry[:tx_pos],
|
|
66
|
+
satoshis: entry[:satoshis], height: entry[:height]
|
|
67
|
+
)
|
|
68
|
+
end
|
|
55
69
|
|
|
56
70
|
result = {
|
|
57
71
|
address: address,
|
|
@@ -63,8 +77,6 @@ module BSV
|
|
|
63
77
|
[::MCP::Content::Text.new(result.to_json)],
|
|
64
78
|
structured_content: result
|
|
65
79
|
)
|
|
66
|
-
rescue BSV::Network::ChainProviderError => e
|
|
67
|
-
Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
|
|
68
80
|
end
|
|
69
81
|
end
|
|
70
82
|
end
|
|
@@ -25,7 +25,7 @@ module BSV
|
|
|
25
25
|
# @return [Hash]
|
|
26
26
|
def self.transaction_to_h(tx)
|
|
27
27
|
{
|
|
28
|
-
txid: tx.txid_hex,
|
|
28
|
+
txid: tx.txid_hex, # MCP tool boundary: display-order hex for human consumption
|
|
29
29
|
version: tx.version,
|
|
30
30
|
lock_time: tx.lock_time,
|
|
31
31
|
inputs: tx.inputs.each_with_index.map { |inp, i| input_to_h(inp, i) },
|
|
@@ -38,7 +38,7 @@ module BSV
|
|
|
38
38
|
def self.input_to_h(input, _index)
|
|
39
39
|
unlock_script = input.unlocking_script
|
|
40
40
|
{
|
|
41
|
-
prev_txid: input.
|
|
41
|
+
prev_txid: input.dtxid_hex,
|
|
42
42
|
vout: input.prev_tx_out_index,
|
|
43
43
|
script_hex: unlock_script ? unlock_script.to_hex : '',
|
|
44
44
|
script_asm: unlock_script ? unlock_script.to_asm : '',
|
data/lib/bsv/network/arc.rb
CHANGED
|
@@ -2,164 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
5
|
class BroadcastError < StandardError
|
|
6
|
+
# ARC API boundary: display-order hex txid as returned by the ARC error response.
|
|
6
7
|
attr_reader :status_code, :txid, :arc_status
|
|
7
8
|
|
|
8
9
|
def initialize(message, status_code: nil, txid: nil, arc_status: nil)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
5
|
class BroadcastResponse
|
|
6
|
+
# ARC API boundary: display-order hex txid as returned by the ARC broadcast endpoint.
|
|
6
7
|
attr_reader :txid, :tx_status, :message, :extra_info, :block_hash, :block_height, :timestamp, :competing_txs
|
|
7
8
|
|
|
8
9
|
def initialize(attrs = {})
|
|
@@ -138,7 +138,8 @@ module BSV
|
|
|
138
138
|
# lacks source_satoshis / source_locking_script.
|
|
139
139
|
def ef_hex_with_fallback(tx)
|
|
140
140
|
tx.to_ef_hex
|
|
141
|
-
rescue ArgumentError
|
|
141
|
+
rescue ArgumentError => e
|
|
142
|
+
BSV.logger&.debug { "[ARC] EF serialisation failed: #{e.message} — falling back to raw hex" }
|
|
142
143
|
tx.to_hex
|
|
143
144
|
end
|
|
144
145
|
|
|
@@ -269,7 +270,7 @@ module BSV
|
|
|
269
270
|
# same field set as broadcast responses rather than the raw parsed JSON.
|
|
270
271
|
# Also checks for rejection status and missing txid (malformed 2xx).
|
|
271
272
|
#
|
|
272
|
-
# @param txid [String]
|
|
273
|
+
# @param txid [String] ARC API boundary: display-order hex transaction ID to query
|
|
273
274
|
# @return [Result::Success, Result::Error, Result::NotFound]
|
|
274
275
|
def call_get_tx_status(txid, **)
|
|
275
276
|
response = default_call(:get_tx_status, txid)
|
|
@@ -306,7 +307,7 @@ module BSV
|
|
|
306
307
|
# @return [Hash]
|
|
307
308
|
def arc_data_from(body)
|
|
308
309
|
{
|
|
309
|
-
txid: body['txid'],
|
|
310
|
+
txid: body['txid'], # ARC API boundary: display-order hex from the ARC JSON response
|
|
310
311
|
tx_status: body['txStatus'],
|
|
311
312
|
message: body['title'],
|
|
312
313
|
extra_info: body['extraInfo'],
|
|
@@ -53,6 +53,7 @@ module BSV
|
|
|
53
53
|
code = response.code.to_i
|
|
54
54
|
body = parse_json_body(response.body)
|
|
55
55
|
|
|
56
|
+
# TAAL API boundary: display-order hex txid from the TAAL broadcast response
|
|
56
57
|
return Result::Success.new(data: { txid: body['txid'] }) if already_known?(body) && body['txid']
|
|
57
58
|
|
|
58
59
|
retryable = code == 429 || (500..599).cover?(code)
|
|
@@ -175,7 +175,7 @@ module BSV
|
|
|
175
175
|
# The +script_hash:+ keyword is accepted for future fallback support
|
|
176
176
|
# but not used in this implementation.
|
|
177
177
|
#
|
|
178
|
-
# @param txid [String] transaction ID
|
|
178
|
+
# @param txid [String] WoC API boundary: display-order hex transaction ID
|
|
179
179
|
# @param vout [Integer] output index
|
|
180
180
|
# @param script_hash [String, nil] ignored
|
|
181
181
|
# @return [Result::Success<Boolean>, Result::Error, Result::NotFound]
|
|
@@ -242,6 +242,7 @@ module BSV
|
|
|
242
242
|
return result unless result.success?
|
|
243
243
|
|
|
244
244
|
# WoC returns plain-text txid — result.data is the raw body string
|
|
245
|
+
# WoC API boundary: display-order hex txid returned as plain text
|
|
245
246
|
Result::Success.new(data: { txid: result.data.to_s.strip })
|
|
246
247
|
end
|
|
247
248
|
|
|
@@ -2,118 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
17
|
+
def self.default(**)
|
|
18
|
+
raise DeprecationError, MESSAGE
|
|
76
19
|
end
|
|
77
20
|
|
|
78
|
-
|
|
79
|
-
|
|
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]
|
|
@@ -334,6 +334,7 @@ module BSV
|
|
|
334
334
|
beef_data = output['beef'] || output[:beef]
|
|
335
335
|
output_index = (output['outputIndex'] || output[:output_index] || 0).to_i
|
|
336
336
|
|
|
337
|
+
# Overlay API boundary: outpoint key uses display-order txid bytes (via Transaction#txid)
|
|
337
338
|
txid =
|
|
338
339
|
begin
|
|
339
340
|
beef = parse_beef(beef_data)
|
|
@@ -110,7 +110,7 @@ module BSV
|
|
|
110
110
|
|
|
111
111
|
OverlayBroadcastResult.new(
|
|
112
112
|
status: 'success',
|
|
113
|
-
txid: tx.txid_hex,
|
|
113
|
+
txid: tx.txid_hex, # Overlay API boundary: display-order hex txid for the broadcast result
|
|
114
114
|
message: "Sent to #{successful.size} Overlay Service host(s)."
|
|
115
115
|
)
|
|
116
116
|
end
|
data/lib/bsv/overlay/types.rb
CHANGED
|
@@ -82,6 +82,7 @@ module BSV
|
|
|
82
82
|
# @return [String] result status ('success' or 'error')
|
|
83
83
|
attr_reader :status
|
|
84
84
|
|
|
85
|
+
# Overlay API boundary: display-order hex txid echoed from the broadcast response.
|
|
85
86
|
# @return [String, nil] transaction identifier (present on success)
|
|
86
87
|
attr_reader :txid
|
|
87
88
|
|
data/lib/bsv/primitives/hex.rb
CHANGED
|
@@ -72,6 +72,70 @@ module BSV
|
|
|
72
72
|
def self.encode(bytes)
|
|
73
73
|
bytes.unpack1('H*')
|
|
74
74
|
end
|
|
75
|
+
|
|
76
|
+
# Validate that +value+ is a 32-byte wire-order transaction ID.
|
|
77
|
+
#
|
|
78
|
+
# @param value [String] expected 32-byte binary string
|
|
79
|
+
# @param name [String] label for the error message (e.g. +'prev_wtxid'+)
|
|
80
|
+
# @return [String] the input value (pass-through for chaining)
|
|
81
|
+
# @raise [ArgumentError] if +value+ is not a 32-byte binary string
|
|
82
|
+
def self.validate_wtxid!(value, name: 'wtxid')
|
|
83
|
+
unless value.is_a?(String) && value.bytesize == 32
|
|
84
|
+
hint = if value.is_a?(String) && value.bytesize == 64 && value.match?(HEX_RE)
|
|
85
|
+
' (looks like a hex txid — use wtxid_from_hex to convert)'
|
|
86
|
+
else
|
|
87
|
+
''
|
|
88
|
+
end
|
|
89
|
+
size = value.is_a?(String) ? "#{value.bytesize}-byte string" : value.class.to_s
|
|
90
|
+
raise ArgumentError,
|
|
91
|
+
"expected 32-byte wire-order wtxid for #{name}, got #{size}#{hint}"
|
|
92
|
+
end
|
|
93
|
+
value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Validate that +value+ is a 32-byte binary hash.
|
|
97
|
+
#
|
|
98
|
+
# General-purpose validator for any 32-byte hash (merkle nodes, roots,
|
|
99
|
+
# etc.) — not specific to transaction IDs. For txid-specific validation
|
|
100
|
+
# use {.validate_wtxid!} or {.validate_dtxid_hex!} instead.
|
|
101
|
+
#
|
|
102
|
+
# @param value [String] expected 32-byte binary string
|
|
103
|
+
# @param name [String] label for the error message
|
|
104
|
+
# @return [String] the input value (pass-through for chaining)
|
|
105
|
+
# @raise [ArgumentError] if +value+ is not a 32-byte binary string
|
|
106
|
+
def self.validate_hash32!(value, name: 'hash')
|
|
107
|
+
unless value.is_a?(String) && value.bytesize == 32
|
|
108
|
+
hint = if value.is_a?(String) && value.bytesize == 64 && value.match?(HEX_RE)
|
|
109
|
+
' (looks like hex — decode it first)'
|
|
110
|
+
else
|
|
111
|
+
''
|
|
112
|
+
end
|
|
113
|
+
size = value.is_a?(String) ? "#{value.bytesize}-byte string" : value.class.to_s
|
|
114
|
+
raise ArgumentError,
|
|
115
|
+
"expected 32-byte hash for #{name}, got #{size}#{hint}"
|
|
116
|
+
end
|
|
117
|
+
value
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Validate that +value+ is a 64-character display-order hex transaction ID.
|
|
121
|
+
#
|
|
122
|
+
# @param value [String] expected 64-char hex string
|
|
123
|
+
# @param name [String] label for the error message (e.g. +'dtxid_hex'+)
|
|
124
|
+
# @return [String] the input value (pass-through for chaining)
|
|
125
|
+
# @raise [ArgumentError] if +value+ is not a 64-char hex string
|
|
126
|
+
def self.validate_dtxid_hex!(value, name: 'dtxid_hex')
|
|
127
|
+
unless value.is_a?(String) && value.length == 64 && value.match?(HEX_RE)
|
|
128
|
+
hint = if value.is_a?(String) && value.bytesize == 32 && !value.match?(HEX_RE)
|
|
129
|
+
' (looks like binary bytes — use dtxid_hex or unpack to convert)'
|
|
130
|
+
else
|
|
131
|
+
''
|
|
132
|
+
end
|
|
133
|
+
size = value.is_a?(String) ? "#{value.length}-char string" : value.class.to_s
|
|
134
|
+
raise ArgumentError,
|
|
135
|
+
"expected 64-char display-order hex for #{name}, got #{size}#{hint}"
|
|
136
|
+
end
|
|
137
|
+
value
|
|
138
|
+
end
|
|
75
139
|
end
|
|
76
140
|
end
|
|
77
141
|
end
|