bsv-sdk 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/lib/bsv/auth/auth_middleware.rb +6 -6
  4. data/lib/bsv/auth/certificate.rb +22 -18
  5. data/lib/bsv/auth/master_certificate.rb +5 -5
  6. data/lib/bsv/auth/nonce.rb +13 -13
  7. data/lib/bsv/auth/peer.rb +53 -53
  8. data/lib/bsv/auth/verifiable_certificate.rb +1 -1
  9. data/lib/bsv/identity/client.rb +27 -32
  10. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +18 -12
  11. data/lib/bsv/mcp/tools/check_balance.rb +16 -4
  12. data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
  13. data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
  14. data/lib/bsv/mcp/tools/helpers.rb +2 -2
  15. data/lib/bsv/network/arc.rb +13 -153
  16. data/lib/bsv/network/broadcast_error.rb +1 -0
  17. data/lib/bsv/network/broadcast_response.rb +1 -0
  18. data/lib/bsv/network/protocols/arc.rb +4 -3
  19. data/lib/bsv/network/protocols/taal_binary.rb +1 -0
  20. data/lib/bsv/network/protocols/woc_rest.rb +2 -1
  21. data/lib/bsv/network/whats_on_chain.rb +13 -107
  22. data/lib/bsv/overlay/admin_token_template.rb +4 -4
  23. data/lib/bsv/overlay/lookup_resolver.rb +1 -0
  24. data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
  25. data/lib/bsv/overlay/types.rb +1 -0
  26. data/lib/bsv/primitives/hex.rb +64 -0
  27. data/lib/bsv/registry/client.rb +26 -28
  28. data/lib/bsv/registry/types.rb +1 -0
  29. data/lib/bsv/script/interpreter/interpreter.rb +7 -0
  30. data/lib/bsv/script/interpreter/operations/crypto.rb +7 -1
  31. data/lib/bsv/script/push_drop_template.rb +4 -4
  32. data/lib/bsv/transaction/beef.rb +122 -83
  33. data/lib/bsv/transaction/merkle_path.rb +54 -38
  34. data/lib/bsv/transaction/transaction.rb +81 -30
  35. data/lib/bsv/transaction/transaction_input.rb +23 -18
  36. data/lib/bsv/version.rb +1 -1
  37. data/lib/bsv/wallet/errors.rb +47 -0
  38. data/lib/bsv/wallet/interface/brc100.rb +270 -0
  39. data/lib/bsv/wallet/interface.rb +9 -0
  40. data/lib/bsv/wallet/proto_wallet/key_deriver.rb +152 -0
  41. data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
  42. data/lib/bsv/wallet/proto_wallet.rb +327 -0
  43. data/lib/bsv/wallet.rb +16 -0
  44. data/lib/bsv-sdk.rb +18 -1
  45. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27489947d80a203e32fad08b53cfee4edabca7df61f244dff2ca12c5fa45e261
4
- data.tar.gz: d3bd5ead19a4fa67f4111e9bd3a203a1cad3c083ad4b8e174ee114b5b7fdb4bd
3
+ metadata.gz: 0c528b336af4735e247429423231a5c6a0dd681718e94d7a3d9598b65b75a6a0
4
+ data.tar.gz: 21efe7a43b3ba356953af1668cb35227d9c72bd3b17a1f08a54fac2f55f0d577
5
5
  SHA512:
