bsv-sdk 0.14.0 → 0.16.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +14 -2
  4. data/lib/bsv/auth/auth_middleware.rb +6 -6
  5. data/lib/bsv/auth/certificate.rb +16 -16
  6. data/lib/bsv/auth/master_certificate.rb +5 -5
  7. data/lib/bsv/auth/nonce.rb +13 -13
  8. data/lib/bsv/auth/peer.rb +53 -53
  9. data/lib/bsv/auth/verifiable_certificate.rb +1 -1
  10. data/lib/bsv/identity/client.rb +26 -32
  11. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +17 -11
  12. data/lib/bsv/mcp/tools/check_balance.rb +16 -4
  13. data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
  14. data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
  15. data/lib/bsv/network/arc.rb +13 -153
  16. data/lib/bsv/network/whats_on_chain.rb +13 -107
  17. data/lib/bsv/overlay/admin_token_template.rb +4 -4
  18. data/lib/bsv/primitives/base58.rb +2 -1
  19. data/lib/bsv/primitives/curve.rb +37 -12
  20. data/lib/bsv/primitives/ecdsa.rb +4 -4
  21. data/lib/bsv/primitives/openssl_ec_shim.rb +32 -5
  22. data/lib/bsv/primitives/private_key.rb +2 -2
  23. data/lib/bsv/primitives/public_key.rb +1 -1
  24. data/lib/bsv/primitives/schnorr.rb +4 -4
  25. data/lib/bsv/primitives/secp256k1.rb +4 -595
  26. data/lib/bsv/primitives/signature.rb +2 -0
  27. data/lib/bsv/primitives/signed_message.rb +6 -5
  28. data/lib/bsv/registry/client.rb +23 -27
  29. data/lib/bsv/script/push_drop_template.rb +4 -4
  30. data/lib/bsv/secp256k1_native.bundle +0 -0
  31. data/lib/bsv/version.rb +1 -1
  32. data/lib/bsv/wallet/errors.rb +47 -0
  33. data/lib/bsv/wallet/interface/brc100.rb +267 -0
  34. data/lib/bsv/wallet/interface.rb +9 -0
  35. data/lib/bsv/wallet/proto_wallet/key_deriver.rb +150 -0
  36. data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
  37. data/lib/bsv/wallet/proto_wallet.rb +321 -0
  38. data/lib/bsv/wallet.rb +16 -0
  39. data/lib/bsv-sdk.rb +4 -1
  40. metadata +37 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4532a163439721455da80290d8b02342e597a01bb1e00b259d13343f8f9bd592
4
- data.tar.gz: b7cfda4a8084b96c28f34afb6829fcba9f5be0dabb01fca536147bfe2c66bad8
3
+ metadata.gz: a76170f9d5dcdfc76945c20c541b556a02f3fb5a7214a8d6f777eb589f048461
4
+ data.tar.gz: 57176276d41d430d654682dd22aef0f93bee9e2e3c57d4fb76d435dbd8c3e705
5
5
  SHA512:
