bsv-sdk 0.18.0 → 0.19.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 +37 -0
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +4 -4
- data/lib/bsv/mcp/tools/check_balance.rb +2 -2
- data/lib/bsv/mcp/tools/fetch_tx.rb +2 -2
- data/lib/bsv/mcp/tools/fetch_utxos.rb +2 -2
- data/lib/bsv/network/protocol.rb +41 -30
- data/lib/bsv/network/protocol_response.rb +86 -0
- data/lib/bsv/network/protocols/arc.rb +73 -119
- data/lib/bsv/network/protocols/ordinals.rb +10 -10
- data/lib/bsv/network/protocols/taal_binary.rb +11 -12
- data/lib/bsv/network/protocols/woc_rest.rb +25 -25
- data/lib/bsv/network/protocols.rb +2 -3
- data/lib/bsv/network/provider.rb +2 -2
- data/lib/bsv/network.rb +1 -6
- data/lib/bsv/transaction/chain_tracker.rb +30 -12
- data/lib/bsv/transaction/chain_trackers/chaintracks.rb +6 -14
- data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +6 -14
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/proto_wallet.rb +44 -8
- metadata +2 -7
- data/lib/bsv/network/arc.rb +0 -26
- data/lib/bsv/network/broadcast_error.rb +0 -18
- data/lib/bsv/network/broadcast_response.rb +0 -31
- data/lib/bsv/network/chain_provider_error.rb +0 -14
- data/lib/bsv/network/result.rb +0 -119
- data/lib/bsv/network/whats_on_chain.rb +0 -26
|
@@ -88,16 +88,16 @@ module BSV
|
|
|
88
88
|
#
|
|
89
89
|
# @param pos_txid [String, nil] transaction ID (positional form)
|
|
90
90
|
# @param txid [String, nil] transaction ID (keyword form)
|
|
91
|
-
# @return [
|
|
91
|
+
# @return [ProtocolResponse]
|
|
92
92
|
def call_get_tx(pos_txid = nil, txid: nil)
|
|
93
93
|
resolved = pos_txid || txid
|
|
94
94
|
raise ArgumentError, 'txid is required' if resolved.nil? || resolved.empty?
|
|
95
95
|
|
|
96
96
|
result = default_call(:get_tx, resolved)
|
|
97
|
-
return result unless result.
|
|
98
|
-
return
|
|
97
|
+
return result unless result.http_success?
|
|
98
|
+
return result.with(http_success: false, error_message: 'empty response body') if result.data.nil? || result.data.empty?
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
result.with(data: result.data.unpack1('H*'))
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Normalises the spend status response from the Ordinals API.
|
|
@@ -111,22 +111,22 @@ module BSV
|
|
|
111
111
|
#
|
|
112
112
|
# @param pos_outpoint [String, nil] outpoint in +"txid_vout"+ format (positional form)
|
|
113
113
|
# @param outpoint [String, nil] outpoint in +"txid_vout"+ format (keyword form)
|
|
114
|
-
# @return [
|
|
114
|
+
# @return [ProtocolResponse]
|
|
115
115
|
def call_get_spend(pos_outpoint = nil, outpoint: nil)
|
|
116
116
|
resolved = pos_outpoint || outpoint
|
|
117
117
|
raise ArgumentError, 'outpoint is required' if resolved.nil? || resolved.empty?
|
|
118
118
|
|
|
119
119
|
result = default_call(:get_spend, resolved)
|
|
120
|
-
return result unless result.
|
|
120
|
+
return result unless result.http_success?
|
|
121
121
|
|
|
122
122
|
spending_txid = JSON.parse(result.data).to_s.strip
|
|
123
123
|
if spending_txid.empty?
|
|
124
|
-
|
|
124
|
+
result.with(data: { spent: false })
|
|
125
125
|
else
|
|
126
|
-
|
|
126
|
+
result.with(data: { spent: true, spending_txid: spending_txid })
|
|
127
127
|
end
|
|
128
|
-
rescue JSON::ParserError => e
|
|
129
|
-
|
|
128
|
+
rescue JSON::ParserError, TypeError => e
|
|
129
|
+
result.with(http_success: false, error_message: "spend response parse error: #{e.message}")
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
end
|
|
@@ -19,7 +19,7 @@ module BSV
|
|
|
19
19
|
# auth: { api_key: 'mainnet_your_key_here' }
|
|
20
20
|
# )
|
|
21
21
|
# result = protocol.call(:broadcast, tx)
|
|
22
|
-
# puts result.data[:txid] if result.
|
|
22
|
+
# puts result.data[:txid] if result.http_success?
|
|
23
23
|
#
|
|
24
24
|
# @see https://docs.taal.com TAAL API documentation
|
|
25
25
|
class TAALBinary < BSV::Network::Protocol
|
|
@@ -42,7 +42,7 @@ module BSV
|
|
|
42
42
|
# TAAL-specific headers and applies TAAL-specific response quirk handling.
|
|
43
43
|
#
|
|
44
44
|
# @param tx [#to_binary, String] transaction object or raw binary string
|
|
45
|
-
# @return [
|
|
45
|
+
# @return [ProtocolResponse]
|
|
46
46
|
def call_broadcast(tx)
|
|
47
47
|
body = tx.respond_to?(:to_binary) ? tx.to_binary : tx
|
|
48
48
|
|
|
@@ -57,27 +57,26 @@ module BSV
|
|
|
57
57
|
parse_broadcast_response(response)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Maps the HTTP response from TAAL broadcast to a
|
|
61
|
-
# +txn-already-known+ idempotency quirk.
|
|
60
|
+
# Maps the HTTP response from TAAL broadcast to a ProtocolResponse, applying
|
|
61
|
+
# the +txn-already-known+ idempotency quirk.
|
|
62
62
|
#
|
|
63
63
|
# @param response [Net::HTTPResponse]
|
|
64
|
-
# @return [
|
|
64
|
+
# @return [ProtocolResponse]
|
|
65
65
|
def parse_broadcast_response(response)
|
|
66
66
|
code = response.code.to_i
|
|
67
67
|
body = parse_json_body(response.body)
|
|
68
68
|
|
|
69
|
-
# TAAL API boundary: display-order hex txid from the TAAL broadcast response
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
retryable = code == 429 || (500..599).cover?(code)
|
|
69
|
+
# TAAL API boundary: display-order hex txid from the TAAL broadcast response.
|
|
70
|
+
# The HTTP response class is non-2xx here, so we must pass http_success: true explicitly.
|
|
71
|
+
return ProtocolResponse.new(response, data: { txid: body['txid'] }, http_success: true) if already_known?(body) && body['txid']
|
|
73
72
|
|
|
74
73
|
if (200..299).cover?(code)
|
|
75
|
-
return
|
|
74
|
+
return ProtocolResponse.new(response, http_success: false, error_message: 'TAAL returned a malformed 2xx response') unless body['txid']
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
ProtocolResponse.new(response, data: { txid: body['txid'] })
|
|
78
77
|
else
|
|
79
78
|
message = (body.is_a?(Hash) && body['error']) || "HTTP #{code}"
|
|
80
|
-
|
|
79
|
+
ProtocolResponse.new(response, http_success: false, error_message: message)
|
|
81
80
|
end
|
|
82
81
|
end
|
|
83
82
|
|
|
@@ -21,7 +21,7 @@ module BSV
|
|
|
21
21
|
#
|
|
22
22
|
# woc = BSV::Network::Protocols::WoCREST.new(network: :main)
|
|
23
23
|
# result = woc.call(:get_tx, 'abc123...')
|
|
24
|
-
# puts result.data if result.
|
|
24
|
+
# puts result.data if result.http_success?
|
|
25
25
|
#
|
|
26
26
|
# @see https://developers.whatsonchain.com/ WhatsOnChain API documentation
|
|
27
27
|
class WoCREST < Protocol
|
|
@@ -169,18 +169,18 @@ module BSV
|
|
|
169
169
|
# @param txid [String] WoC API boundary: display-order hex transaction ID
|
|
170
170
|
# @param vout [Integer] output index
|
|
171
171
|
# @param script_hash [String, nil] ignored
|
|
172
|
-
# @return [
|
|
172
|
+
# @return [ProtocolResponse<Boolean>]
|
|
173
173
|
def call_is_utxo(txid, vout, script_hash: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
174
174
|
result = default_call(:is_utxo, txid, vout)
|
|
175
175
|
|
|
176
176
|
# 404 = no spending tx found = output is unspent
|
|
177
|
-
return
|
|
177
|
+
return result.with(data: true, http_success: true, error_message: nil) if result.http_not_found?
|
|
178
178
|
|
|
179
179
|
# Non-success, non-404 = genuine error
|
|
180
|
-
return result unless result.
|
|
180
|
+
return result unless result.http_success?
|
|
181
181
|
|
|
182
182
|
# 200 with spending tx details = output is spent
|
|
183
|
-
|
|
183
|
+
result.with(data: false)
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
# Bulk-checks whether a set of outputs are unspent.
|
|
@@ -192,15 +192,15 @@ module BSV
|
|
|
192
192
|
# Entries absent from the response (unknown outputs) are treated as spent.
|
|
193
193
|
#
|
|
194
194
|
# @param outpoints [Array<Hash>] array of +{ txid:, vout: }+ hashes
|
|
195
|
-
# @return [
|
|
195
|
+
# @return [ProtocolResponse<Hash{String => Boolean}>]
|
|
196
196
|
# On success, data is a hash mapping +"txid.vout"+ keys to booleans
|
|
197
197
|
# (+true+ = unspent, +false+ = spent).
|
|
198
198
|
def call_is_utxo_bulk(outpoints)
|
|
199
|
-
return
|
|
199
|
+
return ProtocolResponse.new(nil, data: {}, http_success: true) if outpoints.empty?
|
|
200
200
|
|
|
201
201
|
body = JSON.generate(utxos: outpoints.map { |op| { 'txid' => op[:txid].to_s, 'vout' => op[:vout].to_i } })
|
|
202
202
|
result = default_call(:is_utxo_bulk, body: body)
|
|
203
|
-
return result unless result.
|
|
203
|
+
return result unless result.http_success?
|
|
204
204
|
|
|
205
205
|
# Build a lookup from the response entries
|
|
206
206
|
# spentIn present and non-empty → spent (false); absent or empty → unspent (true)
|
|
@@ -219,7 +219,7 @@ module BSV
|
|
|
219
219
|
normalised[key] = false unless normalised.key?(key)
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
result.with(data: normalised)
|
|
223
223
|
end
|
|
224
224
|
|
|
225
225
|
# Broadcasts a raw transaction to WhatsOnChain.
|
|
@@ -229,17 +229,17 @@ module BSV
|
|
|
229
229
|
# stripped and wrapped in a Hash for caller convenience.
|
|
230
230
|
#
|
|
231
231
|
# @param tx [#to_hex, String] transaction object or raw hex string
|
|
232
|
-
# @return [
|
|
232
|
+
# @return [ProtocolResponse<{ txid: String }>]
|
|
233
233
|
def call_broadcast(tx)
|
|
234
234
|
hex = tx.respond_to?(:to_hex) ? tx.to_hex : tx.to_s
|
|
235
235
|
body = JSON.generate(txhex: hex)
|
|
236
236
|
|
|
237
237
|
result = default_call(:broadcast, body: body)
|
|
238
|
-
return result unless result.
|
|
238
|
+
return result unless result.http_success?
|
|
239
239
|
|
|
240
240
|
# WoC returns plain-text txid — result.data is the raw body string
|
|
241
241
|
# WoC API boundary: display-order hex txid returned as plain text
|
|
242
|
-
|
|
242
|
+
result.with(data: { txid: result.data.to_s.strip })
|
|
243
243
|
end
|
|
244
244
|
|
|
245
245
|
# Decodes a raw transaction hex by posting to the WoC decode endpoint.
|
|
@@ -248,7 +248,7 @@ module BSV
|
|
|
248
248
|
# full decoded transaction object.
|
|
249
249
|
#
|
|
250
250
|
# @param txhex [String] raw transaction hex string
|
|
251
|
-
# @return [
|
|
251
|
+
# @return [ProtocolResponse]
|
|
252
252
|
def call_decode_tx(txhex)
|
|
253
253
|
body = JSON.generate(txhex: txhex.to_s)
|
|
254
254
|
default_call(:decode_tx, body: body)
|
|
@@ -259,7 +259,7 @@ module BSV
|
|
|
259
259
|
# WoC expects +{ "txids": [...] }+ as the request body.
|
|
260
260
|
#
|
|
261
261
|
# @param txids [Array<String>] list of transaction IDs
|
|
262
|
-
# @return [
|
|
262
|
+
# @return [ProtocolResponse]
|
|
263
263
|
def call_get_tx_hex_bulk(txids)
|
|
264
264
|
body = JSON.generate(txids: txids)
|
|
265
265
|
default_call(:get_tx_hex_bulk, body: body)
|
|
@@ -270,7 +270,7 @@ module BSV
|
|
|
270
270
|
# WoC expects +{ "scripts": [...] }+ as the request body.
|
|
271
271
|
#
|
|
272
272
|
# @param script_hashes [Array<String>] list of script hashes
|
|
273
|
-
# @return [
|
|
273
|
+
# @return [ProtocolResponse]
|
|
274
274
|
def call_get_script_unspent_bulk(script_hashes)
|
|
275
275
|
body = JSON.generate(scripts: script_hashes)
|
|
276
276
|
default_call(:get_script_unspent_bulk, body: body)
|
|
@@ -281,7 +281,7 @@ module BSV
|
|
|
281
281
|
# WoC expects +{ "txids": [...] }+ as the request body.
|
|
282
282
|
#
|
|
283
283
|
# @param txids [Array<String>] list of transaction IDs
|
|
284
|
-
# @return [
|
|
284
|
+
# @return [ProtocolResponse]
|
|
285
285
|
def call_get_bulk_tx_details(txids)
|
|
286
286
|
body = JSON.generate(txids: txids)
|
|
287
287
|
default_call(:get_bulk_tx_details, body: body)
|
|
@@ -292,7 +292,7 @@ module BSV
|
|
|
292
292
|
# WoC expects +{ "txids": [{ "txid": "...", "vouts": [0, 1] }, ...] }+ as the request body.
|
|
293
293
|
#
|
|
294
294
|
# @param tx_vouts [Array<Hash>] array of +{ txid:, vouts: [Integer] }+ hashes
|
|
295
|
-
# @return [
|
|
295
|
+
# @return [ProtocolResponse]
|
|
296
296
|
def call_get_bulk_output_scripts(tx_vouts)
|
|
297
297
|
body = JSON.generate(txids: tx_vouts.map { |tv| { 'txid' => tv[:txid].to_s, 'vouts' => tv[:vouts] } })
|
|
298
298
|
default_call(:get_bulk_output_scripts, body: body)
|
|
@@ -303,7 +303,7 @@ module BSV
|
|
|
303
303
|
# WoC expects +{ "addresses": [...] }+ as the request body.
|
|
304
304
|
#
|
|
305
305
|
# @param addresses [Array<String>] list of addresses
|
|
306
|
-
# @return [
|
|
306
|
+
# @return [ProtocolResponse]
|
|
307
307
|
def call_get_bulk_address_utxos(addresses)
|
|
308
308
|
body = JSON.generate(addresses: addresses)
|
|
309
309
|
default_call(:get_bulk_address_utxos, body: body)
|
|
@@ -314,7 +314,7 @@ module BSV
|
|
|
314
314
|
# WoC expects +{ "addresses": [...] }+ as the request body.
|
|
315
315
|
#
|
|
316
316
|
# @param addresses [Array<String>] list of addresses
|
|
317
|
-
# @return [
|
|
317
|
+
# @return [ProtocolResponse]
|
|
318
318
|
def call_get_bulk_address_unconfirmed_utxos(addresses)
|
|
319
319
|
body = JSON.generate(addresses: addresses)
|
|
320
320
|
default_call(:get_bulk_address_unconfirmed_utxos, body: body)
|
|
@@ -325,7 +325,7 @@ module BSV
|
|
|
325
325
|
# WoC expects +{ "scripts": [...] }+ as the request body.
|
|
326
326
|
#
|
|
327
327
|
# @param script_hashes [Array<String>] list of script hashes
|
|
328
|
-
# @return [
|
|
328
|
+
# @return [ProtocolResponse]
|
|
329
329
|
def call_get_bulk_script_unconfirmed_unspent(script_hashes)
|
|
330
330
|
body = JSON.generate(scripts: script_hashes)
|
|
331
331
|
default_call(:get_bulk_script_unconfirmed_unspent, body: body)
|
|
@@ -336,7 +336,7 @@ module BSV
|
|
|
336
336
|
# WoC expects +{ "query": "..." }+ as the request body.
|
|
337
337
|
#
|
|
338
338
|
# @param query [String] search term
|
|
339
|
-
# @return [
|
|
339
|
+
# @return [ProtocolResponse]
|
|
340
340
|
def call_search_links(query)
|
|
341
341
|
body = JSON.generate(query: query)
|
|
342
342
|
default_call(:search_links, body: body)
|
|
@@ -351,7 +351,7 @@ module BSV
|
|
|
351
351
|
#
|
|
352
352
|
# @param txids [Array<String>, nil] list of transaction IDs (positional)
|
|
353
353
|
# @param body [String, nil] pre-serialised request body (keyword)
|
|
354
|
-
# @return [
|
|
354
|
+
# @return [ProtocolResponse]
|
|
355
355
|
# @raise [ArgumentError] when neither txids nor body is provided
|
|
356
356
|
def call_get_tx_status(txids = nil, body: nil)
|
|
357
357
|
raise ArgumentError, 'provide txids array or body: keyword' if txids.nil? && body.nil?
|
|
@@ -368,13 +368,13 @@ module BSV
|
|
|
368
368
|
#
|
|
369
369
|
# @param root [String] expected merkle root as hex
|
|
370
370
|
# @param height [Integer] block height
|
|
371
|
-
# @return [
|
|
371
|
+
# @return [ProtocolResponse<Boolean>]
|
|
372
372
|
def call_valid_root(root, height)
|
|
373
373
|
result = default_call(:valid_root, height)
|
|
374
|
-
return result unless result.
|
|
374
|
+
return result unless result.http_success?
|
|
375
375
|
|
|
376
376
|
actual = result.data['merkleroot']
|
|
377
|
-
|
|
377
|
+
result.with(data: actual.to_s.downcase == root.to_s.downcase)
|
|
378
378
|
end
|
|
379
379
|
end
|
|
380
380
|
end
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
5
|
# Protocols is a namespace module for concrete Protocol subclasses.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# the Protocol base class) will be autoloaded here in Phase B.
|
|
6
|
+
# Each class maps a service's commands to HTTP endpoints: ARC, Chaintracks,
|
|
7
|
+
# JungleBus, Ordinals, TAALBinary, and WoCREST.
|
|
9
8
|
module Protocols
|
|
10
9
|
autoload :ARC, 'bsv/network/protocols/arc'
|
|
11
10
|
autoload :Chaintracks, 'bsv/network/protocols/chaintracks'
|
data/lib/bsv/network/provider.rb
CHANGED
|
@@ -20,7 +20,7 @@ module BSV
|
|
|
20
20
|
# end
|
|
21
21
|
#
|
|
22
22
|
# result = gorillapool.call(:broadcast, tx)
|
|
23
|
-
# result.
|
|
23
|
+
# result.http_success? # => true
|
|
24
24
|
class Provider
|
|
25
25
|
attr_reader :name, :auth, :rate_limit
|
|
26
26
|
|
|
@@ -124,7 +124,7 @@ module BSV
|
|
|
124
124
|
# @param command_name [Symbol, String] command to invoke
|
|
125
125
|
# @param args [Array] positional arguments forwarded to the protocol
|
|
126
126
|
# @param kwargs [Hash] keyword arguments forwarded to the protocol
|
|
127
|
-
# @return [
|
|
127
|
+
# @return [ProtocolResponse]
|
|
128
128
|
# @raise [ArgumentError] when no registered protocol serves the command
|
|
129
129
|
def call(command_name, *args, **kwargs)
|
|
130
130
|
sym = command_name.to_sym
|
data/lib/bsv/network.rb
CHANGED
|
@@ -2,16 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
|
-
autoload :
|
|
5
|
+
autoload :ProtocolResponse, 'bsv/network/protocol_response'
|
|
6
6
|
autoload :Protocol, 'bsv/network/protocol'
|
|
7
7
|
autoload :Protocols, 'bsv/network/protocols'
|
|
8
8
|
autoload :Providers, 'bsv/network/providers'
|
|
9
9
|
autoload :Provider, 'bsv/network/provider'
|
|
10
|
-
autoload :BroadcastError, 'bsv/network/broadcast_error'
|
|
11
|
-
autoload :BroadcastResponse, 'bsv/network/broadcast_response'
|
|
12
|
-
autoload :ChainProviderError, 'bsv/network/chain_provider_error'
|
|
13
10
|
autoload :UTXO, 'bsv/network/utxo'
|
|
14
|
-
autoload :ARC, 'bsv/network/arc'
|
|
15
|
-
autoload :WhatsOnChain, 'bsv/network/whats_on_chain'
|
|
16
11
|
end
|
|
17
12
|
end
|
|
@@ -2,22 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Transaction
|
|
5
|
-
#
|
|
5
|
+
# Duck type for block header lookups used by the SDK's verify methods.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# {Beef#verify}, {MerklePath#verify}, and {Transaction#verify} define
|
|
8
|
+
# what a valid structure *is* — they walk trees, check proofs, and
|
|
9
|
+
# compare roots. But they have no data source of their own. The
|
|
10
|
+
# chain tracker is the data source: an object the consumer provides
|
|
11
|
+
# that can answer "is this merkle root valid for this block height?"
|
|
10
12
|
#
|
|
11
|
-
#
|
|
13
|
+
# The SDK is deliberately unopinionated about where that answer comes
|
|
14
|
+
# from. A chain tracker backed by an in-memory hash is declarative.
|
|
15
|
+
# One that fetches from the network on cache miss and writes to a
|
|
16
|
+
# database is imperative. The verify methods don't care — they just
|
|
17
|
+
# ask the question. All imperative behaviour (fetching, caching,
|
|
18
|
+
# persisting) lives in the consumer's chain tracker implementation,
|
|
19
|
+
# not in the SDK.
|
|
12
20
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# end
|
|
21
|
+
# Any object responding to +valid_root_for_height?+ and
|
|
22
|
+
# +current_height+ satisfies this interface. Inheriting from this
|
|
23
|
+
# class is optional — it exists to document the contract and provide
|
|
24
|
+
# clear error messages when methods are missing.
|
|
18
25
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
26
|
+
# @example In-memory chain tracker (test / declarative)
|
|
27
|
+
# class HashTracker < BSV::Transaction::ChainTracker
|
|
28
|
+
# def initialize(headers) @headers = headers end
|
|
29
|
+
# def valid_root_for_height?(root, h) @headers[h] == root end
|
|
30
|
+
# def current_height() @headers.keys.max end
|
|
31
|
+
# end
|
|
32
|
+
# tracker = HashTracker.new(800_000 => 'abcd...')
|
|
33
|
+
#
|
|
34
|
+
# @example Cache-aware chain tracker (production / imperative)
|
|
35
|
+
# class WalletChainTracker < BSV::Transaction::ChainTracker
|
|
36
|
+
# def valid_root_for_height?(root, height)
|
|
37
|
+
# header = @db.find_header(height) || fetch_and_store(height)
|
|
38
|
+
# header.merkle_root == root
|
|
21
39
|
# end
|
|
22
40
|
# end
|
|
23
41
|
class ChainTracker
|
|
@@ -54,17 +54,12 @@ module BSV
|
|
|
54
54
|
# @param root [String] merkle root as a hex string
|
|
55
55
|
# @param height [Integer] block height
|
|
56
56
|
# @return [Boolean]
|
|
57
|
-
# @raise [
|
|
57
|
+
# @raise [StandardError] on network or API error
|
|
58
58
|
def valid_root_for_height?(root, height)
|
|
59
59
|
result = @protocol.call(:get_block_header, height)
|
|
60
|
-
return false if result.
|
|
60
|
+
return false if result.http_not_found?
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
raise BSV::Network::ChainProviderError.new(
|
|
64
|
-
result.message.to_s,
|
|
65
|
-
status_code: result.metadata[:status_code]
|
|
66
|
-
)
|
|
67
|
-
end
|
|
62
|
+
raise result.message.to_s unless result.http_success?
|
|
68
63
|
|
|
69
64
|
merkle_root = result.data['merkleRoot']
|
|
70
65
|
return false unless merkle_root
|
|
@@ -75,15 +70,12 @@ module BSV
|
|
|
75
70
|
# Return the current blockchain height.
|
|
76
71
|
#
|
|
77
72
|
# @return [Integer]
|
|
78
|
-
# @raise [
|
|
73
|
+
# @raise [StandardError] on network or API error
|
|
79
74
|
def current_height
|
|
80
75
|
result = @protocol.call(:current_height)
|
|
81
|
-
return result.data if result.
|
|
76
|
+
return result.data if result.http_success?
|
|
82
77
|
|
|
83
|
-
raise
|
|
84
|
-
result.message.to_s,
|
|
85
|
-
status_code: result.metadata[:status_code]
|
|
86
|
-
)
|
|
78
|
+
raise result.message.to_s
|
|
87
79
|
end
|
|
88
80
|
end
|
|
89
81
|
end
|
|
@@ -47,17 +47,12 @@ module BSV
|
|
|
47
47
|
# @param root [String] merkle root as a hex string
|
|
48
48
|
# @param height [Integer] block height
|
|
49
49
|
# @return [Boolean]
|
|
50
|
-
# @raise [
|
|
50
|
+
# @raise [StandardError] on network or API error
|
|
51
51
|
def valid_root_for_height?(root, height)
|
|
52
52
|
result = @protocol.call(:valid_root, root, height)
|
|
53
|
-
return false if result.
|
|
53
|
+
return false if result.http_not_found?
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
raise BSV::Network::ChainProviderError.new(
|
|
57
|
-
result.message.to_s,
|
|
58
|
-
status_code: result.metadata[:status_code]
|
|
59
|
-
)
|
|
60
|
-
end
|
|
55
|
+
raise result.message.to_s unless result.http_success?
|
|
61
56
|
|
|
62
57
|
result.data == true
|
|
63
58
|
end
|
|
@@ -65,15 +60,12 @@ module BSV
|
|
|
65
60
|
# Return the current blockchain height.
|
|
66
61
|
#
|
|
67
62
|
# @return [Integer]
|
|
68
|
-
# @raise [
|
|
63
|
+
# @raise [StandardError] on network or API error
|
|
69
64
|
def current_height
|
|
70
65
|
result = @protocol.call(:current_height)
|
|
71
|
-
return result.data if result.
|
|
66
|
+
return result.data if result.http_success?
|
|
72
67
|
|
|
73
|
-
raise
|
|
74
|
-
result.message.to_s,
|
|
75
|
-
status_code: result.metadata[:status_code]
|
|
76
|
-
)
|
|
68
|
+
raise result.message.to_s
|
|
77
69
|
end
|
|
78
70
|
|
|
79
71
|
# Wraps an injectable HTTP client to set a raw Authorization header value
|
data/lib/bsv/version.rb
CHANGED
|
@@ -8,15 +8,51 @@ module BSV
|
|
|
8
8
|
module Wallet
|
|
9
9
|
# Minimal cryptographic wallet implementing the BRC-100 interface.
|
|
10
10
|
#
|
|
11
|
-
# ProtoWallet
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
11
|
+
# ProtoWallet is a direct, in-process implementation of the BRC-100 crypto
|
|
12
|
+
# operations. It requires no external gem, no storage, and no blockchain
|
|
13
|
+
# access — making it suitable for use inside the SDK itself (e.g. the Auth
|
|
14
|
+
# module) as well as lightweight applications that need cryptographic
|
|
15
|
+
# operations without full wallet infrastructure.
|
|
16
16
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
17
|
+
# == Supported BRC-100 areas
|
|
18
|
+
#
|
|
19
|
+
# - *Public key management* — {#get_public_key}, {#reveal_counterparty_key_linkage},
|
|
20
|
+
# {#reveal_specific_key_linkage}
|
|
21
|
+
# - *Cryptography* — {#encrypt}, {#decrypt}, {#create_hmac}, {#verify_hmac},
|
|
22
|
+
# {#create_signature}, {#verify_signature}
|
|
23
|
+
# - *Certificates (read-only stub)* — {#list_certificates} returns an empty list;
|
|
24
|
+
# {#prove_certificate} raises {UnsupportedActionError}
|
|
25
|
+
#
|
|
26
|
+
# == NOT supported
|
|
27
|
+
#
|
|
28
|
+
# The following BRC-100 areas are not implemented and will raise
|
|
29
|
+
# +NotImplementedError+:
|
|
30
|
+
#
|
|
31
|
+
# - Transactions (+create_action+, +sign_action+, +list_actions+, etc.)
|
|
32
|
+
# - Output management (+list_outputs+, +relinquish_output+, etc.)
|
|
33
|
+
# - Authentication (+authenticated?+, +wait_for_authentication+)
|
|
34
|
+
# - Blockchain / network data (+get_height+, +get_header_for_height+, etc.)
|
|
35
|
+
# - Certificate acquisition / discovery (+acquire_certificate+,
|
|
36
|
+
# +discover_by_identity_key+, etc.)
|
|
37
|
+
#
|
|
38
|
+
# For full wallet functionality, use the +bsv-wallet+ gem. See
|
|
39
|
+
# +docs/sdk/wallet.md+ for usage guidance.
|
|
40
|
+
#
|
|
41
|
+
# == Construction
|
|
42
|
+
#
|
|
43
|
+
# Pass a {BSV::Primitives::PrivateKey} or the special string <tt>'anyone'</tt>.
|
|
44
|
+
# The <tt>'anyone'</tt> variant uses a well-known key (private key = 1) and is
|
|
45
|
+
# suitable for verifying or encrypting data that should be readable by
|
|
46
|
+
# any party — it must not be used where secrecy is required.
|
|
47
|
+
#
|
|
48
|
+
# @example Normal usage
|
|
49
|
+
# wallet = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
|
|
50
|
+
# sig = wallet.create_signature(protocol_id: [1, 'my-app'], key_id: 'msg-1',
|
|
51
|
+
# data: 'hello'.bytes)
|
|
52
|
+
#
|
|
53
|
+
# @example Anyone wallet
|
|
54
|
+
# wallet = BSV::Wallet::ProtoWallet.new('anyone')
|
|
55
|
+
# pub = wallet.get_public_key(identity_key: true)
|
|
20
56
|
#
|
|
21
57
|
class ProtoWallet
|
|
22
58
|
include Interface::BRC100
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bsv-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Bettison
|
|
@@ -96,11 +96,8 @@ files:
|
|
|
96
96
|
- lib/bsv/mcp/tools/generate_key.rb
|
|
97
97
|
- lib/bsv/mcp/tools/helpers.rb
|
|
98
98
|
- lib/bsv/network.rb
|
|
99
|
-
- lib/bsv/network/arc.rb
|
|
100
|
-
- lib/bsv/network/broadcast_error.rb
|
|
101
|
-
- lib/bsv/network/broadcast_response.rb
|
|
102
|
-
- lib/bsv/network/chain_provider_error.rb
|
|
103
99
|
- lib/bsv/network/protocol.rb
|
|
100
|
+
- lib/bsv/network/protocol_response.rb
|
|
104
101
|
- lib/bsv/network/protocols.rb
|
|
105
102
|
- lib/bsv/network/protocols/arc.rb
|
|
106
103
|
- lib/bsv/network/protocols/chaintracks.rb
|
|
@@ -113,9 +110,7 @@ files:
|
|
|
113
110
|
- lib/bsv/network/providers/gorilla_pool.rb
|
|
114
111
|
- lib/bsv/network/providers/taal.rb
|
|
115
112
|
- lib/bsv/network/providers/whats_on_chain.rb
|
|
116
|
-
- lib/bsv/network/result.rb
|
|
117
113
|
- lib/bsv/network/utxo.rb
|
|
118
|
-
- lib/bsv/network/whats_on_chain.rb
|
|
119
114
|
- lib/bsv/overlay.rb
|
|
120
115
|
- lib/bsv/overlay/admin_token_template.rb
|
|
121
116
|
- lib/bsv/overlay/broadcast_facilitator.rb
|
data/lib/bsv/network/arc.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BSV
|
|
4
|
-
module Network
|
|
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.
|
|
9
|
-
class ARC
|
|
10
|
-
# Raised when deprecated facade classes are instantiated.
|
|
11
|
-
class DeprecationError < StandardError; end
|
|
12
|
-
|
|
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.'
|
|
16
|
-
|
|
17
|
-
def self.default(**)
|
|
18
|
-
raise DeprecationError, MESSAGE
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def initialize(*)
|
|
22
|
-
raise DeprecationError, MESSAGE
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BSV
|
|
4
|
-
module Network
|
|
5
|
-
class BroadcastError < StandardError
|
|
6
|
-
# ARC API boundary: display-order hex txid as returned by the ARC error response.
|
|
7
|
-
attr_reader :status_code, :txid, :arc_status
|
|
8
|
-
|
|
9
|
-
def initialize(message, status_code: nil, txid: nil, arc_status: nil)
|
|
10
|
-
@status_code = status_code
|
|
11
|
-
BSV::Primitives::Hex.validate_dtxid_hex!(txid, name: 'ARC error txid') if txid
|
|
12
|
-
@txid = txid
|
|
13
|
-
@arc_status = arc_status
|
|
14
|
-
super(message)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BSV
|
|
4
|
-
module Network
|
|
5
|
-
class BroadcastResponse
|
|
6
|
-
# ARC API boundary: display-order hex txid as returned by the ARC broadcast endpoint.
|
|
7
|
-
attr_reader :txid, :tx_status, :message, :extra_info, :block_hash, :block_height, :timestamp, :competing_txs
|
|
8
|
-
|
|
9
|
-
def initialize(attrs = {})
|
|
10
|
-
txid = attrs[:txid]
|
|
11
|
-
BSV::Primitives::Hex.validate_dtxid_hex!(txid, name: 'ARC broadcast txid') if txid
|
|
12
|
-
@txid = txid
|
|
13
|
-
@tx_status = attrs[:tx_status]
|
|
14
|
-
@message = attrs[:message]
|
|
15
|
-
@extra_info = attrs[:extra_info]
|
|
16
|
-
@block_hash = attrs[:block_hash]
|
|
17
|
-
@block_height = attrs[:block_height]
|
|
18
|
-
@timestamp = attrs[:timestamp]
|
|
19
|
-
@competing_txs = attrs[:competing_txs]
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def success?
|
|
23
|
-
true
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def mined?
|
|
27
|
-
tx_status == 'MINED'
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|