6
- metadata.gz: 5ebdd5176602d584debb675d470dc31079cb16a29bb858c806123227efe03783c7c7f77eccf38174e254b7579fae9bad71d17c9e71791c21a694653647e15ad6
7
- data.tar.gz: cedddebaec0eeb9c2fa037af4227ad7307492146c2b4dc18d3a7f4dfc7f3179c20428245424d0a8542db97bbad08831a334d7821f17fd3c05264d38289271552
6
+ metadata.gz: 94b507d613413fbb96ab08f72d5db324ea33626f0f32af34f248b7d3133bd847aff1527de7ea1e68c1a1cbc1f5140f7654d4be99726348d85b852483ea00519a
7
+ data.tar.gz: 71c1fd8a86414cce69c1c4ec4fe1df52bab863c624ba70b806f37e2e016968eca9ac0244c42b0d52407c1817f56d9133403d3aec04a41bb39766776eb4b8c108
data/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@ 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.17.0 — 2026-05-02
9
+
10
+ ### Breaking Changes
11
+ - Renamed `TransactionInput#prev_tx_id` to `#prev_wtxid` — all call sites must update (#678)
12
+ - Renamed MerklePath parameters from `txid_hex` to `dtxid_hex` throughout (#681)
13
+
14
+ ### Added
15
+ - `Transaction#wtxid` — wire-order transaction ID (raw SHA-256d bytes) (#673)
16
+ - `Transaction#dtxid` / `#dtxid_hex` — display-order hex aliases (#680)
17
+ - `TransactionInput#dtxid_hex` — display-order hex of the referenced transaction (#678)
18
+ - `TransactionInput.wtxid_from_hex` — convert display-order hex to wire-order bytes (#678)
19
+ - `Hex.validate_wtxid!` / `Hex.validate_dtxid_hex!` — runtime validation for wire/display txid formats (#685)
20
+ - `Hex.validate_hash32!` — general-purpose 32-byte binary hash validator (#686)
21
+ - `BSV.logger` — opt-in debug instrumentation with zero overhead when unused (#686)
22
+ - Debug logging at txid conversions, sighash preimage, script interpreter, ARC broadcast, BEEF ancestry, and ProtoWallet key derivation (#686, #688)
23
+
24
+ ### Changed
25
+ - All internal txid variables renamed to `wtxid` (wire-order) convention (#679)
26
+ - BEEF internals enforce wire-order throughout — no byte-reversals inside the bundle (#675)
27
+ - MerklePath `compute_root_hex` and `from_tsc` now validate hex input (#686)
28
+
29
+ ### Fixed
30
+ - `BeefTx#dtxid` now returns hex string (was returning binary) (#677)
31
+ - Certificate field naming aligned with BRC-52 convention (#677)
32
+
33
+ ## 0.16.0 — 2026-05-01
34
+
35
+ ### Breaking Changes
36
+ - Removed `bsv-wallet` and `bsv-wallet-postgres` gems from the monorepo (#662)
37
+ - Unified `BSV::Wallet` namespace with canonical BRC-100 interface, error reconciliation, and idiomatic ProtoWallet (#664)
38
+ - Hard-deprecated `Network::ARC` and `Network::WhatsOnChain` facades (#659)
39
+
40
+ ### Added
41
+ - `BSV::Wallet::ProtoWallet` for SDK-native cryptographic operations (signing, encryption, HMAC, key derivation) (#662)
42
+
43
+ ### Fixed
44
+ - Guard nil `status_code` in MCP error messages (#659)
45
+
8
46
  ## 0.15.0 — 2026-04-29
9
47
 
10
48
  ### Added
@@ -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*')
@@ -81,6 +81,9 @@ module BSV
81
81
  buf << [@subject].pack('H*')
82
82
  buf << [@certifier].pack('H*')
83
83
 
84
+ # Certificate binary format: revocation outpoint txid stored in display byte
85
+ # order (matching TS and Go SDKs). Go encodes via WriteBytesReverse(wire_order),
86
+ # TS encodes via toArray(display_hex) — both produce identical display-order bytes.
84
87
  txid_hex, output_index_str = @revocation_outpoint.to_s.split('.', 2)
85
88
  buf << [txid_hex].pack('H*')
86
89
  buf << BSV::Transaction::VarInt.encode(output_index_str.to_i)
@@ -124,7 +127,8 @@ module BSV
124
127
  certifier_bytes = data.byteslice(pos, 33)
125
128
  pos += 33
126
129
 
127
- txid_bytes = data.byteslice(pos, 32)
130
+ # Outpoint txid bytes — stored in display byte order (matching TS and Go SDKs).
131
+ outpoint_txid = data.byteslice(pos, 32)
128
132
  pos += 32
129
133
  output_index, vi_len = BSV::Transaction::VarInt.decode(data, pos)
130
134
  pos += vi_len
@@ -158,7 +162,7 @@ module BSV
158
162
  serial_number: Base64.strict_encode64(serial_bytes),
159
163
  subject: subject_bytes.unpack1('H*'),
160
164
  certifier: certifier_bytes.unpack1('H*'),
161
- revocation_outpoint: "#{txid_bytes.unpack1('H*')}.#{output_index}",
165
+ revocation_outpoint: "#{outpoint_txid.unpack1('H*')}.#{output_index}",
162
166
  fields: fields,
163
167
  signature: signature
164
168
  )
@@ -166,27 +170,27 @@ module BSV
166
170
 
167
171
  # Verify the certificate's signature.
168
172
  #
169
- # Uses a fresh +'anyone'+ Client as the verifier, which matches the
173
+ # Uses a fresh +'anyone'+ ProtoWallet as the verifier, which matches the
170
174
  # TS SDK behaviour. If no signature is present, raises +ArgumentError+.
171
175
  #
172
176
  # @param verifier_wallet [#verify_signature, nil] wallet to verify with;
173
- # defaults to +BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new)+
177
+ # defaults to +BSV::Wallet::ProtoWallet.new('anyone')+
174
178
  # @return [Boolean] +true+ if the signature is valid
175
179
  # @raise [ArgumentError] if the certificate has no signature
176
180
  def verify(verifier_wallet = nil)
177
181
  raise ArgumentError, 'certificate has no signature to verify' if @signature.nil? || @signature.empty?
178
182
 
179
- verifier_wallet ||= BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new, allow_memory_store: true)
183
+ verifier_wallet ||= BSV::Wallet::ProtoWallet.new('anyone')
180
184
  preimage = to_binary(include_signature: false)
181
185
  sig_bytes = [@signature].pack('H*').unpack('C*')
182
186
 
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
- })
187
+ result = verifier_wallet.verify_signature(
188
+ data: preimage.unpack('C*'),
189
+ signature: sig_bytes,
190
+ protocol_id: CERT_SIG_PROTOCOL,
191
+ key_id: "#{@type} #{@serial_number}",
192
+ counterparty: @certifier
193
+ )
190
194
 
191
195
  result.is_a?(Hash) && result[:valid] == true
192
196
  rescue BSV::Wallet::InvalidSignatureError
@@ -203,14 +207,14 @@ module BSV
203
207
  def sign(certifier_wallet)
204
208
  raise ArgumentError, "certificate has already been signed: #{@signature}" if @signature && !@signature.empty?
205
209
 
206
- @certifier = certifier_wallet.get_public_key({ identity_key: true })[:public_key]
210
+ @certifier = certifier_wallet.get_public_key(identity_key: true)[:public_key]
207
211
 
208
212
  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
- })
213
+ result = certifier_wallet.create_signature(
214
+ data: preimage.unpack('C*'),
215
+ protocol_id: CERT_SIG_PROTOCOL,
216
+ key_id: "#{@type} #{@serial_number}"
217
+ )
214
218
  @signature = result[:signature].pack('C*').unpack1('H*')
215
219
  end
216
220
 
@@ -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
 
@@ -209,24 +207,23 @@ module BSV
209
207
  raise 'Revoke failed: no transaction found in BEEF' unless tx
210
208
  raise 'Revoke failed: outputIndex out of range' if output_idx >= tx.outputs.length
211
209
 
210
+ # Overlay API boundary: outpoint uses display-order hex txid as per overlay convention
212
211
  txid = tx.txid_hex
213
212
  outpoint = "#{txid}.#{output_idx}"
214
213
 
215
214
  # Create a spending transaction; use unlocking_script_length so the wallet
216
215
  # produces a signable transaction that can then be signed and broadcast.
217
216
  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
- },
217
+ description: 'Spend certificate revelation token',
218
+ input_beef: beef_bytes,
219
+ inputs: [
220
+ {
221
+ input_description: 'Revelation token',
222
+ outpoint: outpoint,
223
+ unlocking_script_length: BSV::Script::PushDropTemplate::Unlocker::ESTIMATED_LENGTH
224
+ }
225
+ ],
226
+ options: { randomize_outputs: false, no_send: true },
230
227
  originator: @originator
231
228
  )
232
229
 
@@ -248,11 +245,9 @@ module BSV
248
245
  unlocking_script = unlocker.sign(partial_tx, spending_input_idx)
249
246
 
250
247
  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
- },
248
+ reference: signable[:reference],
249
+ spends: { spending_input_idx => { unlocking_script: unlocking_script.to_hex } },
250
+ options: { no_send: true },
256
251
  originator: @originator
257
252
  )
258
253
 
@@ -343,7 +338,7 @@ module BSV
343
338
  #
344
339
  # @return [Symbol] :mainnet or :testnet
345
340
  def wallet_network
346
- result = @wallet.get_network({}, originator: @originator)
341
+ result = @wallet.get_network(originator: @originator)
347
342
  net_str = result[:network] || result['network'] || 'mainnet'
348
343
  net_str.to_sym
349
344
  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], # MCP tool boundary: display-order hex from ARC response
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.
@@ -149,7 +154,7 @@ module BSV
149
154
  selected_utxos.each do |utxo|
150
155
  locking_script = p2pkh_lock_for(sender_address)
151
156
  input = BSV::Transaction::TransactionInput.new(
152
- prev_tx_id: BSV::Transaction::TransactionInput.txid_from_hex(utxo.tx_hash),
157
+ prev_wtxid: BSV::Transaction::TransactionInput.wtxid_from_hex(utxo.tx_hash),
153
158
  prev_tx_out_index: utxo.tx_pos
154
159
  )
155
160
  input.source_satoshis = utxo.satoshis
@@ -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.