6
- metadata.gz: ed0b97db3cec8503eba509f232a463d8566373b34ccaf5fe7cd130a72e712cde17b083b4ce94ef64fa84786d6e6aec8965423ca6b8a7471ada70ebd522956e2a
7
- data.tar.gz: 0747a93bef059a8eccf0f504e703d20ddc875f8c562db8f01d3af4050bf7562deb262eb4494db4d98b78665f91274ed77754fa76e99254b8592b7e0f1526aa13
6
+ metadata.gz: fb7257bda0b14c14ab55d8da3a19fdc27a8eca113cfeb857487079d4e0f042ebbaef184e81382d50aed7a9efd69dc4d2cb2d77594b6e1ae5f3f6d047c01d2ab9
7
+ data.tar.gz: 20f9e8fe53476ddd87900d1b58d00811da403703d5ae8cf6dc0f6d4a4836ab8a12e42ddce88c7323bd281e710770f4b7b6805cb842ab4f164c4b17559afa46e7
data/CHANGELOG.md CHANGED
@@ -5,6 +5,49 @@ 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.16.0 — 2026-05-01
9
+
10
+ ### Breaking Changes
11
+ - Removed `bsv-wallet` and `bsv-wallet-postgres` gems from the monorepo (#662)
12
+ - Unified `BSV::Wallet` namespace with canonical BRC-100 interface, error reconciliation, and idiomatic ProtoWallet (#664)
13
+ - Hard-deprecated `Network::ARC` and `Network::WhatsOnChain` facades (#659)
14
+
15
+ ### Added
16
+ - `BSV::Wallet::ProtoWallet` for SDK-native cryptographic operations (signing, encryption, HMAC, key derivation) (#662)
17
+
18
+ ### Fixed
19
+ - Guard nil `status_code` in MCP error messages (#659)
20
+
21
+ ## 0.15.0 — 2026-04-29
22
+
23
+ ### Added
24
+ - Extracted secp256k1 elliptic curve operations to the standalone
25
+ `secp256k1-native` gem, with an optional native C extension providing
26
+ ~22x speedup for field, scalar, and Jacobian point operations (#648)
27
+ - Native C extension scaffold with field arithmetic, scalar arithmetic,
28
+ and Jacobian point operations (#627, #628, #629, #630, #631)
29
+ - Constant-time Montgomery ladder with branchless conditional swap for
30
+ scalar multiplication; `mul` is now constant-time by default, with
31
+ `mul_vt` available for variable-time use cases (#641, #653)
32
+ - Wycheproof Bitcoin ECDSA test vectors (463 cases) with explicit
33
+ categorisation of high-S malleability cases as mathematically valid
34
+ but policy-rejected — documenting the layer separation between
35
+ ECDSA verification and Bitcoin's low-S enforcement (#652)
36
+ - Wycheproof standard ECDSA test vectors (474 cases), RFC 6979
37
+ compliance suite, and secp256k1 field/scalar/point compliance
38
+ examples (#636, #637, #638)
39
+
40
+ ### Fixed
41
+ - Carry overflow in schoolbook multiplication (L2) and branchless
42
+ borrow extraction in field reduction (#631)
43
+ - Gemspec version floor, docstring, and variable name corrections
44
+ from PR review (#653)
45
+
46
+ ### Changed
47
+ - Replaced `BN` hex intermediaries with direct binary construction
48
+ for improved performance (#622)
49
+ - Binary byte comparison in `SignedMessage` verification (#624)
50
+
8
51
  ## 0.14.0 — 2026-04-22
9
52
 
10
53
  ### Added
data/README.md CHANGED
@@ -34,7 +34,9 @@ These are maintained under the BSV Blockchain organisation and backed by the Bit
34
34
 
35
35
  The BSV Blockchain Libraries Project aims to structure and maintain a middleware layer of the BSV Blockchain technology stack. By facilitating the development and maintenance of core libraries, it serves as an essential toolkit for developers looking to build on the BSV Blockchain.
36
36
 
37
- This Ruby SDK brings maximum compatibility with the official SDK family to the Ruby ecosystem. It was born from a practical need: building an attestation gem ([bsv-attest](https://rubygems.org/gems/bsv-attest)) required a complete, idiomatic Ruby implementation of BSV primitives, script handling, and transaction construction. Rather than wrapping FFI bindings or shelling out to other languages, the SDK implements everything in pure Ruby. Elliptic curve operations (secp256k1) use a native Ruby implementation ported from the TypeScript reference SDK; OpenSSL is used only for hashing, HMAC, and symmetric encryption.
37
+ This Ruby SDK brings maximum compatibility with the official SDK family to the Ruby ecosystem. It was born from a practical need: building an attestation gem ([bsv-attest](https://rubygems.org/gems/bsv-attest)) required a complete, idiomatic Ruby implementation of BSV primitives, script handling, and transaction construction.
38
+
39
+ Elliptic curve operations (secp256k1) are provided by the [`secp256k1-native`](https://github.com/sgbett/secp256k1-native) gem, which was originally part of this library and has been extracted as a standalone dependency. This is a custom implementation ported from the TypeScript reference SDK — it does not wrap `libsecp256k1`. It includes a pure Ruby implementation with an optional native C extension for constant-time field arithmetic and performance. OpenSSL is used only for hashing, HMAC, and symmetric encryption.
38
40
 
39
41
  <!-- TODO: Update bsv-attest link once gem documentation is published (see #48) -->
40
42
 
@@ -59,6 +61,16 @@ Or install directly:
59
61
  gem install bsv-sdk
60
62
  ```
61
63
 
64
+ ### Native C Extension (Recommended)
65
+
66
+ The SDK depends on [`secp256k1-native`](https://github.com/sgbett/secp256k1-native) for elliptic curve operations. It works out of the box in pure Ruby, but compiling the optional C extension is strongly recommended — it provides constant-time field arithmetic, which protects against timing side-channel attacks on private key operations:
67
+
68
+ ```bash
69
+ cd $(bundle show secp256k1-native) && bundle exec rake compile
70
+ ```
71
+
72
+ This requires a C99 compiler with `__uint128_t` support (GCC or Clang on macOS/Linux). If compilation is not possible, the SDK falls back to pure Ruby automatically — but the pure Ruby path does not guarantee constant-time execution.
73
+
62
74
  ### Basic Usage
63
75
 
64
76
  Create and sign a P2PKH transaction:
@@ -101,7 +113,7 @@ puts tx.to_hex
101
113
 
102
114
  ## Features & Deliverables
103
115
 
104
- - **Cryptographic Primitives** — ECDSA signing with RFC 6979 deterministic nonces, Schnorr signatures, ECIES encryption/decryption, Bitcoin Signed Messages. Elliptic curve operations use a [pure Ruby secp256k1 implementation](docs/about/secp256k1.md) ported from the TypeScript reference SDK.
116
+ - **Cryptographic Primitives** — ECDSA signing with RFC 6979 deterministic nonces, Schnorr signatures, ECIES encryption/decryption, Bitcoin Signed Messages. Elliptic curve operations are provided by the [`secp256k1-native`](https://github.com/sgbett/secp256k1-native) gem a pure Ruby secp256k1 implementation with an optional C extension (~22× speedup).
105
117
  - **Key Management** — BIP-32 HD key derivation, BIP-39 mnemonic generation (12/24-word phrases), WIF import/export, Base58Check encoding/decoding.
106
118
  - **Script Layer** — Complete opcode set, script parsing and serialisation, type detection and predicates (`p2pkh?`, `p2pk?`, `p2sh?`, `multisig?`, `op_return?`), data extraction (pubkey hashes, script hashes, addresses), and a fluent builder API.
107
119
  - **Script Templates** — Ready-made locking and unlocking script generators for P2PKH, P2PK, P2MS (multisig), and OP_RETURN.
@@ -180,12 +180,12 @@ module BSV
180
180
  response_nonce = ::Base64.strict_encode64(::SecureRandom.random_bytes(32))
181
181
  key_id = "#{response_nonce} #{session.peer_nonce}"
182
182
 
183
- sig_result = @wallet.create_signature({
184
- data: response_payload_bin.bytes,
185
- protocol_id: Peer::AUTH_PROTOCOL,
186
- key_id: key_id,
187
- counterparty: identity_key
188
- })
183
+ sig_result = @wallet.create_signature(
184
+ data: response_payload_bin.bytes,
185
+ protocol_id: Peer::AUTH_PROTOCOL,
186
+ key_id: key_id,
187
+ counterparty: identity_key
188
+ )
189
189
 
190
190
  our_identity_key = @peer.identity_key
191
191
  sig_hex_out = sig_result[:signature].pack('C*').unpack1('H*')
@@ -166,27 +166,27 @@ module BSV
166
166
 
167
167
  # Verify the certificate's signature.
168
168
  #
169
- # Uses a fresh +'anyone'+ Client as the verifier, which matches the
169
+ # Uses a fresh +'anyone'+ ProtoWallet as the verifier, which matches the
170
170
  # TS SDK behaviour. If no signature is present, raises +ArgumentError+.
171
171
  #
172
172
  # @param verifier_wallet [#verify_signature, nil] wallet to verify with;
173
- # defaults to +BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new)+
173
+ # defaults to +BSV::Wallet::ProtoWallet.new('anyone')+
174
174
  # @return [Boolean] +true+ if the signature is valid
175
175
  # @raise [ArgumentError] if the certificate has no signature
176
176
  def verify(verifier_wallet = nil)
177
177
  raise ArgumentError, 'certificate has no signature to verify' if @signature.nil? || @signature.empty?
178
178
 
179
- verifier_wallet ||= BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new, allow_memory_store: true)
179
+ verifier_wallet ||= BSV::Wallet::ProtoWallet.new('anyone')
180
180
  preimage = to_binary(include_signature: false)
181
181
  sig_bytes = [@signature].pack('H*').unpack('C*')
182
182
 
183
- result = verifier_wallet.verify_signature({
184
- data: preimage.unpack('C*'),
185
- signature: sig_bytes,
186
- protocol_id: CERT_SIG_PROTOCOL,
187
- key_id: "#{@type} #{@serial_number}",
188
- counterparty: @certifier
189
- })
183
+ result = verifier_wallet.verify_signature(
184
+ data: preimage.unpack('C*'),
185
+ signature: sig_bytes,
186
+ protocol_id: CERT_SIG_PROTOCOL,
187
+ key_id: "#{@type} #{@serial_number}",
188
+ counterparty: @certifier
189
+ )
190
190
 
191
191
  result.is_a?(Hash) && result[:valid] == true
192
192
  rescue BSV::Wallet::InvalidSignatureError
@@ -203,14 +203,14 @@ module BSV
203
203
  def sign(certifier_wallet)
204
204
  raise ArgumentError, "certificate has already been signed: #{@signature}" if @signature && !@signature.empty?
205
205
 
206
- @certifier = certifier_wallet.get_public_key({ identity_key: true })[:public_key]
206
+ @certifier = certifier_wallet.get_public_key(identity_key: true)[:public_key]
207
207
 
208
208
  preimage = to_binary(include_signature: false)
209
- result = certifier_wallet.create_signature({
210
- data: preimage.unpack('C*'),
211
- protocol_id: CERT_SIG_PROTOCOL,
212
- key_id: "#{@type} #{@serial_number}"
213
- })
209
+ result = certifier_wallet.create_signature(
210
+ data: preimage.unpack('C*'),
211
+ protocol_id: CERT_SIG_PROTOCOL,
212
+ key_id: "#{@type} #{@serial_number}"
213
+ )
214
214
  @signature = result[:signature].pack('C*').unpack1('H*')
215
215
  end
216
216
 
@@ -128,7 +128,7 @@ module BSV
128
128
  privileged_reason: privileged_reason
129
129
  }.merge(Certificate.certificate_field_encryption_details(field_name))
130
130
 
131
- result = creator_wallet.encrypt(enc_args)
131
+ result = creator_wallet.encrypt(**enc_args)
132
132
  master_keyring[field_name] = Base64.strict_encode64(result[:ciphertext].pack('C*'))
133
133
  end
134
134
 
@@ -187,7 +187,7 @@ module BSV
187
187
  privileged_reason: privileged_reason
188
188
  }.merge(Certificate.certificate_field_encryption_details(field_name, serial_number))
189
189
 
190
- result = subject_wallet.encrypt(enc_args)
190
+ result = subject_wallet.encrypt(**enc_args)
191
191
  keyring[field_name] = Base64.strict_encode64(result[:ciphertext].pack('C*'))
192
192
  end
193
193
 
@@ -218,7 +218,7 @@ module BSV
218
218
  keyring = result[:master_keyring]
219
219
 
220
220
  subject_key = if subject == 'self'
221
- certifier_wallet.get_public_key({ identity_key: true })[:public_key]
221
+ certifier_wallet.get_public_key(identity_key: true)[:public_key]
222
222
  else
223
223
  subject
224
224
  end
@@ -229,7 +229,7 @@ module BSV
229
229
  "#{'00' * 32}.0"
230
230
  end
231
231
 
232
- certifier_key = certifier_wallet.get_public_key({ identity_key: true })[:public_key]
232
+ certifier_key = certifier_wallet.get_public_key(identity_key: true)[:public_key]
233
233
 
234
234
  cert = new(
235
235
  type: certificate_type,
@@ -309,7 +309,7 @@ module BSV
309
309
  privileged_reason: privileged_reason
310
310
  }.merge(Certificate.certificate_field_encryption_details(field_name))
311
311
 
312
- field_revelation_key = wallet.decrypt(dec_args)[:plaintext]
312
+ field_revelation_key = wallet.decrypt(**dec_args)[:plaintext]
313
313
 
314
314
  sym_key = BSV::Primitives::SymmetricKey.new(field_revelation_key.pack('C*'))
315
315
  decrypted_bytes = sym_key.decrypt(Base64.strict_decode64(field_value))
@@ -34,12 +34,12 @@ module BSV
34
34
  first_half = SecureRandom.random_bytes(RANDOM_BYTES)
35
35
  key_id = decode_as_utf8(first_half)
36
36
 
37
- result = wallet.create_hmac({
38
- data: first_half.bytes,
39
- protocol_id: PROTOCOL_ID,
40
- key_id: key_id,
41
- counterparty: counterparty
42
- })
37
+ result = wallet.create_hmac(
38
+ data: first_half.bytes,
39
+ protocol_id: PROTOCOL_ID,
40
+ key_id: key_id,
41
+ counterparty: counterparty
42
+ )
43
43
 
44
44
  nonce_bytes = first_half + result[:hmac].pack('C*')
45
45
  ::Base64.strict_encode64(nonce_bytes)
@@ -60,13 +60,13 @@ module BSV
60
60
  hmac_bytes = nonce_bytes.byteslice(RANDOM_BYTES, nonce_bytes.bytesize - RANDOM_BYTES)
61
61
  key_id = decode_as_utf8(first_half)
62
62
 
63
- wallet.verify_hmac({
64
- data: first_half.bytes,
65
- hmac: hmac_bytes.bytes,
66
- protocol_id: PROTOCOL_ID,
67
- key_id: key_id,
68
- counterparty: counterparty
69
- })
63
+ wallet.verify_hmac(
64
+ data: first_half.bytes,
65
+ hmac: hmac_bytes.bytes,
66
+ protocol_id: PROTOCOL_ID,
67
+ key_id: key_id,
68
+ counterparty: counterparty
69
+ )
70
70
 
71
71
  true
72
72
  rescue BSV::Wallet::InvalidHmacError
data/lib/bsv/auth/peer.rb CHANGED
@@ -180,7 +180,7 @@ module BSV
180
180
  #
181
181
  # @return [String] compressed public key hex
182
182
  def identity_key
183
- @identity_key ||= @wallet.get_public_key({ identity_key: true })[:public_key]
183
+ @identity_key ||= @wallet.get_public_key(identity_key: true)[:public_key]
184
184
  end
185
185
 
186
186
  # Checks whether we have an authenticated session with a peer.
@@ -318,12 +318,12 @@ module BSV
318
318
  key_id = key_id_for(request_nonce, session.peer_nonce)
319
319
  data = JSON.generate(certificates_to_request).encode('UTF-8').bytes
320
320
 
321
- sig_result = @wallet.create_signature({
322
- data: data,
323
- protocol_id: AUTH_PROTOCOL,
324
- key_id: key_id,
325
- counterparty: session.peer_identity_key
326
- })
321
+ sig_result = @wallet.create_signature(
322
+ data: data,
323
+ protocol_id: AUTH_PROTOCOL,
324
+ key_id: key_id,
325
+ counterparty: session.peer_identity_key
326
+ )
327
327
 
328
328
  session.last_update = current_time_ms
329
329
  @session_manager.update_session(session)
@@ -355,12 +355,12 @@ module BSV
355
355
  cert_data = certificates.map { |c| c.respond_to?(:to_h) ? c.to_h : c }
356
356
  data = JSON.generate(cert_data).encode('UTF-8').bytes
357
357
 
358
- sig_result = @wallet.create_signature({
359
- data: data,
360
- protocol_id: AUTH_PROTOCOL,
361
- key_id: key_id,
362
- counterparty: session.peer_identity_key
363
- })
358
+ sig_result = @wallet.create_signature(
359
+ data: data,
360
+ protocol_id: AUTH_PROTOCOL,
361
+ key_id: key_id,
362
+ counterparty: session.peer_identity_key
363
+ )
364
364
 
365
365
  session.last_update = current_time_ms
366
366
  @session_manager.update_session(session)
@@ -394,12 +394,12 @@ module BSV
394
394
  request_nonce = ::Base64.strict_encode64(SecureRandom.random_bytes(32))
395
395
  key_id = key_id_for(request_nonce, session.peer_nonce)
396
396
 
397
- sig_result = @wallet.create_signature({
398
- data: payload,
399
- protocol_id: AUTH_PROTOCOL,
400
- key_id: key_id,
401
- counterparty: session.peer_identity_key
402
- })
397
+ sig_result = @wallet.create_signature(
398
+ data: payload,
399
+ protocol_id: AUTH_PROTOCOL,
400
+ key_id: key_id,
401
+ counterparty: session.peer_identity_key
402
+ )
403
403
 
404
404
  session.last_update = current_time_ms
405
405
  @session_manager.update_session(session)
@@ -455,12 +455,12 @@ module BSV
455
455
  sig_data = b64_decode(their_nonce) + b64_decode(our_nonce)
456
456
  key_id = key_id_for(their_nonce, our_nonce)
457
457
 
458
- sig_result = @wallet.create_signature({
459
- data: sig_data,
460
- protocol_id: AUTH_PROTOCOL,
461
- key_id: key_id,
462
- counterparty: peer_key
463
- })
458
+ sig_result = @wallet.create_signature(
459
+ data: sig_data,
460
+ protocol_id: AUTH_PROTOCOL,
461
+ key_id: key_id,
462
+ counterparty: peer_key
463
+ )
464
464
 
465
465
  @last_interacted_peer ||= peer_key if @auto_persist_last_session
466
466
 
@@ -498,13 +498,13 @@ module BSV
498
498
  sig_data = b64_decode(our_nonce) + b64_decode(their_nonce)
499
499
  key_id = key_id_for(our_nonce, their_nonce)
500
500
 
501
- @wallet.verify_signature({
502
- data: sig_data,
503
- signature: signature,
504
- protocol_id: AUTH_PROTOCOL,
505
- key_id: key_id,
506
- counterparty: peer_key
507
- })
501
+ @wallet.verify_signature(
502
+ data: sig_data,
503
+ signature: signature,
504
+ protocol_id: AUTH_PROTOCOL,
505
+ key_id: key_id,
506
+ counterparty: peer_key
507
+ )
508
508
 
509
509
  # Authentication complete
510
510
  session.peer_nonce = their_nonce
@@ -574,13 +574,13 @@ module BSV
574
574
  # Verify signature: signed over payload with key_id "msg_nonce session_nonce"
575
575
  key_id = key_id_for(msg_nonce, session.session_nonce)
576
576
 
577
- @wallet.verify_signature({
578
- data: payload,
579
- signature: signature,
580
- protocol_id: AUTH_PROTOCOL,
581
- key_id: key_id,
582
- counterparty: session.peer_identity_key
583
- })
577
+ @wallet.verify_signature(
578
+ data: payload,
579
+ signature: signature,
580
+ protocol_id: AUTH_PROTOCOL,
581
+ key_id: key_id,
582
+ counterparty: session.peer_identity_key
583
+ )
584
584
 
585
585
  session.last_update = current_time_ms
586
586
  @session_manager.update_session(session)
@@ -610,13 +610,13 @@ module BSV
610
610
  data = JSON.generate(requested).encode('UTF-8').bytes
611
611
  key_id = key_id_for(msg_nonce, session.session_nonce)
612
612
 
613
- @wallet.verify_signature({
614
- data: data,
615
- signature: signature,
616
- protocol_id: AUTH_PROTOCOL,
617
- key_id: key_id,
618
- counterparty: session.peer_identity_key
619
- })
613
+ @wallet.verify_signature(
614
+ data: data,
615
+ signature: signature,
616
+ protocol_id: AUTH_PROTOCOL,
617
+ key_id: key_id,
618
+ counterparty: session.peer_identity_key
619
+ )
620
620
 
621
621
  session.last_update = current_time_ms
622
622
  @session_manager.update_session(session)
@@ -655,13 +655,13 @@ module BSV
655
655
  data = JSON.generate(certs).encode('UTF-8').bytes
656
656
  key_id = key_id_for(msg_nonce, session.session_nonce)
657
657
 
658
- @wallet.verify_signature({
659
- data: data,
660
- signature: signature,
661
- protocol_id: AUTH_PROTOCOL,
662
- key_id: key_id,
663
- counterparty: session.peer_identity_key
664
- })
658
+ @wallet.verify_signature(
659
+ data: data,
660
+ signature: signature,
661
+ protocol_id: AUTH_PROTOCOL,
662
+ key_id: key_id,
663
+ counterparty: session.peer_identity_key
664
+ )
665
665
 
666
666
  if certs.is_a?(Array) && !certs.empty?
667
667
  validation_msg = { identity_key: session.peer_identity_key, certificates: certs }
@@ -130,7 +130,7 @@ module BSV
130
130
  privileged_reason: privileged_reason
131
131
  }.merge(Certificate.certificate_field_encryption_details(field_name, @serial_number))
132
132
 
133
- field_revelation_key = verifier_wallet.decrypt(dec_args)[:plaintext]
133
+ field_revelation_key = verifier_wallet.decrypt(**dec_args)[:plaintext]
134
134
 
135
135
  sym_key = BSV::Primitives::SymmetricKey.new(field_revelation_key.pack('C*'))
136
136
  decrypted_bytes = sym_key.decrypt(Base64.strict_decode64(@fields[field_name]))
@@ -71,7 +71,7 @@ module BSV
71
71
  args[:limit] = limit unless limit.nil?
72
72
  args[:offset] = offset unless offset.nil?
73
73
 
74
- result = @wallet.discover_by_identity_key(args, originator: @originator)
74
+ result = @wallet.discover_by_identity_key(**args, originator: @originator)
75
75
  parse_certificates(result)
76
76
  end
77
77
 
@@ -89,7 +89,7 @@ module BSV
89
89
  args[:limit] = limit unless limit.nil?
90
90
  args[:offset] = offset unless offset.nil?
91
91
 
92
- result = @wallet.discover_by_attributes(args, originator: @originator)
92
+ result = @wallet.discover_by_attributes(**args, originator: @originator)
93
93
  parse_certificates(result)
94
94
  end
95
95
 
@@ -117,7 +117,7 @@ module BSV
117
117
  # Prove the certificate to the "anyone" verifier (PrivateKey(1) public key)
118
118
  anyone_pubkey = BSV::Script::PushDropTemplate::GENERATOR_PUBKEY_HEX
119
119
  prove_result = @wallet.prove_certificate(
120
- { certificate: certificate, fields_to_reveal: fields_to_reveal, verifier: anyone_pubkey },
120
+ certificate: certificate, fields_to_reveal: fields_to_reveal, verifier: anyone_pubkey,
121
121
  originator: @originator
122
122
  )
123
123
  keyring = prove_result[:keyring_for_verifier]
@@ -155,17 +155,15 @@ module BSV
155
155
 
156
156
  # Create the transaction
157
157
  create_result = @wallet.create_action(
158
- {
159
- description: 'Create a new Identity Token',
160
- outputs: [
161
- {
162
- satoshis: @options.token_amount,
163
- locking_script: locking_script.to_hex,
164
- output_description: 'Identity Token'
165
- }
166
- ],
167
- options: { randomize_outputs: false }
168
- },
158
+ description: 'Create a new Identity Token',
159
+ outputs: [
160
+ {
161
+ satoshis: @options.token_amount,
162
+ locking_script: locking_script.to_hex,
163
+ output_description: 'Identity Token'
164
+ }
165
+ ],
166
+ options: { randomize_outputs: false },
169
167
  originator: @originator
170
168
  )
171
169
 
@@ -215,18 +213,16 @@ module BSV
215
213
  # Create a spending transaction; use unlocking_script_length so the wallet
216
214
  # produces a signable transaction that can then be signed and broadcast.
217
215
  create_result = @wallet.create_action(
218
- {
219
- description: 'Spend certificate revelation token',
220
- input_beef: beef_bytes,
221
- inputs: [
222
- {
223
- input_description: 'Revelation token',
224
- outpoint: outpoint,
225
- unlocking_script_length: BSV::Script::PushDropTemplate::Unlocker::ESTIMATED_LENGTH
226
- }
227
- ],
228
- options: { randomize_outputs: false, no_send: true }
229
- },
216
+ description: 'Spend certificate revelation token',
217
+ input_beef: beef_bytes,
218
+ inputs: [
219
+ {
220
+ input_description: 'Revelation token',
221
+ outpoint: outpoint,
222
+ unlocking_script_length: BSV::Script::PushDropTemplate::Unlocker::ESTIMATED_LENGTH
223
+ }
224
+ ],
225
+ options: { randomize_outputs: false, no_send: true },
230
226
  originator: @originator
231
227
  )
232
228
 
@@ -248,11 +244,9 @@ module BSV
248
244
  unlocking_script = unlocker.sign(partial_tx, spending_input_idx)
249
245
 
250
246
  sign_result = @wallet.sign_action(
251
- {
252
- reference: signable[:reference],
253
- spends: { spending_input_idx => { unlocking_script: unlocking_script.to_hex } },
254
- options: { no_send: true }
255
- },
247
+ reference: signable[:reference],
248
+ spends: { spending_input_idx => { unlocking_script: unlocking_script.to_hex } },
249
+ options: { no_send: true },
256
250
  originator: @originator
257
251
  )
258
252
 
@@ -343,7 +337,7 @@ module BSV
343
337
  #
344
338
  # @return [Symbol] :mainnet or :testnet
345
339
  def wallet_network
346
- result = @wallet.get_network({}, originator: @originator)
340
+ result = @wallet.get_network(originator: @originator)
347
341
  net_str = result[:network] || result['network'] || 'mainnet'
348
342
  net_str.to_sym
349
343
  end
@@ -91,8 +91,16 @@ module BSV
91
91
  private_key = BSV::Primitives::PrivateKey.from_wif(wif)
92
92
  sender_address = private_key.public_key.address(network: net_sym)
93
93
 
94
- woc = BSV::Network::WhatsOnChain.new(network: net_sym)
95
- all_utxos = woc.fetch_utxos(sender_address)
94
+ woc = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
95
+ utxo_result = woc.call(:get_utxos_all, sender_address)
96
+ return Helpers.error_response("UTXO fetch failed: #{utxo_result.message}") unless utxo_result.success?
97
+
98
+ all_utxos = utxo_result.data.map do |entry|
99
+ BSV::Network::UTXO.new(
100
+ tx_hash: entry[:tx_hash], tx_pos: entry[:tx_pos],
101
+ satoshis: entry[:satoshis], height: entry[:height]
102
+ )
103
+ end
96
104
 
97
105
  return Helpers.error_response('No UTXOs found for sender address — the address may have no funds') if all_utxos.empty?
98
106
 
@@ -104,11 +112,12 @@ module BSV
104
112
  tx = build_transaction(selected, satoshis, to_address, sender_address, private_key)
105
113
 
106
114
  arc = build_arc(net_sym, server_context)
107
- broadcast_result = arc.broadcast(tx)
115
+ arc_result = arc.call(:broadcast, tx)
116
+ return Helpers.error_response("Broadcast failed: #{arc_result.message}") unless arc_result.success?
108
117
 
109
118
  result = {
110
- txid: broadcast_result.txid,
111
- tx_status: broadcast_result.tx_status,
119
+ txid: arc_result.data[:txid],
120
+ tx_status: arc_result.data[:tx_status],
112
121
  hex: tx.to_hex
113
122
  }
114
123
 
@@ -118,10 +127,6 @@ module BSV
118
127
  )
119
128
  rescue ArgumentError => e
120
129
  Helpers.error_response(e.message)
121
- rescue BSV::Network::ChainProviderError => e
122
- Helpers.error_response("UTXO fetch failed: #{e.message}")
123
- rescue BSV::Network::BroadcastError => e
124
- Helpers.error_response("Broadcast failed: #{e.message}")
125
130
  end
126
131
 
127
132
  # Select UTXOs greedily until the total meets or exceeds the target.
@@ -196,13 +201,14 @@ module BSV
196
201
  end
197
202
  private_class_method :p2pkh_lock_for
198
203
 
199
- # Build an ARC broadcaster using server config when available.
204
+ # Build an ARC protocol instance using server config when available.
200
205
  # @api private
201
206
  def self.build_arc(net_sym, server_context)
202
207
  testnet = net_sym == :testnet
203
208
  opts = {}
204
209
  opts[:api_key] = server_context[:arc_api_key] if server_context.is_a?(Hash) && server_context[:arc_api_key]
205
- BSV::Network::ARC.default(testnet: testnet, **opts)
210
+ provider = BSV::Network::Providers::GorillaPool.default(testnet: testnet, **opts)
211
+ provider.protocol_for(:broadcast)
206
212
  end
207
213
  private_class_method :build_arc
208
214
  end
@@ -59,8 +59,22 @@ module BSV
59
59
  net_sym = Helpers.resolve_network_sym(network, server_context)
60
60
  address = resolve_address(address_or_wif, net_sym)
61
61
 
62
- woc = BSV::Network::WhatsOnChain.new(network: net_sym)
63
- utxos = woc.fetch_utxos(address)
62
+ provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
63
+ utxo_result = provider.call(:get_utxos_all, address)
64
+
65
+ unless utxo_result.success?
66
+ code = utxo_result.metadata[:status_code]
67
+ msg = utxo_result.message
68
+ msg = "#{msg} (HTTP #{code})" if code
69
+ return Helpers.error_response(msg)
70
+ end
71
+
72
+ utxos = utxo_result.data.map do |entry|
73
+ BSV::Network::UTXO.new(
74
+ tx_hash: entry[:tx_hash], tx_pos: entry[:tx_pos],
75
+ satoshis: entry[:satoshis], height: entry[:height]
76
+ )
77
+ end
64
78
 
65
79
  balance = utxos.sum(&:satoshis)
66
80
  result = {
@@ -77,8 +91,6 @@ module BSV
77
91
  )
78
92
  rescue ArgumentError => e
79
93
  Helpers.error_response(e.message)
80
- rescue BSV::Network::ChainProviderError => e
81
- Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
82
94
  end
83
95
 
84
96
  # Resolve a WIF key or address string to a P2PKH address.