bsv-sdk 0.15.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/bsv/auth/auth_middleware.rb +6 -6
- data/lib/bsv/auth/certificate.rb +16 -16
- data/lib/bsv/auth/master_certificate.rb +5 -5
- data/lib/bsv/auth/nonce.rb +13 -13
- data/lib/bsv/auth/peer.rb +53 -53
- data/lib/bsv/auth/verifiable_certificate.rb +1 -1
- data/lib/bsv/identity/client.rb +26 -32
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +17 -11
- data/lib/bsv/mcp/tools/check_balance.rb +16 -4
- data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
- data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
- data/lib/bsv/network/arc.rb +13 -153
- data/lib/bsv/network/whats_on_chain.rb +13 -107
- data/lib/bsv/overlay/admin_token_template.rb +4 -4
- data/lib/bsv/registry/client.rb +23 -27
- data/lib/bsv/script/push_drop_template.rb +4 -4
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +47 -0
- data/lib/bsv/wallet/interface/brc100.rb +267 -0
- data/lib/bsv/wallet/interface.rb +9 -0
- data/lib/bsv/wallet/proto_wallet/key_deriver.rb +150 -0
- data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
- data/lib/bsv/wallet/proto_wallet.rb +321 -0
- data/lib/bsv/wallet.rb +16 -0
- data/lib/bsv-sdk.rb +4 -1
- metadata +22 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a76170f9d5dcdfc76945c20c541b556a02f3fb5a7214a8d6f777eb589f048461
|
|
4
|
+
data.tar.gz: 57176276d41d430d654682dd22aef0f93bee9e2e3c57d4fb76d435dbd8c3e705
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb7257bda0b14c14ab55d8da3a19fdc27a8eca113cfeb857487079d4e0f042ebbaef184e81382d50aed7a9efd69dc4d2cb2d77594b6e1ae5f3f6d047c01d2ab9
|
|
7
|
+
data.tar.gz: 20f9e8fe53476ddd87900d1b58d00811da403703d5ae8cf6dc0f6d4a4836ab8a12e42ddce88c7323bd281e710770f4b7b6805cb842ab4f164c4b17559afa46e7
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ 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
|
+
|
|
8
21
|
## 0.15.0 — 2026-04-29
|
|
9
22
|
|
|
10
23
|
### 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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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*')
|
data/lib/bsv/auth/certificate.rb
CHANGED
|
@@ -166,27 +166,27 @@ module BSV
|
|
|
166
166
|
|
|
167
167
|
# Verify the certificate's signature.
|
|
168
168
|
#
|
|
169
|
-
# Uses a fresh +'anyone'+
|
|
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::
|
|
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::
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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(
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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(
|
|
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(
|
|
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))
|
data/lib/bsv/auth/nonce.rb
CHANGED
|
@@ -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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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(
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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]))
|
data/lib/bsv/identity/client.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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(
|
|
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.
|
|
95
|
-
|
|
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
|
-
|
|
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:
|
|
111
|
-
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
|
|
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::
|
|
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
|
-
|
|
63
|
-
|
|
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.
|
|
@@ -49,8 +49,17 @@ module BSV
|
|
|
49
49
|
|
|
50
50
|
def self.call(txid:, network: nil, server_context: nil)
|
|
51
51
|
net_sym = Helpers.resolve_network_sym(network, server_context)
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
53
|
+
fetch_result = provider.call(:get_tx, txid)
|
|
54
|
+
|
|
55
|
+
unless fetch_result.success?
|
|
56
|
+
code = fetch_result.metadata[:status_code]
|
|
57
|
+
msg = fetch_result.message
|
|
58
|
+
msg = "#{msg} (HTTP #{code})" if code
|
|
59
|
+
return Helpers.error_response(msg)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
tx = BSV::Transaction::Transaction.from_hex(fetch_result.data)
|
|
54
63
|
|
|
55
64
|
result = {
|
|
56
65
|
hex: tx.to_hex,
|
|
@@ -61,8 +70,6 @@ module BSV
|
|
|
61
70
|
[::MCP::Content::Text.new(result.to_json)],
|
|
62
71
|
structured_content: result
|
|
63
72
|
)
|
|
64
|
-
rescue BSV::Network::ChainProviderError => e
|
|
65
|
-
Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
|
|
66
73
|
rescue ArgumentError => e
|
|
67
74
|
Helpers.error_response(e.message)
|
|
68
75
|
end
|
|
@@ -50,8 +50,22 @@ module BSV
|
|
|
50
50
|
|
|
51
51
|
def self.call(address:, network: nil, server_context: nil)
|
|
52
52
|
net_sym = Helpers.resolve_network_sym(network, server_context)
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
54
|
+
utxo_result = provider.call(:get_utxos_all, address)
|
|
55
|
+
|
|
56
|
+
unless utxo_result.success?
|
|
57
|
+
code = utxo_result.metadata[:status_code]
|
|
58
|
+
msg = utxo_result.message
|
|
59
|
+
msg = "#{msg} (HTTP #{code})" if code
|
|
60
|
+
return Helpers.error_response(msg)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
utxos = utxo_result.data.map do |entry|
|
|
64
|
+
BSV::Network::UTXO.new(
|
|
65
|
+
tx_hash: entry[:tx_hash], tx_pos: entry[:tx_pos],
|
|
66
|
+
satoshis: entry[:satoshis], height: entry[:height]
|
|
67
|
+
)
|
|
68
|
+
end
|
|
55
69
|
|
|
56
70
|
result = {
|
|
57
71
|
address: address,
|
|
@@ -63,8 +77,6 @@ module BSV
|
|
|
63
77
|
[::MCP::Content::Text.new(result.to_json)],
|
|
64
78
|
structured_content: result
|
|
65
79
|
)
|
|
66
|
-
rescue BSV::Network::ChainProviderError => e
|
|
67
|
-
Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
|
|
68
80
|
end
|
|
69
81
|
end
|
|
70
82
|
end
|