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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -0
  3. data/README.md +2 -2
  4. data/lib/bsv/auth/transport.rb +1 -1
  5. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
  6. data/lib/bsv/network/protocol.rb +4 -5
  7. data/lib/bsv/network/protocols/arc.rb +4 -30
  8. data/lib/bsv/network/protocols/arcade.rb +163 -0
  9. data/lib/bsv/network/protocols/chaintracks.rb +6 -3
  10. data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
  11. data/lib/bsv/network/protocols.rb +1 -0
  12. data/lib/bsv/network/provider.rb +7 -9
  13. data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
  14. data/lib/bsv/network/util.rb +44 -0
  15. data/lib/bsv/network.rb +1 -0
  16. data/lib/bsv/overlay/lookup_resolver.rb +0 -1
  17. data/lib/bsv/overlay/topic_broadcaster.rb +0 -1
  18. data/lib/bsv/primitives/curve.rb +1 -11
  19. data/lib/bsv/primitives/ecies.rb +1 -8
  20. data/lib/bsv/primitives/hex.rb +1 -1
  21. data/lib/bsv/script/script.rb +1 -1
  22. data/lib/bsv/transaction/beef.rb +0 -2
  23. data/lib/bsv/transaction/chain_tracker.rb +74 -13
  24. data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +3 -3
  25. data/lib/bsv/transaction/chain_trackers.rb +0 -10
  26. data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
  27. data/lib/bsv/transaction/merkle_path.rb +0 -2
  28. data/lib/bsv/version.rb +1 -1
  29. data/lib/bsv/wallet/errors.rb +65 -21
  30. data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
  31. data/lib/bsv/wallet/proto_wallet.rb +15 -8
  32. data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
  33. data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
  34. data/lib/bsv/wallet/serializer/certificate.rb +184 -0
  35. data/lib/bsv/wallet/serializer/common.rb +207 -0
  36. data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
  37. data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
  38. data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
  39. data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
  40. data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
  41. data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
  42. data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
  43. data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
  44. data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
  45. data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
  46. data/lib/bsv/wallet/serializer/get_height.rb +46 -0
  47. data/lib/bsv/wallet/serializer/get_network.rb +65 -0
  48. data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
  49. data/lib/bsv/wallet/serializer/get_version.rb +44 -0
  50. data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
  51. data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
  52. data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
  53. data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
  54. data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
  55. data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
  56. data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
  57. data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
  58. data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
  59. data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
  60. data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
  61. data/lib/bsv/wallet/serializer/status.rb +85 -0
  62. data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
  63. data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
  64. data/lib/bsv/wallet/serializer.rb +180 -0
  65. data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
  66. data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
  67. data/lib/bsv/wallet/wallet_wire.rb +20 -0
  68. data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
  69. data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
  70. data/lib/bsv/wallet/wire/calls.rb +79 -0
  71. data/lib/bsv/wallet/wire/frame.rb +181 -0
  72. data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
  73. data/lib/bsv/wallet/wire/validation.rb +213 -0
  74. data/lib/bsv/wallet/wire.rb +13 -0
  75. data/lib/bsv/wallet.rb +17 -0
  76. metadata +47 -3
  77. data/lib/bsv/transaction/chain_trackers/chaintracks.rb +0 -83
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Serializer
6
+ # BRC-103 wire codec for the +verify_hmac+ call (call byte 14).
7
+ #
8
+ # Port of go-sdk/wallet/serializer/verify_hmac.go.
9
+ module VerifyHmac
10
+ HMAC_SIZE = 32
11
+
12
+ # Args wire layout:
13
+ # [key-related params]
14
+ # [32 bytes: HMAC]
15
+ # [VarInt data_len][data bytes]
16
+ # [optional_bool seek_permission]
17
+ module Args
18
+ module_function
19
+
20
+ def serialize(args)
21
+ hmac = Common.to_binary(args[:hmac])
22
+ raise BSV::Wallet::InvalidParameterError.new('hmac', "exactly #{HMAC_SIZE} bytes") unless hmac.bytesize == HMAC_SIZE
23
+
24
+ w = BSV::Wallet::Wire::Writer.new
25
+ Common.write_key_related_params(
26
+ w,
27
+ protocol_id: args[:protocol_id],
28
+ key_id: args[:key_id],
29
+ counterparty: args[:counterparty],
30
+ privileged: args[:privileged],
31
+ privileged_reason: args[:privileged_reason]
32
+ )
33
+ w.write_bytes(hmac)
34
+ data = Common.to_binary(args[:data])
35
+ w.write_varint(data.bytesize)
36
+ w.write_bytes(data)
37
+ w.write_optional_bool(args[:seek_permission])
38
+ w.buf
39
+ end
40
+
41
+ def deserialize(bytes)
42
+ r = BSV::Wallet::Wire::Reader.new(bytes)
43
+ params = Common.read_key_related_params(r)
44
+ hmac = r.read_bytes(HMAC_SIZE)
45
+ data_len = r.read_varint
46
+ data = r.read_bytes(data_len)
47
+ seek_permission = r.read_optional_bool
48
+ params.merge(hmac: hmac, data: data, seek_permission: seek_permission)
49
+ end
50
+ end
51
+
52
+ # Result wire layout: empty — success implies valid.
53
+ module Result
54
+ module_function
55
+
56
+ def serialize(_result)
57
+ ''.b
58
+ end
59
+
60
+ def deserialize(_bytes)
61
+ { valid: true }
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Serializer
6
+ # BRC-103 wire codec for the +verify_signature+ call (call byte 16).
7
+ #
8
+ # Port of go-sdk/wallet/serializer/verify_signature.go.
9
+ module VerifySignature
10
+ HASH_SIZE = 32
11
+
12
+ # Args wire layout:
13
+ # [key-related params]
14
+ # [optional_bool for_self]
15
+ # [VarInt sig_len][DER signature bytes]
16
+ # [1 byte: data-type flag — 1=data, 2=hash_to_directly_verify]
17
+ # If flag=1: [VarInt data_len][data bytes]
18
+ # If flag=2: [32 bytes: hash]
19
+ # [optional_bool seek_permission]
20
+ module Args
21
+ module_function
22
+
23
+ def serialize(args)
24
+ data = args[:data] && Common.to_binary(args[:data])
25
+ hash = args[:hash_to_directly_verify] && Common.to_binary(args[:hash_to_directly_verify])
26
+ sig = args[:signature] && Common.to_binary(args[:signature])
27
+
28
+ if data && hash
29
+ raise BSV::Wallet::InvalidParameterError.new(
30
+ 'data and hash_to_directly_verify',
31
+ 'not both provided — supply one or the other'
32
+ )
33
+ end
34
+ raise BSV::Wallet::InvalidParameterError.new('data or hash_to_directly_verify', 'present') unless data || hash
35
+ raise BSV::Wallet::InvalidParameterError.new('signature', 'present') unless sig
36
+
37
+ w = BSV::Wallet::Wire::Writer.new
38
+ Common.write_key_related_params(
39
+ w,
40
+ protocol_id: args[:protocol_id],
41
+ key_id: args[:key_id],
42
+ counterparty: args[:counterparty],
43
+ privileged: args[:privileged],
44
+ privileged_reason: args[:privileged_reason]
45
+ )
46
+
47
+ w.write_optional_bool(args[:for_self])
48
+
49
+ w.write_varint(sig.bytesize)
50
+ w.write_bytes(sig)
51
+
52
+ if data
53
+ w.write_byte(1)
54
+ w.write_varint(data.bytesize)
55
+ w.write_bytes(data)
56
+ else
57
+ w.write_byte(2)
58
+ w.write_bytes(hash)
59
+ end
60
+
61
+ w.write_optional_bool(args[:seek_permission])
62
+ w.buf
63
+ end
64
+
65
+ def deserialize(bytes)
66
+ r = BSV::Wallet::Wire::Reader.new(bytes)
67
+ params = Common.read_key_related_params(r)
68
+ for_self = r.read_optional_bool
69
+ sig_len = r.read_varint
70
+ signature = r.read_bytes(sig_len)
71
+ flag = r.read_byte
72
+ payload = case flag
73
+ when 1
74
+ len = r.read_varint
75
+ { data: r.read_bytes(len) }
76
+ when 2
77
+ { hash_to_directly_verify: r.read_bytes(HASH_SIZE) }
78
+ else
79
+ raise ArgumentError, "invalid data-type flag: #{flag}"
80
+ end
81
+ seek_permission = r.read_optional_bool
82
+ params.merge(for_self: for_self, signature: signature).merge(payload).merge(seek_permission: seek_permission)
83
+ end
84
+ end
85
+
86
+ # Result wire layout: empty — success implies valid.
87
+ module Result
88
+ module_function
89
+
90
+ def serialize(_result)
91
+ ''.b
92
+ end
93
+
94
+ def deserialize(_bytes)
95
+ { valid: true }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Registry of per-call BRC-103 binary serialisers.
6
+ #
7
+ # Each table maps a Wire::Calls constant to the module_function on the
8
+ # matching Serializer sub-module. Other developer agents populate their
9
+ # tiers in parallel — add one entry per call, one line each.
10
+ module Serializer
11
+ autoload :Common, 'bsv/wallet/serializer/common'
12
+
13
+ # Crypto tier (call bytes 8-16)
14
+ autoload :GetPublicKey, 'bsv/wallet/serializer/get_public_key'
15
+ autoload :RevealCounterpartyKeyLinkage, 'bsv/wallet/serializer/reveal_counterparty_key_linkage'
16
+ autoload :RevealSpecificKeyLinkage, 'bsv/wallet/serializer/reveal_specific_key_linkage'
17
+ autoload :Encrypt, 'bsv/wallet/serializer/encrypt'
18
+ autoload :Decrypt, 'bsv/wallet/serializer/decrypt'
19
+ autoload :CreateHmac, 'bsv/wallet/serializer/create_hmac'
20
+ autoload :VerifyHmac, 'bsv/wallet/serializer/verify_hmac'
21
+ autoload :CreateSignature, 'bsv/wallet/serializer/create_signature'
22
+ autoload :VerifySignature, 'bsv/wallet/serializer/verify_signature'
23
+
24
+ # Trivial tier (call bytes 23-28)
25
+ autoload :GetNetwork, 'bsv/wallet/serializer/get_network'
26
+ autoload :GetVersion, 'bsv/wallet/serializer/get_version'
27
+ autoload :GetHeight, 'bsv/wallet/serializer/get_height'
28
+ autoload :GetHeaderForHeight, 'bsv/wallet/serializer/get_header_for_height'
29
+ autoload :IsAuthenticated, 'bsv/wallet/serializer/status'
30
+ autoload :WaitForAuthentication, 'bsv/wallet/serializer/status'
31
+ autoload :CreateActionArgs, 'bsv/wallet/serializer/create_action_args'
32
+ autoload :CreateActionResult, 'bsv/wallet/serializer/create_action_result'
33
+ autoload :SignActionArgs, 'bsv/wallet/serializer/sign_action_args'
34
+ autoload :SignActionResult, 'bsv/wallet/serializer/sign_action_result'
35
+ autoload :AbortAction, 'bsv/wallet/serializer/abort_action'
36
+ autoload :InternalizeActionArgs, 'bsv/wallet/serializer/internalize_action'
37
+ autoload :InternalizeActionResult, 'bsv/wallet/serializer/internalize_action'
38
+ autoload :ListActionsArgs, 'bsv/wallet/serializer/list_actions'
39
+ autoload :ListActionsResult, 'bsv/wallet/serializer/list_actions'
40
+ autoload :Certificate, 'bsv/wallet/serializer/certificate'
41
+ autoload :DiscoverCertificatesResult, 'bsv/wallet/serializer/discover_certificates_result'
42
+ autoload :ListOutputs, 'bsv/wallet/serializer/list_outputs'
43
+ autoload :RelinquishOutput, 'bsv/wallet/serializer/relinquish_output'
44
+ autoload :AcquireCertificate, 'bsv/wallet/serializer/acquire_certificate'
45
+ autoload :ListCertificates, 'bsv/wallet/serializer/list_certificates'
46
+ autoload :ProveCertificate, 'bsv/wallet/serializer/prove_certificate'
47
+ autoload :RelinquishCertificate, 'bsv/wallet/serializer/relinquish_certificate'
48
+ autoload :DiscoverByIdentityKey, 'bsv/wallet/serializer/discover_by_identity_key'
49
+ autoload :DiscoverByAttributes, 'bsv/wallet/serializer/discover_by_attributes'
50
+
51
+ # Client-side: serialise outgoing args for each call.
52
+ SERIALIZE_ARGS = {
53
+ Wire::Calls::GET_PUBLIC_KEY => GetPublicKey::Args.method(:serialize),
54
+ Wire::Calls::REVEAL_COUNTERPARTY_KEY_LINKAGE => RevealCounterpartyKeyLinkage::Args.method(:serialize),
55
+ Wire::Calls::REVEAL_SPECIFIC_KEY_LINKAGE => RevealSpecificKeyLinkage::Args.method(:serialize),
56
+ Wire::Calls::ENCRYPT => Encrypt::Args.method(:serialize),
57
+ Wire::Calls::DECRYPT => Decrypt::Args.method(:serialize),
58
+ Wire::Calls::CREATE_HMAC => CreateHmac::Args.method(:serialize),
59
+ Wire::Calls::VERIFY_HMAC => VerifyHmac::Args.method(:serialize),
60
+ Wire::Calls::CREATE_SIGNATURE => CreateSignature::Args.method(:serialize),
61
+ Wire::Calls::VERIFY_SIGNATURE => VerifySignature::Args.method(:serialize),
62
+ Wire::Calls::GET_NETWORK => GetNetwork::Args.method(:serialize),
63
+ Wire::Calls::GET_VERSION => GetVersion::Args.method(:serialize),
64
+ Wire::Calls::GET_HEIGHT => GetHeight::Args.method(:serialize),
65
+ Wire::Calls::GET_HEADER_FOR_HEIGHT => GetHeaderForHeight::Args.method(:serialize),
66
+ Wire::Calls::IS_AUTHENTICATED => IsAuthenticated::Args.method(:serialize),
67
+ Wire::Calls::WAIT_FOR_AUTHENTICATION => WaitForAuthentication::Args.method(:serialize),
68
+ Wire::Calls::CREATE_ACTION => CreateActionArgs.method(:serialize),
69
+ Wire::Calls::SIGN_ACTION => SignActionArgs.method(:serialize),
70
+ Wire::Calls::ABORT_ACTION => AbortAction.method(:serialize_args),
71
+ Wire::Calls::INTERNALIZE_ACTION => InternalizeActionArgs.method(:serialize),
72
+ Wire::Calls::LIST_ACTIONS => ListActionsArgs.method(:serialize),
73
+ Wire::Calls::LIST_OUTPUTS => ListOutputs.method(:serialize_args),
74
+ Wire::Calls::RELINQUISH_OUTPUT => RelinquishOutput.method(:serialize_args),
75
+ Wire::Calls::ACQUIRE_CERTIFICATE => AcquireCertificate.method(:serialize_args),
76
+ Wire::Calls::LIST_CERTIFICATES => ListCertificates.method(:serialize_args),
77
+ Wire::Calls::PROVE_CERTIFICATE => ProveCertificate.method(:serialize_args),
78
+ Wire::Calls::RELINQUISH_CERTIFICATE => RelinquishCertificate.method(:serialize_args),
79
+ Wire::Calls::DISCOVER_BY_IDENTITY_KEY => DiscoverByIdentityKey.method(:serialize_args),
80
+ Wire::Calls::DISCOVER_BY_ATTRIBUTES => DiscoverByAttributes.method(:serialize_args)
81
+ }.freeze
82
+
83
+ # Client-side: deserialise incoming result payload for each call.
84
+ DESERIALIZE_RESULT = {
85
+ Wire::Calls::GET_PUBLIC_KEY => GetPublicKey::Result.method(:deserialize),
86
+ Wire::Calls::REVEAL_COUNTERPARTY_KEY_LINKAGE => RevealCounterpartyKeyLinkage::Result.method(:deserialize),
87
+ Wire::Calls::REVEAL_SPECIFIC_KEY_LINKAGE => RevealSpecificKeyLinkage::Result.method(:deserialize),
88
+ Wire::Calls::ENCRYPT => Encrypt::Result.method(:deserialize),
89
+ Wire::Calls::DECRYPT => Decrypt::Result.method(:deserialize),
90
+ Wire::Calls::CREATE_HMAC => CreateHmac::Result.method(:deserialize),
91
+ Wire::Calls::VERIFY_HMAC => VerifyHmac::Result.method(:deserialize),
92
+ Wire::Calls::CREATE_SIGNATURE => CreateSignature::Result.method(:deserialize),
93
+ Wire::Calls::VERIFY_SIGNATURE => VerifySignature::Result.method(:deserialize),
94
+ Wire::Calls::GET_NETWORK => GetNetwork::Result.method(:deserialize),
95
+ Wire::Calls::GET_VERSION => GetVersion::Result.method(:deserialize),
96
+ Wire::Calls::GET_HEIGHT => GetHeight::Result.method(:deserialize),
97
+ Wire::Calls::GET_HEADER_FOR_HEIGHT => GetHeaderForHeight::Result.method(:deserialize),
98
+ Wire::Calls::IS_AUTHENTICATED => IsAuthenticated::Result.method(:deserialize),
99
+ Wire::Calls::WAIT_FOR_AUTHENTICATION => WaitForAuthentication::Result.method(:deserialize),
100
+ Wire::Calls::CREATE_ACTION => CreateActionResult.method(:deserialize),
101
+ Wire::Calls::SIGN_ACTION => SignActionResult.method(:deserialize),
102
+ Wire::Calls::ABORT_ACTION => AbortAction.method(:deserialize_result),
103
+ Wire::Calls::INTERNALIZE_ACTION => InternalizeActionResult.method(:deserialize),
104
+ Wire::Calls::LIST_ACTIONS => ListActionsResult.method(:deserialize),
105
+ Wire::Calls::LIST_OUTPUTS => ListOutputs.method(:deserialize_result),
106
+ Wire::Calls::RELINQUISH_OUTPUT => RelinquishOutput.method(:deserialize_result),
107
+ Wire::Calls::ACQUIRE_CERTIFICATE => AcquireCertificate.method(:deserialize_result),
108
+ Wire::Calls::LIST_CERTIFICATES => ListCertificates.method(:deserialize_result),
109
+ Wire::Calls::PROVE_CERTIFICATE => ProveCertificate.method(:deserialize_result),
110
+ Wire::Calls::RELINQUISH_CERTIFICATE => RelinquishCertificate.method(:deserialize_result),
111
+ Wire::Calls::DISCOVER_BY_IDENTITY_KEY => DiscoverByIdentityKey.method(:deserialize_result),
112
+ Wire::Calls::DISCOVER_BY_ATTRIBUTES => DiscoverByAttributes.method(:deserialize_result)
113
+ }.freeze
114
+
115
+ # Server-side: deserialise incoming args payload for each call.
116
+ DESERIALIZE_ARGS = {
117
+ Wire::Calls::GET_PUBLIC_KEY => GetPublicKey::Args.method(:deserialize),
118
+ Wire::Calls::REVEAL_COUNTERPARTY_KEY_LINKAGE => RevealCounterpartyKeyLinkage::Args.method(:deserialize),
119
+ Wire::Calls::REVEAL_SPECIFIC_KEY_LINKAGE => RevealSpecificKeyLinkage::Args.method(:deserialize),
120
+ Wire::Calls::ENCRYPT => Encrypt::Args.method(:deserialize),
121
+ Wire::Calls::DECRYPT => Decrypt::Args.method(:deserialize),
122
+ Wire::Calls::CREATE_HMAC => CreateHmac::Args.method(:deserialize),
123
+ Wire::Calls::VERIFY_HMAC => VerifyHmac::Args.method(:deserialize),
124
+ Wire::Calls::CREATE_SIGNATURE => CreateSignature::Args.method(:deserialize),
125
+ Wire::Calls::VERIFY_SIGNATURE => VerifySignature::Args.method(:deserialize),
126
+ Wire::Calls::GET_NETWORK => GetNetwork::Args.method(:deserialize),
127
+ Wire::Calls::GET_VERSION => GetVersion::Args.method(:deserialize),
128
+ Wire::Calls::GET_HEIGHT => GetHeight::Args.method(:deserialize),
129
+ Wire::Calls::GET_HEADER_FOR_HEIGHT => GetHeaderForHeight::Args.method(:deserialize),
130
+ Wire::Calls::IS_AUTHENTICATED => IsAuthenticated::Args.method(:deserialize),
131
+ Wire::Calls::WAIT_FOR_AUTHENTICATION => WaitForAuthentication::Args.method(:deserialize),
132
+ Wire::Calls::CREATE_ACTION => CreateActionArgs.method(:deserialize),
133
+ Wire::Calls::SIGN_ACTION => SignActionArgs.method(:deserialize),
134
+ Wire::Calls::ABORT_ACTION => AbortAction.method(:deserialize_args),
135
+ Wire::Calls::INTERNALIZE_ACTION => InternalizeActionArgs.method(:deserialize),
136
+ Wire::Calls::LIST_ACTIONS => ListActionsArgs.method(:deserialize),
137
+ Wire::Calls::LIST_OUTPUTS => ListOutputs.method(:deserialize_args),
138
+ Wire::Calls::RELINQUISH_OUTPUT => RelinquishOutput.method(:deserialize_args),
139
+ Wire::Calls::ACQUIRE_CERTIFICATE => AcquireCertificate.method(:deserialize_args),
140
+ Wire::Calls::LIST_CERTIFICATES => ListCertificates.method(:deserialize_args),
141
+ Wire::Calls::PROVE_CERTIFICATE => ProveCertificate.method(:deserialize_args),
142
+ Wire::Calls::RELINQUISH_CERTIFICATE => RelinquishCertificate.method(:deserialize_args),
143
+ Wire::Calls::DISCOVER_BY_IDENTITY_KEY => DiscoverByIdentityKey.method(:deserialize_args),
144
+ Wire::Calls::DISCOVER_BY_ATTRIBUTES => DiscoverByAttributes.method(:deserialize_args)
145
+ }.freeze
146
+
147
+ # Server-side: serialise outgoing result for each call.
148
+ SERIALIZE_RESULT = {
149
+ Wire::Calls::GET_PUBLIC_KEY => GetPublicKey::Result.method(:serialize),
150
+ Wire::Calls::REVEAL_COUNTERPARTY_KEY_LINKAGE => RevealCounterpartyKeyLinkage::Result.method(:serialize),
151
+ Wire::Calls::REVEAL_SPECIFIC_KEY_LINKAGE => RevealSpecificKeyLinkage::Result.method(:serialize),
152
+ Wire::Calls::ENCRYPT => Encrypt::Result.method(:serialize),
153
+ Wire::Calls::DECRYPT => Decrypt::Result.method(:serialize),
154
+ Wire::Calls::CREATE_HMAC => CreateHmac::Result.method(:serialize),
155
+ Wire::Calls::VERIFY_HMAC => VerifyHmac::Result.method(:serialize),
156
+ Wire::Calls::CREATE_SIGNATURE => CreateSignature::Result.method(:serialize),
157
+ Wire::Calls::VERIFY_SIGNATURE => VerifySignature::Result.method(:serialize),
158
+ Wire::Calls::GET_NETWORK => GetNetwork::Result.method(:serialize),
159
+ Wire::Calls::GET_VERSION => GetVersion::Result.method(:serialize),
160
+ Wire::Calls::GET_HEIGHT => GetHeight::Result.method(:serialize),
161
+ Wire::Calls::GET_HEADER_FOR_HEIGHT => GetHeaderForHeight::Result.method(:serialize),
162
+ Wire::Calls::IS_AUTHENTICATED => IsAuthenticated::Result.method(:serialize),
163
+ Wire::Calls::WAIT_FOR_AUTHENTICATION => WaitForAuthentication::Result.method(:serialize),
164
+ Wire::Calls::CREATE_ACTION => CreateActionResult.method(:serialize),
165
+ Wire::Calls::SIGN_ACTION => SignActionResult.method(:serialize),
166
+ Wire::Calls::ABORT_ACTION => AbortAction.method(:serialize_result),
167
+ Wire::Calls::INTERNALIZE_ACTION => InternalizeActionResult.method(:serialize),
168
+ Wire::Calls::LIST_ACTIONS => ListActionsResult.method(:serialize),
169
+ Wire::Calls::LIST_OUTPUTS => ListOutputs.method(:serialize_result),
170
+ Wire::Calls::RELINQUISH_OUTPUT => RelinquishOutput.method(:serialize_result),
171
+ Wire::Calls::ACQUIRE_CERTIFICATE => AcquireCertificate.method(:serialize_result),
172
+ Wire::Calls::LIST_CERTIFICATES => ListCertificates.method(:serialize_result),
173
+ Wire::Calls::PROVE_CERTIFICATE => ProveCertificate.method(:serialize_result),
174
+ Wire::Calls::RELINQUISH_CERTIFICATE => RelinquishCertificate.method(:serialize_result),
175
+ Wire::Calls::DISCOVER_BY_IDENTITY_KEY => DiscoverByIdentityKey.method(:serialize_result),
176
+ Wire::Calls::DISCOVER_BY_ATTRIBUTES => DiscoverByAttributes.method(:serialize_result)
177
+ }.freeze
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module BSV
8
+ module Wallet
9
+ module Substrates
10
+ # JSON-RPC-style HTTP substrate implementing BRC-100.
11
+ #
12
+ # Dispatches all 28 BRC-100 methods as JSON POST requests to
13
+ # {base_url}/v1/wallet/{methodNameInCamelCase}. Request bodies are
14
+ # deep-converted from snake_case to camelCase via {BSV::WireFormat.to_wire};
15
+ # responses are deep-converted back via {BSV::WireFormat.from_wire}.
16
+ #
17
+ # This substrate does NOT use the BRC-103 binary frame codec — it speaks
18
+ # plain JSON over HTTP, matching the Go SDK HTTPWalletJSON implementation.
19
+ #
20
+ # @example
21
+ # client = BSV::Wallet::Substrates::HTTPWalletJSON.new(
22
+ # base_url: 'https://wallet.example.com',
23
+ # headers: { 'Authorization' => 'Bearer token' }
24
+ # )
25
+ # client.get_network # => { network: 'mainnet' }
26
+ class HTTPWalletJSON
27
+ include Interface::BRC100
28
+
29
+ # Maps each BRC-100 Ruby method name to its camelCase wire name.
30
+ # authenticated? maps to isAuthenticated — the BRC-100 wire name uses the
31
+ # is_ prefix convention; the Ruby predicate suffix is dropped for the lookup.
32
+ WIRE_METHOD_NAMES = Wire::Calls::CALL_TO_METHOD.each_with_object({}) do |(call_byte, ruby_method), map|
33
+ snake = call_byte == Wire::Calls::IS_AUTHENTICATED ? 'is_authenticated' : ruby_method.to_s
34
+ map[ruby_method] = BSV::WireFormat.snake_to_camel(snake)
35
+ end.freeze
36
+
37
+ # @param base_url [String] wallet base URL (e.g. 'https://wallet.example')
38
+ # @param http_client [#request, nil] injectable HTTP client for testing;
39
+ # must respond to +#request(uri, net_http_request)+. Defaults to +Net::HTTP+.
40
+ # @param headers [Hash] additional headers merged into every request
41
+ def initialize(base_url:, http_client: nil, headers: {})
42
+ @base_url = base_url.to_s.chomp('/')
43
+ @http_client = http_client
44
+ @headers = headers.transform_keys(&:to_s)
45
+ end
46
+
47
+ Wire::Calls::CALL_TO_METHOD.each_value do |method_name|
48
+ define_method(method_name) do |**args|
49
+ _dispatch(method_name, args)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def _dispatch(method_name, args)
56
+ wire_name = WIRE_METHOD_NAMES.fetch(method_name)
57
+ url = URI.parse("#{@base_url}/v1/wallet/#{wire_name}")
58
+ body = args.empty? ? {} : BSV::WireFormat.to_wire(args)
59
+
60
+ response = _post(url, body)
61
+ _handle_response(response)
62
+ end
63
+
64
+ def _post(url, body)
65
+ req = Net::HTTP::Post.new(url)
66
+ @headers.each { |k, v| req[k] = v }
67
+ req['Content-Type'] = 'application/json'
68
+ req['Accept'] = 'application/json'
69
+ req.body = body.to_json
70
+
71
+ _execute_request(url, req)
72
+ rescue BSV::Wallet::Error
73
+ raise
74
+ rescue StandardError => e
75
+ raise BSV::Wallet::Error.new("HTTP request failed: #{e.message}", code: 1)
76
+ end
77
+
78
+ def _execute_request(url, req)
79
+ if @http_client
80
+ @http_client.request(url, req)
81
+ else
82
+ Net::HTTP.start(url.host, url.port,
83
+ use_ssl: url.scheme == 'https') { |conn| conn.request(req) }
84
+ end
85
+ end
86
+
87
+ def _handle_response(response)
88
+ status = response.code.to_i
89
+ raw = response.body.to_s
90
+
91
+ if (200..299).cover?(status)
92
+ _parse_success(raw, status)
93
+ else
94
+ _raise_error(raw)
95
+ end
96
+ end
97
+
98
+ def _parse_success(raw, status)
99
+ return {} if status == 204 || raw.empty?
100
+
101
+ begin
102
+ parsed = JSON.parse(raw)
103
+ rescue JSON::ParserError => e
104
+ raise BSV::Wallet::Error.new("Invalid JSON in response: #{e.message}", code: 1)
105
+ end
106
+
107
+ return parsed unless parsed.is_a?(Hash)
108
+
109
+ BSV::WireFormat.from_wire(parsed)
110
+ end
111
+
112
+ def _raise_error(raw)
113
+ error_hash = begin
114
+ JSON.parse(raw)
115
+ rescue JSON::ParserError
116
+ {}
117
+ end
118
+
119
+ code = error_hash['code'].to_i
120
+ message = error_hash['message'].to_s
121
+ stack = error_hash['stack'].to_s
122
+ code = 1 if code.zero?
123
+
124
+ raise BSV::Wallet.error_from_wire(code, message.empty? ? raw : message, stack)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ module BSV
7
+ module Wallet
8
+ module Substrates
9
+ # Concrete {WalletWire} implementation over HTTP/HTTPS.
10
+ #
11
+ # Transmits binary BRC-103 request frames via HTTP POST to `{base_url}/wallet`
12
+ # using `Content-Type: application/octet-stream`, and returns the raw binary
13
+ # response body.
14
+ #
15
+ # The optional `http_client:` parameter accepts any object responding to
16
+ # `#request(uri, net_http_request)` — matching the injectable client
17
+ # convention used throughout this SDK. When omitted, `Net::HTTP` is used
18
+ # directly.
19
+ #
20
+ # @example Direct wire usage
21
+ # wire = BSV::Wallet::Substrates::HTTPWalletWire.new(base_url: 'https://wallet.example')
22
+ # client = BSV::Wallet::WalletWireTransceiver.new(wire)
23
+ # client.get_network #=> { network: :mainnet }
24
+ #
25
+ # @example Convenience factory
26
+ # client = BSV::Wallet::Substrates::HTTPWalletWire.client(base_url: 'https://wallet.example')
27
+ # client.get_network #=> { network: :mainnet }
28
+ class HTTPWalletWire
29
+ include WalletWire
30
+
31
+ # @param base_url [String] wallet base URL (e.g. 'https://wallet.example')
32
+ # @param http_client [#request, nil] injectable HTTP client for testing;
33
+ # must respond to +#request(uri, net_http_request)+. Defaults to +Net::HTTP+.
34
+ # @param headers [Hash] additional headers merged into every request
35
+ def initialize(base_url:, http_client: nil, headers: {})
36
+ @uri = build_uri(base_url)
37
+ @http_client = http_client
38
+ @headers = headers.transform_keys(&:to_s)
39
+ end
40
+
41
+ # Convenience factory: wraps a new {HTTPWalletWire} in a {WalletWireTransceiver}.
42
+ #
43
+ # @param base_url [String]
44
+ # @param http_client [#request, nil]
45
+ # @param headers [Hash]
46
+ # @return [WalletWireTransceiver]
47
+ def self.client(base_url:, http_client: nil, headers: {})
48
+ WalletWireTransceiver.new(new(base_url: base_url, http_client: http_client, headers: headers))
49
+ end
50
+
51
+ # POST binary frame to `{base_url}/wallet` and return the binary response body.
52
+ #
53
+ # @param message [String] binary request frame (ASCII-8BIT)
54
+ # @return [String] binary result frame (ASCII-8BIT)
55
+ # @raise [BSV::Wallet::Error] on non-2xx HTTP status or network failure
56
+ def transmit_to_wallet(message)
57
+ http_post(message)
58
+ rescue BSV::Wallet::Error
59
+ raise
60
+ rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
61
+ Net::OpenTimeout, Net::ReadTimeout, Net::HTTPError => e
62
+ raise BSV::Wallet::Error.new("wallet connection failed: #{e.message}", code: 1)
63
+ rescue StandardError => e
64
+ raise BSV::Wallet::Error.new("wallet request failed: #{e.message}", code: 1)
65
+ end
66
+
67
+ private
68
+
69
+ def build_uri(base_url)
70
+ normalised = base_url.to_s.chomp('/')
71
+ URI.parse("#{normalised}/wallet")
72
+ end
73
+
74
+ def http_post(body)
75
+ request = Net::HTTP::Post.new(@uri.path)
76
+ @headers.each { |k, v| request[k] = v }
77
+ request['Content-Type'] = 'application/octet-stream'
78
+ request.body = body.respond_to?(:b) ? body.b : body.pack('C*')
79
+
80
+ response = execute_request(request)
81
+
82
+ raise BSV::Wallet::Error.new("HTTP #{response.code}: #{response.body.to_s.strip}", code: 1) unless response.is_a?(Net::HTTPSuccess)
83
+
84
+ (response.body || '').b
85
+ end
86
+
87
+ def execute_request(request)
88
+ if @http_client
89
+ @http_client.request(@uri, request)
90
+ else
91
+ Net::HTTP.start(@uri.host, @uri.port, use_ssl: @uri.scheme == 'https') do |http|
92
+ http.request(request)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Abstract binary transport for BRC-103 wallet communication.
6
+ #
7
+ # Include this module and implement {#transmit_to_wallet} to provide a
8
+ # concrete transport (e.g. HTTP, in-process loopback, Unix socket).
9
+ module WalletWire
10
+ # Transmit a binary request frame to the wallet and return the binary result frame.
11
+ #
12
+ # @param message [String] binary request frame (ASCII-8BIT)
13
+ # @return [String] binary result frame (ASCII-8BIT)
14
+ # @raise [NotImplementedError] unless overridden by the including class
15
+ def transmit_to_wallet(message)
16
+ raise NotImplementedError, "#{self.class}#transmit_to_wallet is not implemented"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Server-side BRC-103 dispatcher.
6
+ #
7
+ # Takes any {Interface::BRC100} implementation and exposes it as a
8
+ # {WalletWire}. Decodes incoming binary request frames, dispatches to the
9
+ # wallet, serialises the result, and returns a binary result frame.
10
+ #
11
+ # Error boundary: any {Error} from the wallet is serialised into an error
12
+ # frame so the client can rehydrate it. Any other {StandardError} is wrapped
13
+ # in {Error} (code 1) before framing — the processor never lets a Ruby
14
+ # exception propagate past this boundary.
15
+ #
16
+ # @example Wrap a ProtoWallet for loopback testing
17
+ # processor = BSV::Wallet::WalletWireProcessor.new(proto_wallet)
18
+ # transceiver = BSV::Wallet::WalletWireTransceiver.new(processor)
19
+ class WalletWireProcessor
20
+ include WalletWire
21
+
22
+ # @param wallet [Interface::BRC100] any BRC-100 wallet implementation
23
+ def initialize(wallet)
24
+ @wallet = wallet
25
+ end
26
+
27
+ # Process a binary request frame and return a binary result frame.
28
+ #
29
+ # @param request_bytes [String] binary request frame
30
+ # @return [String] binary result frame (success or error)
31
+ def transmit_to_wallet(request_bytes)
32
+ req = Wire::Frame.read_request(request_bytes)
33
+ call_byte = req[:call]
34
+
35
+ method_name = Wire::Calls::CALL_TO_METHOD.fetch(call_byte) do
36
+ raise Error.new("unknown call byte: #{call_byte}", code: 1)
37
+ end
38
+
39
+ serialize_result = Serializer::SERIALIZE_RESULT.fetch(call_byte) do
40
+ raise Error.new("no result serialiser for call #{call_byte}", code: 1)
41
+ end
42
+
43
+ args = Serializer::DESERIALIZE_ARGS.fetch(call_byte) do
44
+ raise Error.new("no args deserialiser for call #{call_byte}", code: 1)
45
+ end.call(req[:params])
46
+
47
+ args[:originator] = req[:originator] unless req[:originator].empty?
48
+
49
+ result = @wallet.public_send(method_name, **args)
50
+ payload = serialize_result.call(result)
51
+ Wire::Frame.write_result(payload: payload)
52
+ rescue NotImplementedError => e
53
+ Wire::Frame.write_error(error: UnsupportedActionError.new(e.message.to_s))
54
+ rescue Error => e
55
+ Wire::Frame.write_error(error: e)
56
+ rescue StandardError => e
57
+ Wire::Frame.write_error(error: Error.new(e.message.to_s, code: 1))
58
+ end
59
+ end
60
+ end
61
+ end