bsv-sdk 0.19.1 → 0.22.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 +89 -0
- data/README.md +2 -2
- data/lib/bsv/auth/transport.rb +1 -1
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
- data/lib/bsv/network/protocol.rb +4 -5
- data/lib/bsv/network/protocols/arc.rb +4 -30
- data/lib/bsv/network/protocols/arcade.rb +163 -0
- data/lib/bsv/network/protocols/chaintracks.rb +6 -3
- data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
- data/lib/bsv/network/protocols.rb +1 -0
- data/lib/bsv/network/provider.rb +7 -9
- data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
- data/lib/bsv/network/util.rb +44 -0
- data/lib/bsv/network.rb +1 -0
- data/lib/bsv/overlay/lookup_resolver.rb +0 -1
- data/lib/bsv/overlay/topic_broadcaster.rb +0 -1
- data/lib/bsv/primitives/curve.rb +1 -11
- data/lib/bsv/primitives/ecies.rb +1 -8
- data/lib/bsv/primitives/hex.rb +1 -1
- data/lib/bsv/script/script.rb +1 -1
- data/lib/bsv/transaction/beef.rb +0 -2
- data/lib/bsv/transaction/chain_tracker.rb +74 -13
- data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +3 -3
- data/lib/bsv/transaction/chain_trackers.rb +0 -10
- data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
- data/lib/bsv/transaction/merkle_path.rb +0 -2
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +65 -21
- data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
- data/lib/bsv/wallet/proto_wallet.rb +15 -8
- data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
- data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
- data/lib/bsv/wallet/serializer/certificate.rb +184 -0
- data/lib/bsv/wallet/serializer/common.rb +207 -0
- data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
- data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
- data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
- data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
- data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
- data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
- data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
- data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
- data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
- data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
- data/lib/bsv/wallet/serializer/get_height.rb +46 -0
- data/lib/bsv/wallet/serializer/get_network.rb +65 -0
- data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
- data/lib/bsv/wallet/serializer/get_version.rb +44 -0
- data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
- data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
- data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
- data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
- data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
- data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
- data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
- data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
- data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
- data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
- data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
- data/lib/bsv/wallet/serializer/status.rb +85 -0
- data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
- data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
- data/lib/bsv/wallet/serializer.rb +180 -0
- data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
- data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
- data/lib/bsv/wallet/wallet_wire.rb +20 -0
- data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
- data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
- data/lib/bsv/wallet/wire/calls.rb +79 -0
- data/lib/bsv/wallet/wire/frame.rb +181 -0
- data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
- data/lib/bsv/wallet/wire/validation.rb +213 -0
- data/lib/bsv/wallet/wire.rb +13 -0
- data/lib/bsv/wallet.rb +17 -0
- metadata +47 -3
- data/lib/bsv/transaction/chain_trackers/chaintracks.rb +0 -83
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d666b0e5af3155dbe92f4298f5448a5792275848f6e3c08fcc1bbfa09d9a20bd
|
|
4
|
+
data.tar.gz: c4f3beba29031b049afe90a967b5d982ee473cea57ee077c09ba9c263385c067
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce7d22f5b2267f44909e871e15c619af28e0055db71f84a5fc7529c70c7c030d0314f466d328a2154c476530a6485b19588e09a96ce7ae3f406f29cfd6ec8569
|
|
7
|
+
data.tar.gz: 46cfee8000801ffd398d659b6174b9ac48cc2e4fc098c93e367634f06cfc9d9c659211ef51c452fd7c83262121e8923c6ab7240588fe01782c3002a6fa4f8075
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,95 @@ 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.22.0 — 2026-05-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- BRC-103 wire layer: `WalletWire` transport abstraction, `WalletWireTransceiver` (client), `WalletWireProcessor` (server), `Substrates::HTTPWalletWire` (binary HTTP transport), and `Substrates::HTTPWalletJSON` (JSON-over-HTTP, MetaNet Desktop compatible)
|
|
12
|
+
- Full BRC-103 serialisers for all 28 BRC-100 methods; wire format compatible with go-sdk byte-for-byte
|
|
13
|
+
- `BSV::Wallet.error_from_wire` helper rehydrates error frames into typed `BSV::Wallet::Error` subclasses (codes 1–7)
|
|
14
|
+
- New guide: `docs/guides/brc-103-wire.md` — end-to-end walkthrough of the wire layer
|
|
15
|
+
- Wire layer reference section added to `docs/sdk/wallet.md`
|
|
16
|
+
- `BSV::Transaction::ChainTracker.default(testnet: false)` — provider-routed chain tracker backed by GorillaPool + JungleBus by default; no credentials required (closes #783)
|
|
17
|
+
- `BSV::Network::Protocols::JungleBus` declares `:current_height` so the GorillaPool provider can satisfy chain-tip queries (#784)
|
|
18
|
+
- `BSV::Network::Providers::GorillaPool.testnet` now registers `Protocols::JungleBus` alongside `Protocols::Arcade` so chain-header lookups work on testnet
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `BSV::Transaction::ChainTracker` is now a working default implementation when constructed with a `Provider`; subclasses that override `valid_root_for_height?` and `current_height` continue to work unchanged (constructor now accepts an optional positional `provider` argument: `ChainTracker.new(provider)`, with `provider = nil` preserved for subclass-only use)
|
|
22
|
+
|
|
23
|
+
### Removed (breaking)
|
|
24
|
+
- **`BSV::Transaction::ChainTrackers::Chaintracks`** is removed. Migrate to
|
|
25
|
+
`BSV::Transaction::ChainTracker.default` for general use (GorillaPool), or construct
|
|
26
|
+
a single-protocol Provider explicitly for own-server use:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
own = BSV::Network::Provider.new('local') do |p|
|
|
30
|
+
p.protocol BSV::Network::Protocols::Chaintracks, base_url: 'http://my-server'
|
|
31
|
+
end
|
|
32
|
+
tracker = BSV::Transaction::ChainTracker.new(own)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **`BSV::Transaction::ChainTrackers.default`** (the namespace-level factory method) is
|
|
36
|
+
removed. Use `BSV::Transaction::ChainTracker.default` (the singular class-level
|
|
37
|
+
method) instead. The `ChainTrackers` namespace is now a pure autoload container for
|
|
38
|
+
concrete protocol-specific wrappers; `ChainTracker` (singular) is the porcelain entry
|
|
39
|
+
point.
|
|
40
|
+
|
|
41
|
+
- Closes #778 — GorillaPool chaintracks connectivity: chain data is now reachable
|
|
42
|
+
through the provider via JungleBus; the open question dissolves.
|
|
43
|
+
|
|
44
|
+
### Note
|
|
45
|
+
- The default `ChainTracker` in the Ruby SDK resolves against GorillaPool/JungleBus,
|
|
46
|
+
which diverges from the TS SDK (WhatsOnChain) and Go/Python SDK defaults. This
|
|
47
|
+
reflects the broader pattern of GorillaPool being the default broadcast provider in
|
|
48
|
+
the Ruby SDK.
|
|
49
|
+
|
|
50
|
+
## 0.21.0 — 2026-05-29 (not separately released — see 0.22.0)
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- `Protocols::Arcade` for `bsv-blockchain/arcade` services (run by GorillaPool at
|
|
54
|
+
`arcade.gorillapool.io`). Commands: `broadcast` (`POST /tx`), `get_tx_status`
|
|
55
|
+
(`GET /tx/{txid}`), `health` (`GET /health`). Distinct from `Protocols::ARC` —
|
|
56
|
+
paths, response bodies, and status taxonomies do not overlap. (#775)
|
|
57
|
+
- Shared `BSV::Network::Util` helpers: `safe_parse_json`, `resolve_tx_hex` —
|
|
58
|
+
available to all protocol classes without copy-paste. (#775)
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
- `ARC#call_broadcast` and `ARC#call_broadcast_many` now read their broadcast and
|
|
62
|
+
batch-broadcast paths from the endpoint table rather than hardcoding `/v1/tx` and
|
|
63
|
+
`/v1/txs`. Behaviour is unchanged; subclasses can now declare a different path. (#775)
|
|
64
|
+
- `LivePolicy.default` now points at `https://arc.taal.com` for fee-policy queries
|
|
65
|
+
rather than GorillaPool's Arcade endpoint, which does not expose `/v1/policy`. (#775)
|
|
66
|
+
- `broadcast_p2pkh` MCP tool updated to handle Arcade's `{"status": "submitted"}`
|
|
67
|
+
response shape from GorillaPool. (#775)
|
|
68
|
+
|
|
69
|
+
### Breaking Changes
|
|
70
|
+
- **`Providers::GorillaPool` no longer registers `Protocols::ARC` or
|
|
71
|
+
`Protocols::Chaintracks`.** Mainnet now registers `Protocols::Arcade` instead.
|
|
72
|
+
Consumers calling `provider.call(:broadcast, ...)` against GorillaPool and
|
|
73
|
+
expecting an ARC-shaped response (`{txid, txStatus, ...}`) must migrate to
|
|
74
|
+
Arcade's response shape (`{status: "submitted"}`). No compatibility shim is
|
|
75
|
+
provided — pre-1.0 clean break. (#775)
|
|
76
|
+
- **`BSV_TESTNET_ARC_URL` environment variable renamed to `BSV_TESTNET_ARCADE_URL`.**
|
|
77
|
+
Update any scripts or CI configuration that set this variable. Default value:
|
|
78
|
+
`https://testnet.arcade.gorillapool.io`. (#775, #779)
|
|
79
|
+
- **`Providers::GorillaPool` no longer provides `:current_height` or
|
|
80
|
+
`:get_block_header` commands** (previously routed through the broken
|
|
81
|
+
`Protocols::Chaintracks` registration). A follow-up issue (#778) tracks correctly
|
|
82
|
+
wiring GorillaPool's chaintracks_server at its actual separate URL/port.
|
|
83
|
+
|
|
84
|
+
### Removed
|
|
85
|
+
- `Protocols::Chaintracks` registration from `Providers::GorillaPool` (mainnet and
|
|
86
|
+
testnet). The registration was broken — GorillaPool's chaintracks_server runs as
|
|
87
|
+
a separate service on a separate port; the `/chaintracks/v2/` paths were returning
|
|
88
|
+
404. See #778 for the follow-up to wire it correctly.
|
|
89
|
+
|
|
90
|
+
## 0.20.0 — 2026-05-24
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
- Raise minimum Ruby version from 2.7 to 3.3; add Ruby 4.0 to CI matrix
|
|
94
|
+
- Remove Ruby 2.7 compatibility workarounds (Point#add fallback, OpenSSL.fixed_length_secure_compare fallback)
|
|
95
|
+
- Apply RuboCop modernisations unlocked by Ruby 3.3 target (anonymous forwarding, redundant `require 'set'`, `Hash#except`)
|
|
96
|
+
|
|
8
97
|
## 0.19.1 — 2026-05-13
|
|
9
98
|
|
|
10
99
|
### Fixed
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/sgbett/bsv-ruby-sdk/actions/workflows/ci.yml)
|
|
4
4
|
[](https://codecov.io/gh/sgbett/bsv-ruby-sdk)
|
|
5
5
|
[](https://rubygems.org/gems/bsv-sdk)
|
|
6
|
-
[](https://rubygems.org/gems/bsv-sdk)
|
|
7
7
|
|
|
8
8
|
Welcome to the BSV Blockchain Libraries Project, the comprehensive Ruby SDK designed to provide an updated and unified layer for developing scalable applications on the BSV Blockchain. This SDK addresses the limitations of previous tools by offering a fresh, peer-to-peer approach, adhering to SPV, and ensuring privacy and scalability.
|
|
9
9
|
|
|
@@ -44,7 +44,7 @@ Elliptic curve operations (secp256k1) are provided by the [`secp256k1-native`](h
|
|
|
44
44
|
|
|
45
45
|
### Requirements
|
|
46
46
|
|
|
47
|
-
- Ruby >=
|
|
47
|
+
- Ruby >= 3.3
|
|
48
48
|
- No external dependencies beyond Ruby's standard library (`openssl` for hashing, HMAC, PBKDF2, and AES)
|
|
49
49
|
|
|
50
50
|
### Installation
|
data/lib/bsv/auth/transport.rb
CHANGED
|
@@ -115,11 +115,13 @@ module BSV
|
|
|
115
115
|
arc_result = arc.call(:broadcast, tx)
|
|
116
116
|
return Helpers.error_response("Broadcast failed: #{arc_result.message}") unless arc_result.http_success?
|
|
117
117
|
|
|
118
|
+
data = arc_result.data || {}
|
|
118
119
|
result = {
|
|
119
|
-
txid:
|
|
120
|
-
tx_status:
|
|
120
|
+
txid: data['txid'], # MCP tool boundary: display-order hex from Arcade response (present on re-submission)
|
|
121
|
+
tx_status: data['status'],
|
|
122
|
+
state: data['state'],
|
|
121
123
|
hex: tx.to_hex
|
|
122
|
-
}
|
|
124
|
+
}.compact
|
|
123
125
|
|
|
124
126
|
::MCP::Tool::Response.new(
|
|
125
127
|
[::MCP::Content::Text.new(result.to_json)],
|
data/lib/bsv/network/protocol.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'set'
|
|
4
3
|
require 'net/http'
|
|
5
4
|
require 'json'
|
|
6
5
|
require 'uri'
|
|
@@ -132,11 +131,11 @@ module BSV
|
|
|
132
131
|
# Subscriptions are not callable; calling one raises +NotImplementedError+.
|
|
133
132
|
#
|
|
134
133
|
# @param command_name [Symbol, String] command to invoke
|
|
135
|
-
# @param
|
|
134
|
+
# @param * [Array] positional arguments forwarded to path interpolation
|
|
136
135
|
# @param kwargs [Hash] keyword arguments forwarded to path interpolation
|
|
137
136
|
# @return [ProtocolResponse]
|
|
138
137
|
# @raise [ArgumentError] when command_name is not registered
|
|
139
|
-
def call(command_name,
|
|
138
|
+
def call(command_name, *, **kwargs)
|
|
140
139
|
name = command_name.to_sym
|
|
141
140
|
|
|
142
141
|
if self.class.subscriptions.key?(name)
|
|
@@ -147,10 +146,10 @@ module BSV
|
|
|
147
146
|
escape = :"call_#{name}"
|
|
148
147
|
if respond_to?(escape, true)
|
|
149
148
|
BSV.logger&.debug { "[Protocol] #{self.class.name} :#{name} → escape hatch" }
|
|
150
|
-
return kwargs.empty? ? send(escape, *
|
|
149
|
+
return kwargs.empty? ? send(escape, *) : send(escape, *, **kwargs)
|
|
151
150
|
end
|
|
152
151
|
|
|
153
|
-
default_call(name,
|
|
152
|
+
default_call(name, *, **kwargs)
|
|
154
153
|
end
|
|
155
154
|
|
|
156
155
|
# Dispatches a command directly via HTTP, bypassing any escape hatch.
|
|
@@ -92,7 +92,7 @@ module BSV
|
|
|
92
92
|
callback_batch: callback_batch
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
-
response = post_with_headers(
|
|
95
|
+
response = post_with_headers(self.class.endpoints[:broadcast][:path], body, extra_headers)
|
|
96
96
|
parse_single_broadcast_response(response)
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -124,7 +124,7 @@ module BSV
|
|
|
124
124
|
callback_batch: callback_batch
|
|
125
125
|
)
|
|
126
126
|
|
|
127
|
-
response = post_with_headers(
|
|
127
|
+
response = post_with_headers(self.class.endpoints[:broadcast_many][:path], body, extra_headers)
|
|
128
128
|
parse_batch_broadcast_response(response)
|
|
129
129
|
end
|
|
130
130
|
|
|
@@ -135,30 +135,8 @@ module BSV
|
|
|
135
135
|
request
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
# Coerce a transaction input to hex for the ARC JSON body.
|
|
139
|
-
#
|
|
140
|
-
# Accepts (in order of preference):
|
|
141
|
-
# 1. Hex string — pass-through, zero conversion
|
|
142
|
-
# 2. Binary string — convert to hex
|
|
143
|
-
# 3. Transaction object — prefer EF hex (BRC-30), fall back to raw hex
|
|
144
|
-
#
|
|
145
|
-
# Detection uses content, not encoding: a string is hex if it has even
|
|
146
|
-
# length and contains only hex characters. This handles hex strings
|
|
147
|
-
# tagged as ASCII-8BIT (e.g. read from IO in binary mode).
|
|
148
|
-
#
|
|
149
|
-
# @param tx [String, #to_ef_hex, #to_hex] transaction in any supported form
|
|
150
|
-
# @return [String] hex-encoded transaction
|
|
151
138
|
def resolve_tx_hex(tx)
|
|
152
|
-
|
|
153
|
-
return tx if tx.match?(/\A[0-9a-fA-F]*\z/) && tx.length.even?
|
|
154
|
-
|
|
155
|
-
return tx.unpack1('H*')
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
tx.to_ef_hex
|
|
159
|
-
rescue ArgumentError => e
|
|
160
|
-
BSV.logger&.debug { "[ARC] EF serialisation failed: #{e.message} — falling back to raw hex" }
|
|
161
|
-
tx.to_hex
|
|
139
|
+
BSV::Network::Util.resolve_tx_hex(tx)
|
|
162
140
|
end
|
|
163
141
|
|
|
164
142
|
# Build the hash of ARC-specific extra headers.
|
|
@@ -296,12 +274,8 @@ module BSV
|
|
|
296
274
|
false
|
|
297
275
|
end
|
|
298
276
|
|
|
299
|
-
# Parse JSON, returning a hash with a 'detail' key on parse failure.
|
|
300
|
-
# When the raw input is nil or empty the detail is nil (not an empty string).
|
|
301
277
|
def safe_parse_json(raw)
|
|
302
|
-
|
|
303
|
-
rescue JSON::ParserError
|
|
304
|
-
{ 'detail' => (raw.to_s.empty? ? nil : raw.to_s) }
|
|
278
|
+
BSV::Network::Util.safe_parse_json(raw)
|
|
305
279
|
end
|
|
306
280
|
end
|
|
307
281
|
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module BSV
|
|
6
|
+
module Network
|
|
7
|
+
module Protocols
|
|
8
|
+
# Arcade protocol implementation for submitting transactions to the BSV network.
|
|
9
|
+
#
|
|
10
|
+
# Arcade is a sibling to ARC — both subclass Protocol directly. The request
|
|
11
|
+
# and response shapes diverge enough to rule out a shared abstract base:
|
|
12
|
+
# Arcade uses a narrower status taxonomy and a different broadcast response
|
|
13
|
+
# structure (no txid on fresh submission; idempotent re-submit returns
|
|
14
|
+
# status plus txid plus state).
|
|
15
|
+
#
|
|
16
|
+
# == Example
|
|
17
|
+
#
|
|
18
|
+
# arcade = BSV::Network::Protocols::Arcade.new(
|
|
19
|
+
# base_url: 'https://arcade.gorillapool.io'
|
|
20
|
+
# )
|
|
21
|
+
# result = arcade.call(:broadcast, tx)
|
|
22
|
+
# result.http_success? # => true
|
|
23
|
+
# result.data['status'] # => "submitted"
|
|
24
|
+
#
|
|
25
|
+
# @see https://github.com/bsv-blockchain/arcade Arcade repository
|
|
26
|
+
class Arcade < Protocol
|
|
27
|
+
# Arcade response statuses that indicate a transaction was NOT accepted.
|
|
28
|
+
# Narrower than ARC's taxonomy — Arcade has a single explicit rejection signal.
|
|
29
|
+
REJECTED_STATUSES = %w[REJECTED].freeze
|
|
30
|
+
|
|
31
|
+
endpoint :broadcast, :post, '/tx', response: :json
|
|
32
|
+
endpoint :get_tx_status, :get, '/tx/{txid}', response: :json
|
|
33
|
+
endpoint :health, :get, '/health', response: :json
|
|
34
|
+
|
|
35
|
+
# @param base_url [String] Arcade base URL
|
|
36
|
+
# @param api_key [String, nil] legacy bearer token shorthand
|
|
37
|
+
# @param auth [Hash, Symbol, nil] auth config; takes precedence over +api_key:+
|
|
38
|
+
# @param network [String, nil] network name for base URL interpolation
|
|
39
|
+
# @param http_client [#request, nil] injectable HTTP client for testing
|
|
40
|
+
# @param callback_url [String, nil] optional X-CallbackUrl header value
|
|
41
|
+
# @param callback_token [String, nil] optional X-CallbackToken header value
|
|
42
|
+
def initialize(base_url:, api_key: nil, auth: nil, network: nil, http_client: nil,
|
|
43
|
+
callback_url: nil, callback_token: nil)
|
|
44
|
+
super(base_url: base_url, api_key: api_key, auth: auth, network: network, http_client: http_client)
|
|
45
|
+
@callback_url = callback_url
|
|
46
|
+
@callback_token = callback_token
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Broadcast escape hatch: Arcade-specific headers and response shape.
|
|
52
|
+
#
|
|
53
|
+
# @param tx [#to_ef_hex, #to_hex, String] transaction object, hex string,
|
|
54
|
+
# or binary string
|
|
55
|
+
# @param callback_url [String, nil]
|
|
56
|
+
# @param callback_token [String, nil]
|
|
57
|
+
# @param full_status_updates [Boolean, nil]
|
|
58
|
+
# @param skip_fee_validation [Boolean, nil]
|
|
59
|
+
# @param skip_script_validation [Boolean, nil]
|
|
60
|
+
# @return [ProtocolResponse]
|
|
61
|
+
def call_broadcast(tx, callback_url: nil, callback_token: nil,
|
|
62
|
+
full_status_updates: nil, skip_fee_validation: nil,
|
|
63
|
+
skip_script_validation: nil, **)
|
|
64
|
+
hex = BSV::Network::Util.resolve_tx_hex(tx)
|
|
65
|
+
body = JSON.generate(rawTx: hex)
|
|
66
|
+
|
|
67
|
+
extra_headers = build_broadcast_headers(
|
|
68
|
+
callback_url: callback_url || @callback_url,
|
|
69
|
+
callback_token: callback_token || @callback_token,
|
|
70
|
+
full_status_updates: full_status_updates,
|
|
71
|
+
skip_fee_validation: skip_fee_validation,
|
|
72
|
+
skip_script_validation: skip_script_validation
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
path = self.class.endpoints[:broadcast][:path]
|
|
76
|
+
uri = URI("#{@base_url}#{path}")
|
|
77
|
+
request = build_request(:post, uri, body)
|
|
78
|
+
extra_headers.each { |k, v| request[k] = v }
|
|
79
|
+
response = execute(uri, request)
|
|
80
|
+
|
|
81
|
+
parse_broadcast_response(response)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# get_tx_status: pass-through to default_call plus rejection check.
|
|
85
|
+
#
|
|
86
|
+
# @param txid [String] display-order hex transaction ID
|
|
87
|
+
# @return [ProtocolResponse]
|
|
88
|
+
def call_get_tx_status(txid, **)
|
|
89
|
+
response = default_call(:get_tx_status, txid)
|
|
90
|
+
return response unless response.http_success?
|
|
91
|
+
|
|
92
|
+
body = response.data
|
|
93
|
+
return response unless body.is_a?(Hash)
|
|
94
|
+
|
|
95
|
+
if rejected_status?(body)
|
|
96
|
+
return response.with(
|
|
97
|
+
http_success: false,
|
|
98
|
+
error_message: body['txStatus'] || 'REJECTED'
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
response
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def build_broadcast_headers(callback_url:, callback_token:, full_status_updates:,
|
|
106
|
+
skip_fee_validation:, skip_script_validation:)
|
|
107
|
+
headers = {}
|
|
108
|
+
headers['X-CallbackUrl'] = callback_url if callback_url
|
|
109
|
+
headers['X-CallbackToken'] = callback_token if callback_token
|
|
110
|
+
headers['X-FullStatusUpdates'] = 'true' if full_status_updates
|
|
111
|
+
headers['X-SkipFeeValidation'] = 'true' if skip_fee_validation
|
|
112
|
+
headers['X-SkipScriptValidation'] = 'true' if skip_script_validation
|
|
113
|
+
headers
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def parse_broadcast_response(response)
|
|
117
|
+
code = response.code.to_i
|
|
118
|
+
|
|
119
|
+
if code == 503
|
|
120
|
+
retry_after = response['Retry-After']
|
|
121
|
+
msg = retry_after ? "Arcade backpressure; retry after #{retry_after}s" : 'Arcade backpressure'
|
|
122
|
+
return ProtocolResponse.new(response, http_success: false, error_message: msg)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
unless (200..299).cover?(code)
|
|
126
|
+
body = BSV::Network::Util.safe_parse_json(response.body)
|
|
127
|
+
return ProtocolResponse.new(
|
|
128
|
+
response,
|
|
129
|
+
http_success: false,
|
|
130
|
+
error_message: body.is_a?(Hash) ? (body['reason'] || body['detail'] || "HTTP #{code}") : "HTTP #{code}"
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
body = BSV::Network::Util.safe_parse_json(response.body)
|
|
135
|
+
|
|
136
|
+
unless body.is_a?(Hash)
|
|
137
|
+
return ProtocolResponse.new(
|
|
138
|
+
response,
|
|
139
|
+
http_success: false,
|
|
140
|
+
error_message: 'Arcade returned a malformed 2xx response'
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
status = body['status']
|
|
145
|
+
unless status
|
|
146
|
+
return ProtocolResponse.new(
|
|
147
|
+
response,
|
|
148
|
+
http_success: false,
|
|
149
|
+
error_message: 'Arcade returned a malformed 2xx response'
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
ProtocolResponse.new(response, data: body)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def rejected_status?(body)
|
|
157
|
+
tx_status = body['txStatus'].to_s.upcase
|
|
158
|
+
REJECTED_STATUSES.include?(tx_status)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -16,15 +16,18 @@ module BSV
|
|
|
16
16
|
#
|
|
17
17
|
# == Usage
|
|
18
18
|
#
|
|
19
|
-
# ct = BSV::Network::Protocols::Chaintracks.new(base_url: '
|
|
19
|
+
# ct = BSV::Network::Protocols::Chaintracks.new(base_url: 'http://localhost:8080')
|
|
20
20
|
# result = ct.call(:current_height)
|
|
21
21
|
# result.data # => 800000
|
|
22
22
|
#
|
|
23
23
|
# result = ct.call(:get_block_header, 800_000)
|
|
24
24
|
# result.data # => { 'hash' => '...', 'height' => 800000, 'merkleRoot' => '...' }
|
|
25
25
|
#
|
|
26
|
-
# @note Chaintracks is
|
|
27
|
-
#
|
|
26
|
+
# @note No public Chaintracks instance is hosted by major providers — GorillaPool
|
|
27
|
+
# serves chain data via JungleBus instead. For general chain-tracking against
|
|
28
|
+
# GorillaPool, use the porcelain interface:
|
|
29
|
+
# +BSV::Transaction::ChainTracker.new(BSV::Network::Providers::GorillaPool.mainnet)+
|
|
30
|
+
# which routes through JungleBus automatically.
|
|
28
31
|
class Chaintracks < Protocol
|
|
29
32
|
endpoint :get_block_header, :get, '/chaintracks/v2/header/height/{height}', response: :json
|
|
30
33
|
endpoint :current_height, :get, '/chaintracks/v2/tip',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
module BSV
|
|
4
6
|
module Network
|
|
5
7
|
module Protocols
|
|
@@ -39,6 +41,10 @@ module BSV
|
|
|
39
41
|
# Block headers from a given height (supports ?limit=N, max 10000)
|
|
40
42
|
endpoint :get_block_headers, :get, '/v1/block_header/list/{height}', response: :json_array
|
|
41
43
|
|
|
44
|
+
# Current chain tip height
|
|
45
|
+
endpoint :current_height, :get, '/v1/block_header/tip',
|
|
46
|
+
response: ->(body) { JSON.parse(body)['height'] }
|
|
47
|
+
|
|
42
48
|
# @param base_url [String] base URL for the JungleBus API
|
|
43
49
|
# @param api_key [String, nil] legacy API key shorthand — use +auth:+ for new code
|
|
44
50
|
# @param auth [Hash, Symbol, nil] auth config; takes precedence over +api_key:+
|
|
@@ -7,6 +7,7 @@ module BSV
|
|
|
7
7
|
# JungleBus, Ordinals, TAALBinary, and WoCREST.
|
|
8
8
|
module Protocols
|
|
9
9
|
autoload :ARC, 'bsv/network/protocols/arc'
|
|
10
|
+
autoload :Arcade, 'bsv/network/protocols/arcade'
|
|
10
11
|
autoload :Chaintracks, 'bsv/network/protocols/chaintracks'
|
|
11
12
|
autoload :JungleBus, 'bsv/network/protocols/jungle_bus'
|
|
12
13
|
autoload :Ordinals, 'bsv/network/protocols/ordinals'
|
data/lib/bsv/network/provider.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'set'
|
|
4
|
-
|
|
5
3
|
module BSV
|
|
6
4
|
module Network
|
|
7
5
|
# Provider is a named configuration container that hosts one or more Protocol
|
|
@@ -56,10 +54,10 @@ module BSV
|
|
|
56
54
|
# execution.
|
|
57
55
|
#
|
|
58
56
|
# @param klass [Class] a Protocol subclass
|
|
59
|
-
# @param
|
|
57
|
+
# @param ** [Hash] keyword arguments forwarded to +klass.new+
|
|
60
58
|
# @return [Protocol] the newly created protocol instance
|
|
61
|
-
def protocol(klass, **
|
|
62
|
-
instance = klass.new(**
|
|
59
|
+
def protocol(klass, **)
|
|
60
|
+
instance = klass.new(**)
|
|
63
61
|
@protocols << instance
|
|
64
62
|
klass.commands.each do |cmd|
|
|
65
63
|
@command_index[cmd] ||= instance
|
|
@@ -122,16 +120,16 @@ module BSV
|
|
|
122
120
|
# Dispatches a command to the first-registered protocol that serves it.
|
|
123
121
|
#
|
|
124
122
|
# @param command_name [Symbol, String] command to invoke
|
|
125
|
-
# @param
|
|
126
|
-
# @param
|
|
123
|
+
# @param * [Array] positional arguments forwarded to the protocol
|
|
124
|
+
# @param ** [Hash] keyword arguments forwarded to the protocol
|
|
127
125
|
# @return [ProtocolResponse]
|
|
128
126
|
# @raise [ArgumentError] when no registered protocol serves the command
|
|
129
|
-
def call(command_name,
|
|
127
|
+
def call(command_name, *, **)
|
|
130
128
|
sym = command_name.to_sym
|
|
131
129
|
instance = @command_index[sym]
|
|
132
130
|
raise ArgumentError, "#{@name} does not provide command :#{sym}" unless instance
|
|
133
131
|
|
|
134
|
-
instance.call(sym,
|
|
132
|
+
instance.call(sym, *, **)
|
|
135
133
|
end
|
|
136
134
|
|
|
137
135
|
private
|
|
@@ -4,17 +4,18 @@ module BSV
|
|
|
4
4
|
module Network
|
|
5
5
|
module Providers
|
|
6
6
|
# GorillaPool returns pre-configured Provider instances using the GorillaPool
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# Arcade infrastructure for broadcasting, the GorillaPool Ordinals API for
|
|
8
|
+
# transaction and merkle path lookups, and JungleBus for indexed transaction
|
|
9
|
+
# data and block headers.
|
|
10
10
|
#
|
|
11
|
-
# Mainnet composes
|
|
12
|
-
# -
|
|
13
|
-
# - Chaintracks at +https://arcade.gorillapool.io+
|
|
11
|
+
# Mainnet composes three protocols:
|
|
12
|
+
# - Arcade at +https://arcade.gorillapool.io+
|
|
14
13
|
# - Ordinals at +https://ordinals.gorillapool.io+
|
|
15
14
|
# - JungleBus at +https://junglebus.gorillapool.io+
|
|
16
15
|
#
|
|
17
|
-
# Testnet
|
|
16
|
+
# Testnet composes two protocols:
|
|
17
|
+
# - Arcade at +https://testnet.arcade.gorillapool.io+
|
|
18
|
+
# - JungleBus at +https://testnet.junglebus.gorillapool.io+
|
|
18
19
|
#
|
|
19
20
|
# == Example
|
|
20
21
|
#
|
|
@@ -30,9 +31,9 @@ module BSV
|
|
|
30
31
|
# Default requests-per-second limit for unauthenticated use.
|
|
31
32
|
DEFAULT_RATE_LIMIT = 3
|
|
32
33
|
|
|
33
|
-
# Returns a mainnet Provider configured with
|
|
34
|
+
# Returns a mainnet Provider configured with Arcade, Ordinals, and JungleBus.
|
|
34
35
|
#
|
|
35
|
-
# Auth is forwarded to all
|
|
36
|
+
# Auth is forwarded to all three protocols so each can authenticate independently.
|
|
36
37
|
#
|
|
37
38
|
# @param auth [Hash, Symbol, nil] auth config forwarded to Provider and all protocols
|
|
38
39
|
# @param rate_limit [Numeric, nil] requests per second; defaults to +DEFAULT_RATE_LIMIT+
|
|
@@ -42,16 +43,14 @@ module BSV
|
|
|
42
43
|
resolved_auth = auth || (opts[:api_key] ? { bearer: opts[:api_key] } : :none)
|
|
43
44
|
common = opts.slice(:api_key, :http_client).merge(auth: auth)
|
|
44
45
|
Provider.new('GorillaPool', auth: resolved_auth, rate_limit: rate_limit) do |p|
|
|
45
|
-
p.protocol Protocols::
|
|
46
|
-
|
|
47
|
-
p.protocol Protocols::Ordinals,
|
|
48
|
-
p.protocol Protocols::JungleBus,
|
|
46
|
+
p.protocol Protocols::Arcade, base_url: 'https://arcade.gorillapool.io', auth: auth, **opts
|
|
47
|
+
# TODO: re-register chaintracks_server at separate URL/port — see issue #778
|
|
48
|
+
p.protocol Protocols::Ordinals, base_url: 'https://ordinals.gorillapool.io', **common
|
|
49
|
+
p.protocol Protocols::JungleBus, base_url: 'https://junglebus.gorillapool.io', **common
|
|
49
50
|
end
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
# Returns a testnet Provider configured with
|
|
53
|
-
#
|
|
54
|
-
# Auth is forwarded to both protocols.
|
|
53
|
+
# Returns a testnet Provider configured with Arcade only.
|
|
55
54
|
#
|
|
56
55
|
# @param auth [Hash, Symbol, nil] auth config forwarded to Provider and all protocols
|
|
57
56
|
# @param rate_limit [Numeric, nil] requests per second; defaults to +DEFAULT_RATE_LIMIT+
|
|
@@ -61,8 +60,9 @@ module BSV
|
|
|
61
60
|
resolved_auth = auth || (opts[:api_key] ? { bearer: opts[:api_key] } : :none)
|
|
62
61
|
common = opts.slice(:api_key, :http_client).merge(auth: auth)
|
|
63
62
|
Provider.new('GorillaPool', auth: resolved_auth, rate_limit: rate_limit) do |p|
|
|
64
|
-
p.protocol Protocols::
|
|
65
|
-
p.protocol Protocols::
|
|
63
|
+
p.protocol Protocols::Arcade, base_url: 'https://testnet.arcade.gorillapool.io', auth: auth, **opts
|
|
64
|
+
p.protocol Protocols::JungleBus, base_url: 'https://testnet.junglebus.gorillapool.io', **common
|
|
65
|
+
# TODO: re-register chaintracks_server at separate URL/port — see issue #778
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module BSV
|
|
6
|
+
module Network
|
|
7
|
+
# Shared utility methods for protocol implementations.
|
|
8
|
+
module Util
|
|
9
|
+
# Parse JSON, returning a hash with a 'detail' key on parse failure.
|
|
10
|
+
# When the raw input is nil or empty the detail is nil (not an empty string).
|
|
11
|
+
def self.safe_parse_json(raw)
|
|
12
|
+
JSON.parse(raw.to_s)
|
|
13
|
+
rescue JSON::ParserError
|
|
14
|
+
{ 'detail' => (raw.to_s.empty? ? nil : raw.to_s) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Coerce a transaction input to hex.
|
|
18
|
+
#
|
|
19
|
+
# Accepts (in order of preference):
|
|
20
|
+
# 1. Hex string — pass-through, zero conversion
|
|
21
|
+
# 2. Binary string — convert to hex
|
|
22
|
+
# 3. Transaction object — prefer EF hex (BRC-30), fall back to raw hex
|
|
23
|
+
#
|
|
24
|
+
# Detection uses content, not encoding: a string is hex if it has even
|
|
25
|
+
# length and contains only hex characters. This handles hex strings
|
|
26
|
+
# tagged as ASCII-8BIT (e.g. read from IO in binary mode).
|
|
27
|
+
#
|
|
28
|
+
# @param tx [String, #to_ef_hex, #to_hex] transaction in any supported form
|
|
29
|
+
# @return [String] hex-encoded transaction
|
|
30
|
+
def self.resolve_tx_hex(tx)
|
|
31
|
+
if tx.is_a?(String)
|
|
32
|
+
return tx if tx.match?(/\A[0-9a-fA-F]*\z/) && tx.length.even?
|
|
33
|
+
|
|
34
|
+
return tx.unpack1('H*')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
tx.to_ef_hex
|
|
38
|
+
rescue ArgumentError => e
|
|
39
|
+
BSV.logger&.debug { "[Network::Util] EF serialisation failed: #{e.message} — falling back to raw hex" }
|
|
40
|
+
tx.to_hex
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/bsv/network.rb
CHANGED