bsv-sdk 0.17.0 → 0.18.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 +28 -0
- data/lib/bsv/auth/get_verifiable_certificates.rb +6 -6
- data/lib/bsv/auth/peer.rb +10 -4
- data/lib/bsv/auth/session_manager.rb +81 -5
- data/lib/bsv/identity/client.rb +4 -2
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +2 -2
- data/lib/bsv/mcp/tools/check_balance.rb +2 -2
- data/lib/bsv/mcp/tools/fetch_utxos.rb +2 -2
- data/lib/bsv/network/broadcast_error.rb +1 -0
- data/lib/bsv/network/broadcast_response.rb +3 -1
- data/lib/bsv/network/protocol.rb +56 -4
- data/lib/bsv/network/protocols/arc.rb +6 -3
- data/lib/bsv/network/protocols/chaintracks.rb +6 -2
- data/lib/bsv/network/protocols/jungle_bus.rb +52 -0
- data/lib/bsv/network/protocols/ordinals.rb +110 -8
- data/lib/bsv/network/protocols/taal_binary.rb +17 -4
- data/lib/bsv/network/protocols/woc_rest.rb +164 -84
- data/lib/bsv/network/protocols.rb +1 -0
- data/lib/bsv/network/provider.rb +36 -5
- data/lib/bsv/network/providers/gorilla_pool.rb +42 -20
- data/lib/bsv/network/providers/taal.rb +38 -15
- data/lib/bsv/network/providers/whats_on_chain.rb +42 -21
- data/lib/bsv/network/utxo.rb +8 -2
- data/lib/bsv/overlay/lookup_resolver.rb +5 -5
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
- data/lib/bsv/overlay/types.rb +1 -0
- data/lib/bsv/registry/client.rb +8 -8
- data/lib/bsv/registry/types.rb +1 -0
- data/lib/bsv/transaction/beef.rb +139 -102
- data/lib/bsv/transaction/transaction.rb +31 -19
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wire_format.rb +40 -14
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bb4b97822f38d793a308e9beddc94bd90e64457bda8f1782875ddcd8a1ff1f3
|
|
4
|
+
data.tar.gz: 85009a67a04ee1264acd883224ea418ed46558629a18db3d1757486cc544852d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85e2958f6bb6af774b2ca06e5187bbc1c756546e322430375108617b4675e6e5d5373aa8d93fd4504327d3c48e29507a2caea566d0b9af5e16c3fdd089bc2fbc
|
|
7
|
+
data.tar.gz: 56797e445e8996c2a41e85484e49f53b3568eaebeaf01de39a1e919c6949f40b172236ad5bd89fbc77eb9405a532653c9ab1972e696e699d6410e503318360d7
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to the `bsv-sdk` gem are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
6
|
and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 0.18.0 — 2026-05-09
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
- Eliminated display-order binary txid from public API — all methods now use `wtxid` (wire-order) internally (#690)
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Provider auth and rate limit metadata: `auth:`, `rate_limit:`, `authenticated?` on Provider; generic auth dispatch in Protocol `build_request` (#718)
|
|
15
|
+
- JungleBus protocol: transaction lookup, address queries, block headers via GorillaPool JungleBus REST API (#717)
|
|
16
|
+
- Ordinals protocol: fixed endpoint paths, added tx details, tx status, UTXOs, balance, spends, chain tip (#727)
|
|
17
|
+
- Full WoC API coverage: expanded from 30 to 54 endpoints with complete address, script, UTXO, and analytics support (#715)
|
|
18
|
+
- Integration tests for ARC, JungleBus, Ordinals, and WoCREST against live APIs (#726)
|
|
19
|
+
- API documentation URLs in all protocol file headers (#730)
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Ordinals `get_tx` path corrected (`/hex` → `/raw`) and response handler fixed (binary, not JSON) (#727)
|
|
23
|
+
- Auth normalisation: `{ bearer: nil }` and `{ api_key: nil }` treated as unauthenticated (#725)
|
|
24
|
+
- WoC health endpoint path corrected (`/health` → `/woc`)
|
|
25
|
+
- Bulk POST body formats and `get_utxos_all` path corrected for WoC
|
|
26
|
+
- Lazy removal of expired sessions during identity-key lookup (#707)
|
|
27
|
+
- Session TTL expiration added to SessionManager (#707)
|
|
28
|
+
- Guard against nil source data and out-of-bounds input_index in sighash (#702)
|
|
29
|
+
- Wire format deep conversion depth limit (#709)
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- BeefTx refactored to polymorphic subclass hierarchy with validated constructors (#692-698)
|
|
33
|
+
- WoCREST `build_request` override removed — replaced by generic auth dispatch
|
|
34
|
+
- MCP dependency bumped from ~> 0.12 to ~> 0.15 (#700)
|
|
35
|
+
|
|
8
36
|
## 0.17.0 — 2026-05-02
|
|
9
37
|
|
|
10
38
|
### Breaking Changes
|
|
@@ -21,7 +21,9 @@ module BSV
|
|
|
21
21
|
# - +:types+ [Hash] type (Base64 string) → array of field names to reveal
|
|
22
22
|
# @param verifier_identity_key [String] the verifier's compressed public key hex
|
|
23
23
|
# @return [Array<VerifiableCertificate>] list of verifiable certificates ready for
|
|
24
|
-
# presentation, or +[]+
|
|
24
|
+
# presentation, or +[]+ when the wallet does not support certificate operations
|
|
25
|
+
# @raise [StandardError] propagates unexpected errors (network failures, key derivation
|
|
26
|
+
# errors, etc.) to the caller — only +UnsupportedActionError+ is swallowed
|
|
25
27
|
def get_verifiable_certificates(wallet, requested_certificates, verifier_identity_key)
|
|
26
28
|
return [] unless wallet.respond_to?(:list_certificates) && wallet.respond_to?(:prove_certificate)
|
|
27
29
|
|
|
@@ -65,11 +67,9 @@ module BSV
|
|
|
65
67
|
signature: cert[:signature] || cert['signature']
|
|
66
68
|
)
|
|
67
69
|
end
|
|
68
|
-
rescue
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# "no certificates" gracefully — the requesting peer enforces its own
|
|
72
|
-
# certificate requirements independently.
|
|
70
|
+
rescue BSV::Wallet::UnsupportedActionError
|
|
71
|
+
# Wallet does not implement certificate operations (e.g. ProtoWallet).
|
|
72
|
+
# Return empty — the requesting peer enforces its own requirements independently.
|
|
73
73
|
[]
|
|
74
74
|
end
|
|
75
75
|
end
|
data/lib/bsv/auth/peer.rb
CHANGED
|
@@ -488,7 +488,7 @@ module BSV
|
|
|
488
488
|
signature = fetch!(message, :signature)
|
|
489
489
|
|
|
490
490
|
# Verify the echoed nonce is one we created
|
|
491
|
-
|
|
491
|
+
verify_nonce!(our_nonce, context: "initial response from peer: #{peer_key}")
|
|
492
492
|
|
|
493
493
|
session = @session_manager.get_session(our_nonce)
|
|
494
494
|
raise AuthError, "No pending session for nonce: #{our_nonce.inspect}" unless session
|
|
@@ -566,7 +566,7 @@ module BSV
|
|
|
566
566
|
msg_nonce = fetch!(message, :nonce)
|
|
567
567
|
|
|
568
568
|
# Verify the echoed nonce is one we created
|
|
569
|
-
|
|
569
|
+
verify_nonce!(our_nonce, context: "general message from: #{peer_key}")
|
|
570
570
|
|
|
571
571
|
session = @session_manager.get_session(our_nonce)
|
|
572
572
|
raise AuthError, "Session not found for nonce: #{our_nonce.inspect}" unless session
|
|
@@ -602,7 +602,7 @@ module BSV
|
|
|
602
602
|
requested = message[:requested_certificates] || message['requested_certificates']
|
|
603
603
|
signature = fetch!(message, :signature)
|
|
604
604
|
|
|
605
|
-
|
|
605
|
+
verify_nonce!(our_nonce, context: "certificate request from: #{peer_key}")
|
|
606
606
|
|
|
607
607
|
session = @session_manager.get_session(our_nonce)
|
|
608
608
|
raise AuthError, "Session not found for nonce: #{our_nonce.inspect}" unless session
|
|
@@ -647,7 +647,7 @@ module BSV
|
|
|
647
647
|
certs = message[:certificates] || message['certificates'] || []
|
|
648
648
|
signature = fetch!(message, :signature)
|
|
649
649
|
|
|
650
|
-
|
|
650
|
+
verify_nonce!(our_nonce, context: "certificate response from: #{peer_key}")
|
|
651
651
|
|
|
652
652
|
session = @session_manager.get_session(our_nonce)
|
|
653
653
|
raise AuthError, "Session not found for nonce: #{our_nonce.inspect}" unless session
|
|
@@ -773,6 +773,12 @@ module BSV
|
|
|
773
773
|
certs.map { |c| c.respond_to?(:to_h) ? c.to_h : c }
|
|
774
774
|
end
|
|
775
775
|
|
|
776
|
+
def verify_nonce!(nonce, context:)
|
|
777
|
+
return if Nonce.verify(nonce, @wallet)
|
|
778
|
+
|
|
779
|
+
raise AuthError, "Nonce verification failed for #{context}"
|
|
780
|
+
end
|
|
781
|
+
|
|
776
782
|
def current_time_ms
|
|
777
783
|
(Time.now.to_f * 1000).to_i
|
|
778
784
|
end
|
|
@@ -9,9 +9,16 @@ module BSV
|
|
|
9
9
|
# peer identity key are supported — the most recently updated session
|
|
10
10
|
# is returned when looking up by identity key.
|
|
11
11
|
#
|
|
12
|
+
# Sessions expire after +default_ttl+ seconds (default: 3600). Pass
|
|
13
|
+
# +default_ttl: nil+ to disable expiry entirely. Expired sessions are
|
|
14
|
+
# removed lazily on access; call {#sweep_expired} for proactive cleanup.
|
|
15
|
+
#
|
|
12
16
|
# Matches the ts-sdk SessionManager dual-index design.
|
|
13
17
|
class SessionManager
|
|
14
|
-
|
|
18
|
+
# @param default_ttl [Integer, nil] seconds before a session expires;
|
|
19
|
+
# +nil+ disables TTL entirely.
|
|
20
|
+
def initialize(default_ttl: 3600)
|
|
21
|
+
@default_ttl = default_ttl
|
|
15
22
|
# session_nonce -> PeerSession
|
|
16
23
|
@by_nonce = {}
|
|
17
24
|
# peer_identity_key -> Set of session_nonces
|
|
@@ -50,25 +57,40 @@ module BSV
|
|
|
50
57
|
#
|
|
51
58
|
# When the identifier is a session nonce, returns that exact session.
|
|
52
59
|
# When the identifier is a peer identity key, returns the most recently
|
|
53
|
-
# updated session for that peer.
|
|
60
|
+
# updated non-expired session for that peer.
|
|
61
|
+
#
|
|
62
|
+
# Returns +nil+ if the session has expired (and removes it from the store).
|
|
54
63
|
#
|
|
55
64
|
# @param identifier [String]
|
|
56
65
|
# @return [PeerSession, nil]
|
|
57
66
|
def get_session(identifier)
|
|
58
67
|
@mutex.synchronize do
|
|
59
68
|
direct = @by_nonce[identifier]
|
|
60
|
-
|
|
69
|
+
if direct
|
|
70
|
+
if expired_locked?(direct)
|
|
71
|
+
remove_session_locked(direct)
|
|
72
|
+
return nil
|
|
73
|
+
end
|
|
74
|
+
return direct
|
|
75
|
+
end
|
|
61
76
|
|
|
62
77
|
nonces = @by_identity[identifier]
|
|
63
78
|
return nil if nonces.nil? || nonces.empty?
|
|
64
79
|
|
|
65
80
|
best = nil
|
|
81
|
+
expired_nonces = []
|
|
66
82
|
nonces.each do |nonce|
|
|
67
83
|
s = @by_nonce[nonce]
|
|
68
84
|
next if s.nil?
|
|
69
85
|
|
|
86
|
+
if expired_locked?(s)
|
|
87
|
+
expired_nonces << s
|
|
88
|
+
next
|
|
89
|
+
end
|
|
90
|
+
|
|
70
91
|
best = s if best.nil? || (s.last_update || 0) > (best.last_update || 0)
|
|
71
92
|
end
|
|
93
|
+
expired_nonces.each { |s| remove_session_locked(s) }
|
|
72
94
|
best
|
|
73
95
|
end
|
|
74
96
|
end
|
|
@@ -84,13 +106,53 @@ module BSV
|
|
|
84
106
|
# @return [Boolean]
|
|
85
107
|
def session?(identifier)
|
|
86
108
|
@mutex.synchronize do
|
|
87
|
-
|
|
109
|
+
direct = @by_nonce[identifier]
|
|
110
|
+
if direct
|
|
111
|
+
if expired_locked?(direct)
|
|
112
|
+
remove_session_locked(direct)
|
|
113
|
+
return false
|
|
114
|
+
end
|
|
115
|
+
return true
|
|
116
|
+
end
|
|
88
117
|
|
|
89
118
|
nonces = @by_identity[identifier]
|
|
90
|
-
|
|
119
|
+
return false if nonces.nil? || nonces.empty?
|
|
120
|
+
|
|
121
|
+
has_active = false
|
|
122
|
+
expired_sessions = []
|
|
123
|
+
nonces.each do |nonce|
|
|
124
|
+
s = @by_nonce[nonce]
|
|
125
|
+
next if s.nil?
|
|
126
|
+
|
|
127
|
+
if expired_locked?(s)
|
|
128
|
+
expired_sessions << s
|
|
129
|
+
else
|
|
130
|
+
has_active = true
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
expired_sessions.each { |s| remove_session_locked(s) }
|
|
134
|
+
has_active
|
|
91
135
|
end
|
|
92
136
|
end
|
|
93
137
|
|
|
138
|
+
# Removes all expired sessions from the store.
|
|
139
|
+
#
|
|
140
|
+
# Not called automatically — intended for use in long-running servers
|
|
141
|
+
# that want to proactively reclaim memory.
|
|
142
|
+
#
|
|
143
|
+
# @return [Integer] number of sessions removed
|
|
144
|
+
def sweep_expired
|
|
145
|
+
removed = 0
|
|
146
|
+
@mutex.synchronize do
|
|
147
|
+
expired = @by_nonce.values.select { |s| expired_locked?(s) }
|
|
148
|
+
expired.each do |s|
|
|
149
|
+
remove_session_locked(s)
|
|
150
|
+
removed += 1
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
removed
|
|
154
|
+
end
|
|
155
|
+
|
|
94
156
|
private
|
|
95
157
|
|
|
96
158
|
def add_session_locked(session)
|
|
@@ -115,6 +177,20 @@ module BSV
|
|
|
115
177
|
nonces.delete(session.session_nonce)
|
|
116
178
|
@by_identity.delete(session.peer_identity_key) if nonces.empty?
|
|
117
179
|
end
|
|
180
|
+
|
|
181
|
+
# Must be called within @mutex.
|
|
182
|
+
def expired_locked?(session)
|
|
183
|
+
return false if @default_ttl.nil?
|
|
184
|
+
|
|
185
|
+
last = session.last_update
|
|
186
|
+
return true if last.nil? || last.zero?
|
|
187
|
+
|
|
188
|
+
current_time_ms - last > @default_ttl * 1000
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def current_time_ms
|
|
192
|
+
(Time.now.to_f * 1000).to_i
|
|
193
|
+
end
|
|
118
194
|
end
|
|
119
195
|
end
|
|
120
196
|
end
|
data/lib/bsv/identity/client.rb
CHANGED
|
@@ -203,8 +203,10 @@ module BSV
|
|
|
203
203
|
raise 'Revoke failed: invalid outputIndex from overlay' if output_idx.negative?
|
|
204
204
|
|
|
205
205
|
beef = BSV::Transaction::Beef.from_binary(beef_bytes)
|
|
206
|
-
|
|
207
|
-
raise 'Revoke failed: no transaction found in BEEF'
|
|
206
|
+
beef_tx = beef.transactions.last
|
|
207
|
+
raise 'Revoke failed: no transaction found in BEEF' if beef_tx.nil? || beef_tx.is_a?(BSV::Transaction::Beef::TxidOnlyEntry)
|
|
208
|
+
|
|
209
|
+
tx = beef_tx.transaction
|
|
208
210
|
raise 'Revoke failed: outputIndex out of range' if output_idx >= tx.outputs.length
|
|
209
211
|
|
|
210
212
|
# Overlay API boundary: outpoint uses display-order hex txid as per overlay convention
|
|
@@ -97,8 +97,8 @@ module BSV
|
|
|
97
97
|
|
|
98
98
|
all_utxos = utxo_result.data.map do |entry|
|
|
99
99
|
BSV::Network::UTXO.new(
|
|
100
|
-
tx_hash: entry[
|
|
101
|
-
|
|
100
|
+
tx_hash: entry['tx_hash'], tx_pos: entry['tx_pos'],
|
|
101
|
+
value: entry['value'], height: entry['height']
|
|
102
102
|
)
|
|
103
103
|
end
|
|
104
104
|
|
|
@@ -71,8 +71,8 @@ module BSV
|
|
|
71
71
|
|
|
72
72
|
utxos = utxo_result.data.map do |entry|
|
|
73
73
|
BSV::Network::UTXO.new(
|
|
74
|
-
tx_hash: entry[
|
|
75
|
-
|
|
74
|
+
tx_hash: entry['tx_hash'], tx_pos: entry['tx_pos'],
|
|
75
|
+
value: entry['value'], height: entry['height']
|
|
76
76
|
)
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -62,8 +62,8 @@ module BSV
|
|
|
62
62
|
|
|
63
63
|
utxos = utxo_result.data.map do |entry|
|
|
64
64
|
BSV::Network::UTXO.new(
|
|
65
|
-
tx_hash: entry[
|
|
66
|
-
|
|
65
|
+
tx_hash: entry['tx_hash'], tx_pos: entry['tx_pos'],
|
|
66
|
+
value: entry['value'], height: entry['height']
|
|
67
67
|
)
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -7,7 +7,9 @@ module BSV
|
|
|
7
7
|
attr_reader :txid, :tx_status, :message, :extra_info, :block_hash, :block_height, :timestamp, :competing_txs
|
|
8
8
|
|
|
9
9
|
def initialize(attrs = {})
|
|
10
|
-
|
|
10
|
+
txid = attrs[:txid]
|
|
11
|
+
BSV::Primitives::Hex.validate_dtxid_hex!(txid, name: 'ARC broadcast txid') if txid
|
|
12
|
+
@txid = txid
|
|
11
13
|
@tx_status = attrs[:tx_status]
|
|
12
14
|
@message = attrs[:message]
|
|
13
15
|
@extra_info = attrs[:extra_info]
|
data/lib/bsv/network/protocol.rb
CHANGED
|
@@ -103,14 +103,21 @@ module BSV
|
|
|
103
103
|
@endpoints = {}
|
|
104
104
|
@subscriptions = {}
|
|
105
105
|
|
|
106
|
-
attr_reader :base_url, :api_key, :network, :http_client
|
|
106
|
+
attr_reader :base_url, :api_key, :auth, :network, :http_client
|
|
107
107
|
|
|
108
108
|
# @param base_url [String] base URL, may contain +{network}+ placeholder
|
|
109
|
-
# @param api_key [String, nil] API key
|
|
109
|
+
# @param api_key [String, nil] legacy API key — sends +Authorization: Bearer <key>+
|
|
110
|
+
# @param auth [Hash, Symbol, nil] auth config hash; takes precedence over +api_key:+.
|
|
111
|
+
# Supported forms:
|
|
112
|
+
# - +{ bearer: 'token' }+ → +Authorization: Bearer token+
|
|
113
|
+
# - +{ api_key: 'key' }+ → +Authorization: key+ (no Bearer prefix, WoC style)
|
|
114
|
+
# - +{ api_key: 'key', header: 'X-Custom' }+ → +X-Custom: key+
|
|
115
|
+
# - +:none+ or +nil+ → no auth header
|
|
110
116
|
# @param network [String, Symbol, nil] network name (e.g. 'main', 'test')
|
|
111
117
|
# @param http_client [Object, nil] injectable HTTP client (used in Task 3)
|
|
112
|
-
def initialize(base_url:, api_key: nil, network: nil, http_client: nil)
|
|
118
|
+
def initialize(base_url:, api_key: nil, auth: nil, network: nil, http_client: nil)
|
|
113
119
|
@api_key = api_key
|
|
120
|
+
@auth = normalise_auth(auth)
|
|
114
121
|
@network = network
|
|
115
122
|
@http_client = http_client
|
|
116
123
|
@base_url = build_base_url(base_url, network)
|
|
@@ -228,6 +235,14 @@ module BSV
|
|
|
228
235
|
|
|
229
236
|
# Builds a Net::HTTP request for the given method, URI, and optional body.
|
|
230
237
|
#
|
|
238
|
+
# Auth header dispatch (in priority order):
|
|
239
|
+
# 1. +auth:+ config hash takes precedence over the legacy +api_key:+ shorthand.
|
|
240
|
+
# 2. +{ bearer: 'token' }+ → +Authorization: Bearer token+
|
|
241
|
+
# 3. +{ api_key: 'key', header: 'X-Custom' }+ → +X-Custom: key+
|
|
242
|
+
# 4. +{ api_key: 'key' }+ → +Authorization: key+ (no Bearer prefix)
|
|
243
|
+
# 5. +auth: :none+ or no auth at all → no Authorization header set
|
|
244
|
+
# 6. Legacy +api_key:+ (no auth: provided) → +Authorization: Bearer api_key+
|
|
245
|
+
#
|
|
231
246
|
# @param http_method [Symbol] +:get+ or +:post+
|
|
232
247
|
# @param uri [URI]
|
|
233
248
|
# @param body [String, nil] raw body for POST requests
|
|
@@ -240,7 +255,8 @@ module BSV
|
|
|
240
255
|
else raise ArgumentError, "unsupported HTTP method: #{http_method}"
|
|
241
256
|
end
|
|
242
257
|
|
|
243
|
-
request
|
|
258
|
+
apply_auth(request)
|
|
259
|
+
|
|
244
260
|
if body && request.respond_to?(:body=)
|
|
245
261
|
request.body = body
|
|
246
262
|
request.content_type = 'application/json' unless request.content_type
|
|
@@ -248,6 +264,40 @@ module BSV
|
|
|
248
264
|
request
|
|
249
265
|
end
|
|
250
266
|
|
|
267
|
+
# Applies the auth header to the request based on the +auth:+ config or
|
|
268
|
+
# the legacy +api_key:+ shorthand.
|
|
269
|
+
#
|
|
270
|
+
# @param request [Net::HTTPRequest]
|
|
271
|
+
def apply_auth(request)
|
|
272
|
+
# auth: config takes precedence over legacy api_key:
|
|
273
|
+
if @auth != :none
|
|
274
|
+
auth = @auth
|
|
275
|
+
if auth[:bearer]
|
|
276
|
+
request['Authorization'] = "Bearer #{auth[:bearer]}"
|
|
277
|
+
elsif auth[:api_key]
|
|
278
|
+
header = auth[:header] || 'Authorization'
|
|
279
|
+
request[header] = auth[:api_key]
|
|
280
|
+
end
|
|
281
|
+
elsif @api_key
|
|
282
|
+
# Legacy shorthand: api_key: without auth: sends Bearer
|
|
283
|
+
request['Authorization'] = "Bearer #{@api_key}"
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Normalises the +auth+ argument so that +nil+ and empty hashes are
|
|
288
|
+
# stored as +:none+, giving a single canonical sentinel value for
|
|
289
|
+
# "no authentication".
|
|
290
|
+
#
|
|
291
|
+
# @param auth [Hash, Symbol, nil]
|
|
292
|
+
# @return [Hash, Symbol]
|
|
293
|
+
def normalise_auth(auth)
|
|
294
|
+
return :none if auth.nil?
|
|
295
|
+
return :none if auth == :none
|
|
296
|
+
return :none if auth.is_a?(Hash) && (auth.empty? || (auth[:bearer].nil? && auth[:api_key].nil?))
|
|
297
|
+
|
|
298
|
+
auth
|
|
299
|
+
end
|
|
300
|
+
|
|
251
301
|
# Executes the request via the injectable client or +Net::HTTP.start+.
|
|
252
302
|
#
|
|
253
303
|
# @param uri [URI]
|
|
@@ -305,6 +355,8 @@ module BSV
|
|
|
305
355
|
JSON.parse(body)
|
|
306
356
|
when :json_array
|
|
307
357
|
parsed = JSON.parse(body)
|
|
358
|
+
# Some providers (e.g. WoC) wrap arrays in { "result": [...] }
|
|
359
|
+
parsed = parsed['result'] if parsed.is_a?(Hash) && parsed.key?('result')
|
|
308
360
|
raise TypeError, "expected Array, got #{parsed.class}" unless parsed.is_a?(Array)
|
|
309
361
|
|
|
310
362
|
parsed
|
|
@@ -24,6 +24,8 @@ module BSV
|
|
|
24
24
|
# result = arc.call(:broadcast, tx)
|
|
25
25
|
# result.success? # => true
|
|
26
26
|
# result.data[:txid] # => "abc123..."
|
|
27
|
+
#
|
|
28
|
+
# @see https://docs.gorillapool.io/arc/api.html ARC API v1 documentation
|
|
27
29
|
class ARC < Protocol
|
|
28
30
|
# ARC response statuses that indicate a transaction was NOT accepted.
|
|
29
31
|
# Matches the TypeScript SDK's ARC broadcaster failure set.
|
|
@@ -45,16 +47,17 @@ module BSV
|
|
|
45
47
|
endpoint :health, :get, '/v1/health', response: :json
|
|
46
48
|
|
|
47
49
|
# @param base_url [String] ARC base URL (may contain {network})
|
|
48
|
-
# @param api_key [String, nil]
|
|
50
|
+
# @param api_key [String, nil] legacy bearer token shorthand — use +auth:+ for new code
|
|
51
|
+
# @param auth [Hash, Symbol, nil] auth config; takes precedence over +api_key:+
|
|
49
52
|
# @param network [String, nil] network name for base URL interpolation
|
|
50
53
|
# @param deployment_id [String, nil] deployment identifier for the
|
|
51
54
|
# XDeployment-ID header; defaults to a per-instance random hex value
|
|
52
55
|
# @param callback_url [String, nil] optional X-CallbackUrl header value
|
|
53
56
|
# @param callback_token [String, nil] optional X-CallbackToken header value
|
|
54
57
|
# @param http_client [#request, nil] injectable HTTP client for testing
|
|
55
|
-
def initialize(base_url:, api_key: nil, network: nil, deployment_id: nil,
|
|
58
|
+
def initialize(base_url:, api_key: nil, auth: nil, network: nil, deployment_id: nil,
|
|
56
59
|
callback_url: nil, callback_token: nil, http_client: nil)
|
|
57
|
-
super(base_url: base_url, api_key: api_key, network: network, http_client: http_client)
|
|
60
|
+
super(base_url: base_url, api_key: api_key, auth: auth, network: network, http_client: http_client)
|
|
58
61
|
@deployment_id = deployment_id || "bsv-ruby-sdk-#{SecureRandom.hex(8)}"
|
|
59
62
|
@callback_url = callback_url
|
|
60
63
|
@callback_token = callback_token
|
|
@@ -22,15 +22,19 @@ module BSV
|
|
|
22
22
|
#
|
|
23
23
|
# result = ct.call(:get_block_header, 800_000)
|
|
24
24
|
# result.data # => { 'hash' => '...', 'height' => 800000, 'merkleRoot' => '...' }
|
|
25
|
+
#
|
|
26
|
+
# @note Chaintracks is an internal GorillaPool service; no public API documentation
|
|
27
|
+
# is available.
|
|
25
28
|
class Chaintracks < Protocol
|
|
26
29
|
endpoint :get_block_header, :get, '/chaintracks/v2/header/height/{height}', response: :json
|
|
27
30
|
endpoint :current_height, :get, '/chaintracks/v2/tip',
|
|
28
31
|
response: ->(body) { JSON.parse(body)['height'] }
|
|
29
32
|
|
|
30
33
|
# @param base_url [String] base URL for the Chaintracks API
|
|
31
|
-
# @param api_key [String, nil]
|
|
34
|
+
# @param api_key [String, nil] legacy Bearer API key shorthand — use +auth:+ for new code
|
|
35
|
+
# @param auth [Hash, Symbol, nil] auth config; takes precedence over +api_key:+
|
|
32
36
|
# @param http_client [Object, nil] injectable HTTP client for testing
|
|
33
|
-
def initialize(base_url:, api_key: nil, http_client: nil)
|
|
37
|
+
def initialize(base_url:, api_key: nil, auth: nil, http_client: nil)
|
|
34
38
|
super
|
|
35
39
|
end
|
|
36
40
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Network
|
|
5
|
+
module Protocols
|
|
6
|
+
# JungleBus implements the GorillaPool JungleBus REST API as a Protocol
|
|
7
|
+
# subclass.
|
|
8
|
+
#
|
|
9
|
+
# JungleBus is a blockchain indexer that indexes all BSV transactions and
|
|
10
|
+
# provides parsed transaction data, address lookups, and block headers.
|
|
11
|
+
# The +get_tx+ endpoint returns both the full transaction (base64-encoded)
|
|
12
|
+
# and a TSC merkle proof in a single call.
|
|
13
|
+
#
|
|
14
|
+
# == Usage
|
|
15
|
+
#
|
|
16
|
+
# jb = BSV::Network::Protocols::JungleBus.new(
|
|
17
|
+
# base_url: 'https://junglebus.gorillapool.io'
|
|
18
|
+
# )
|
|
19
|
+
# result = jb.call(:get_tx, 'abc123...')
|
|
20
|
+
# result.data['transaction'] # => base64-encoded raw tx
|
|
21
|
+
# result.data['merkle_proof'] # => base64-encoded TSC proof (or nil)
|
|
22
|
+
#
|
|
23
|
+
# @see https://junglebus.gorillapool.io/docs/ JungleBus API documentation
|
|
24
|
+
class JungleBus < Protocol
|
|
25
|
+
# Transaction — full tx with parsed metadata and merkle proof
|
|
26
|
+
endpoint :get_tx, :get, '/v1/transaction/get/{txid}', response: :json
|
|
27
|
+
|
|
28
|
+
# Address — transaction metadata for an address
|
|
29
|
+
endpoint :get_address_meta, :get, '/v1/address/get/{address}', response: :json_array
|
|
30
|
+
|
|
31
|
+
# Address — full transaction documents for an address.
|
|
32
|
+
# Note: this endpoint currently returns empty bodies for all addresses
|
|
33
|
+
# via the REST API. It may require a subscription to populate.
|
|
34
|
+
endpoint :get_address_txs, :get, '/v1/address/transactions/{address}', response: :json_array
|
|
35
|
+
|
|
36
|
+
# Block header by height or hash
|
|
37
|
+
endpoint :get_block_header, :get, '/v1/block_header/get/{height}', response: :json
|
|
38
|
+
|
|
39
|
+
# Block headers from a given height (supports ?limit=N, max 10000)
|
|
40
|
+
endpoint :get_block_headers, :get, '/v1/block_header/list/{height}', response: :json_array
|
|
41
|
+
|
|
42
|
+
# @param base_url [String] base URL for the JungleBus API
|
|
43
|
+
# @param api_key [String, nil] legacy API key shorthand — use +auth:+ for new code
|
|
44
|
+
# @param auth [Hash, Symbol, nil] auth config; takes precedence over +api_key:+
|
|
45
|
+
# @param http_client [Object, nil] injectable HTTP client for testing
|
|
46
|
+
def initialize(base_url:, api_key: nil, auth: nil, http_client: nil)
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|