bsv-wallet 0.7.0 → 0.8.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 +22 -0
- data/lib/bsv/wallet_interface/substrates/http_wallet_json.rb +268 -0
- data/lib/bsv/wallet_interface/substrates/http_wallet_wire.rb +85 -0
- data/lib/bsv/wallet_interface/substrates/wallet_wire_transceiver.rb +168 -0
- data/lib/bsv/wallet_interface/substrates.rb +16 -0
- data/lib/bsv/wallet_interface/version.rb +2 -2
- data/lib/bsv/wallet_interface/wallet_client.rb +188 -38
- data/lib/bsv/wallet_interface.rb +3 -4
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c71d9a7e4b68e5d47c4d95e3a8bcd6c23c698a92ca3faa4809a9a7abf1171b8e
|
|
4
|
+
data.tar.gz: f61618ef74ba89eae855dc710244f5a9208bff34a2244f4bb2ab6425d6774fea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c9d6badbfaac1bb19ed0bf5745ccfe21aba5372822e692be72f1065df56175481277e8f1b41daf4c17dd0acc758e43a2dcf73d4e8c96147d982dafd7b7a7ca3
|
|
7
|
+
data.tar.gz: 161733d4e9c277c55d4e3b1d89eab89bf156d066575c3f924e066c636d55f65606cd1e4df7afdbbdf0ff8cea615d5d6c09c30aafc63773294370c22659a3bf85
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to the `bsv-wallet` 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.8.0 — 2026-04-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- BRC-100 substrates: `HTTPWalletJSON` for JSON-over-HTTP, `HTTPWalletWire`
|
|
12
|
+
for binary transport, and `WalletWireTransceiver` Interface adapter (#449–#451)
|
|
13
|
+
- `WalletClient` accepts `substrate:` constructor param for remote wallet
|
|
14
|
+
delegation — all Interface methods delegate to the substrate when set (#452)
|
|
15
|
+
- `list_actions` and `list_outputs` honour `include_labels`, `include_inputs`,
|
|
16
|
+
and `include_outputs` flags (#448)
|
|
17
|
+
- `acquire_certificate` uses `AuthFetch` for BRC-104 authenticated certificate
|
|
18
|
+
issuance (#453)
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `prove_certificate` now uses correct protocol ID (`'certificate field encryption'`)
|
|
22
|
+
and key ID format (`"#{serial_number} #{field_name}"`) matching TS/Go SDKs —
|
|
23
|
+
previously incompatible cross-SDK (#424)
|
|
24
|
+
- Code review findings addressed for substrates and include flags
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- `BSV::WalletInterface` module removed — `VERSION` now lives in `BSV::Wallet::VERSION`
|
|
28
|
+
where all other wallet constants already reside
|
|
29
|
+
|
|
8
30
|
## 0.7.0 — 2026-04-12
|
|
9
31
|
|
|
10
32
|
### Added
|
|
@@ -0,0 +1,268 @@
|
|
|
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
|
+
# BRC-100 wallet substrate that delegates all Interface methods to a remote
|
|
11
|
+
# wallet server via JSON-over-HTTP (POST #{base_url}/#{camelCaseMethodName}).
|
|
12
|
+
#
|
|
13
|
+
# Key conversion is handled by {BSV::WireFormat}: Ruby snake_case symbol keys
|
|
14
|
+
# in args are converted to camelCase strings before the request, and the
|
|
15
|
+
# camelCase JSON response is converted back to snake_case symbol keys.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# wallet = BSV::Wallet::Substrates::HTTPWalletJSON.new('http://localhost:3321',
|
|
19
|
+
# originator: 'myapp.example.com')
|
|
20
|
+
# result = wallet.get_public_key({ identity_key: true })
|
|
21
|
+
# # => { public_key: '02abc...' }
|
|
22
|
+
class HTTPWalletJSON
|
|
23
|
+
include BSV::Wallet::Interface
|
|
24
|
+
|
|
25
|
+
# Maps the 28 BRC-100 Interface method symbols to their camelCase HTTP endpoint names.
|
|
26
|
+
# Derived from Wire::Serializer::CALL_CODES keys via BSV::WireFormat.snake_to_camel.
|
|
27
|
+
METHOD_NAMES = BSV::Wallet::Wire::Serializer::CALL_CODES.keys.to_h do |sym|
|
|
28
|
+
[sym, BSV::WireFormat.snake_to_camel(sym.to_s)]
|
|
29
|
+
end.freeze
|
|
30
|
+
|
|
31
|
+
# @param base_url [String] base URL of the remote wallet server (e.g. 'http://localhost:3321')
|
|
32
|
+
# @param originator [String, nil] FQDN of the originating application (sent as Origin/Originator headers)
|
|
33
|
+
# @param http_client [Object, nil] injectable HTTP client for testing; must respond to
|
|
34
|
+
# `start(uri, &block)` returning a Net::HTTP-compatible response
|
|
35
|
+
def initialize(base_url, originator: nil, http_client: nil)
|
|
36
|
+
@base_url = base_url
|
|
37
|
+
@originator = originator
|
|
38
|
+
@http_client = http_client
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Per-call originator is accepted for Interface conformance but not forwarded.
|
|
42
|
+
# The Origin header uses the constructor-level @originator for the lifetime of
|
|
43
|
+
# the connection — matching the TS SDK's HTTPWalletJSON, which also ignores per-call
|
|
44
|
+
# originator. WalletWireTransceiver supports per-call originator because the wire
|
|
45
|
+
# frame encodes it per-message; HTTP substrates identify by connection, not by call.
|
|
46
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
|
47
|
+
|
|
48
|
+
def create_action(args, originator: nil)
|
|
49
|
+
call(METHOD_NAMES[:create_action], args)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sign_action(args, originator: nil)
|
|
53
|
+
call(METHOD_NAMES[:sign_action], args)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def abort_action(args, originator: nil)
|
|
57
|
+
call(METHOD_NAMES[:abort_action], args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def list_actions(args, originator: nil)
|
|
61
|
+
call(METHOD_NAMES[:list_actions], args)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def internalize_action(args, originator: nil)
|
|
65
|
+
call(METHOD_NAMES[:internalize_action], args)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def list_outputs(args, originator: nil)
|
|
69
|
+
call(METHOD_NAMES[:list_outputs], args)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def relinquish_output(args, originator: nil)
|
|
73
|
+
call(METHOD_NAMES[:relinquish_output], args)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def get_public_key(args, originator: nil)
|
|
77
|
+
call(METHOD_NAMES[:get_public_key], args)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def reveal_counterparty_key_linkage(args, originator: nil)
|
|
81
|
+
call(METHOD_NAMES[:reveal_counterparty_key_linkage], args)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def reveal_specific_key_linkage(args, originator: nil)
|
|
85
|
+
call(METHOD_NAMES[:reveal_specific_key_linkage], args)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def encrypt(args, originator: nil)
|
|
89
|
+
call(METHOD_NAMES[:encrypt], args)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def decrypt(args, originator: nil)
|
|
93
|
+
call(METHOD_NAMES[:decrypt], args)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def create_hmac(args, originator: nil)
|
|
97
|
+
call(METHOD_NAMES[:create_hmac], args)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def verify_hmac(args, originator: nil)
|
|
101
|
+
call(METHOD_NAMES[:verify_hmac], args)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def create_signature(args, originator: nil)
|
|
105
|
+
call(METHOD_NAMES[:create_signature], args)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def verify_signature(args, originator: nil)
|
|
109
|
+
call(METHOD_NAMES[:verify_signature], args)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def acquire_certificate(args, originator: nil)
|
|
113
|
+
call(METHOD_NAMES[:acquire_certificate], args)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def list_certificates(args, originator: nil)
|
|
117
|
+
call(METHOD_NAMES[:list_certificates], args)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def prove_certificate(args, originator: nil)
|
|
121
|
+
call(METHOD_NAMES[:prove_certificate], args)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def relinquish_certificate(args, originator: nil)
|
|
125
|
+
call(METHOD_NAMES[:relinquish_certificate], args)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def discover_by_identity_key(args, originator: nil)
|
|
129
|
+
call(METHOD_NAMES[:discover_by_identity_key], args)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def discover_by_attributes(args, originator: nil)
|
|
133
|
+
call(METHOD_NAMES[:discover_by_attributes], args)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def is_authenticated(args = {}, originator: nil)
|
|
137
|
+
call(METHOD_NAMES[:is_authenticated], args)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def wait_for_authentication(args = {}, originator: nil)
|
|
141
|
+
call(METHOD_NAMES[:wait_for_authentication], args)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def get_height(args = {}, originator: nil)
|
|
145
|
+
call(METHOD_NAMES[:get_height], args)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def get_header_for_height(args, originator: nil)
|
|
149
|
+
call(METHOD_NAMES[:get_header_for_height], args)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def get_network(args = {}, originator: nil)
|
|
153
|
+
call(METHOD_NAMES[:get_network], args)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def get_version(args = {}, originator: nil)
|
|
157
|
+
call(METHOD_NAMES[:get_version], args)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# Posts args to the remote wallet endpoint for the given camelCase method name.
|
|
165
|
+
#
|
|
166
|
+
# Outbound: converts snake_case symbol keys to camelCase strings via WireFormat.to_wire.
|
|
167
|
+
# Inbound: converts camelCase string keys to snake_case symbols via WireFormat.from_wire.
|
|
168
|
+
# Errors: non-2xx response raises the appropriate WalletError subclass.
|
|
169
|
+
#
|
|
170
|
+
# @param method_name [String] camelCase endpoint name (e.g. 'getPublicKey')
|
|
171
|
+
# @param args [Hash] method arguments (snake_case symbol keys)
|
|
172
|
+
# @return [Hash] response with snake_case symbol keys
|
|
173
|
+
def call(method_name, args)
|
|
174
|
+
args ||= {}
|
|
175
|
+
wire_args = BSV::WireFormat.to_wire(args)
|
|
176
|
+
body = JSON.generate(wire_args)
|
|
177
|
+
|
|
178
|
+
uri = build_uri(method_name)
|
|
179
|
+
headers = build_headers
|
|
180
|
+
|
|
181
|
+
response = execute_request(uri, body, headers)
|
|
182
|
+
|
|
183
|
+
handle_response(response, method_name, args)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def build_uri(method_name)
|
|
187
|
+
URI.parse("#{@base_url}/#{method_name}")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def build_headers
|
|
191
|
+
h = {
|
|
192
|
+
'Content-Type' => 'application/json',
|
|
193
|
+
'Accept' => 'application/json'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if @originator
|
|
197
|
+
origin_value = to_origin_header(@originator)
|
|
198
|
+
h['Origin'] = origin_value
|
|
199
|
+
h['Originator'] = origin_value
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
h
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Converts an originator domain to a full Origin header value.
|
|
206
|
+
# Prepends 'http://' if no scheme is present (matching TS SDK toOriginHeader).
|
|
207
|
+
def to_origin_header(originator)
|
|
208
|
+
return originator if originator.match?(%r{\A[a-z][a-z0-9+.-]*://}i)
|
|
209
|
+
|
|
210
|
+
"http://#{originator}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def execute_request(uri, body, headers)
|
|
214
|
+
if @http_client
|
|
215
|
+
@http_client.post(uri, body, headers)
|
|
216
|
+
else
|
|
217
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
218
|
+
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
|
219
|
+
request.body = body
|
|
220
|
+
http.request(request)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def handle_response(response, method_name, args)
|
|
226
|
+
code = response.code.to_i
|
|
227
|
+
|
|
228
|
+
unless (200..299).cover?(code)
|
|
229
|
+
data = begin
|
|
230
|
+
parse_json_body(response.body)
|
|
231
|
+
rescue JSON::ParserError
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
raise_error_response(code, data, method_name, args)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
data = parse_json_body(response.body)
|
|
238
|
+
return {} if data.nil?
|
|
239
|
+
|
|
240
|
+
data.is_a?(Hash) ? BSV::WireFormat.from_wire(data) : data
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def parse_json_body(body)
|
|
244
|
+
return nil if body.nil? || body.empty?
|
|
245
|
+
|
|
246
|
+
JSON.parse(body)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def raise_error_response(code, data, method_name, _args)
|
|
250
|
+
if code == 400 && data.is_a?(Hash) && data['isError']
|
|
251
|
+
case data['code']
|
|
252
|
+
when 5
|
|
253
|
+
raise BSV::Wallet::WalletError.new(data['message'] || 'Review actions required', 5)
|
|
254
|
+
when 6
|
|
255
|
+
raise BSV::Wallet::InvalidParameterError, data['parameter'] || 'unknown'
|
|
256
|
+
when 7
|
|
257
|
+
raise BSV::Wallet::InsufficientFundsError, data['message']
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
message = (data.is_a?(Hash) && data['message']) ||
|
|
262
|
+
"HTTP #{code} error calling #{method_name}"
|
|
263
|
+
raise BSV::Wallet::WalletError, message
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module BSV
|
|
7
|
+
module Wallet
|
|
8
|
+
module Substrates
|
|
9
|
+
# Binary wire transport that transmits BRC-100 wallet wire messages over HTTP.
|
|
10
|
+
#
|
|
11
|
+
# Implements the single-method WalletWire interface: given a raw binary frame
|
|
12
|
+
# (as an Array of byte integers), parses the call code, maps it to a URL path,
|
|
13
|
+
# and POSTs the payload to the remote wallet endpoint.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# wire = BSV::Wallet::Substrates::HTTPWalletWire.new('http://localhost:3301')
|
|
17
|
+
# response_bytes = wire.transmit_to_wallet(frame_bytes)
|
|
18
|
+
class HTTPWalletWire
|
|
19
|
+
# @param base_url [String] the base URL of the remote wallet (e.g. 'http://localhost:3301')
|
|
20
|
+
# @param originator [String, nil] FQDN of the calling application (sent as Origin header)
|
|
21
|
+
# @param http_client [#call, nil] injectable HTTP client for testing; nil uses Net::HTTP
|
|
22
|
+
def initialize(base_url, originator: nil, http_client: nil)
|
|
23
|
+
@base_url = base_url
|
|
24
|
+
@originator = originator
|
|
25
|
+
@http_client = http_client
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Transmits a binary wallet wire message to the remote wallet.
|
|
29
|
+
#
|
|
30
|
+
# Parses the call code from byte 0, reads the originator from the header,
|
|
31
|
+
# and POSTs the remaining payload bytes to the appropriate URL path.
|
|
32
|
+
#
|
|
33
|
+
# @param message [Array<Integer>] raw wire frame as array of byte integers
|
|
34
|
+
# @return [Array<Integer>] response body as array of byte integers
|
|
35
|
+
# @raise [ArgumentError] if the message is empty or contains an unknown call code
|
|
36
|
+
# @raise [RuntimeError] if the HTTP response indicates an error (non-2xx)
|
|
37
|
+
def transmit_to_wallet(message)
|
|
38
|
+
raise ArgumentError, 'message must not be empty' if message.nil? || message.empty?
|
|
39
|
+
|
|
40
|
+
call_code = message[0]
|
|
41
|
+
call_name = BSV::Wallet::Wire::Serializer::METHODS_BY_CODE[call_code]
|
|
42
|
+
raise ArgumentError, "unknown call code: #{call_code}" if call_name.nil?
|
|
43
|
+
|
|
44
|
+
originator_length = message[1] || 0
|
|
45
|
+
originator = message[2, originator_length].pack('C*').force_encoding('UTF-8') if originator_length.positive?
|
|
46
|
+
|
|
47
|
+
payload_start = 2 + originator_length
|
|
48
|
+
payload = message[payload_start..] || []
|
|
49
|
+
|
|
50
|
+
camel_name = BSV::WireFormat.snake_to_camel(call_name.to_s)
|
|
51
|
+
url = "#{@base_url}/#{camel_name}"
|
|
52
|
+
|
|
53
|
+
response_body = post_binary(url, payload, originator || @originator)
|
|
54
|
+
response_body.bytes.to_a
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def post_binary(url, payload_bytes, originator)
|
|
60
|
+
uri = URI.parse(url)
|
|
61
|
+
body = payload_bytes.pack('C*')
|
|
62
|
+
|
|
63
|
+
if @http_client
|
|
64
|
+
@http_client.call(uri, body, originator)
|
|
65
|
+
else
|
|
66
|
+
perform_request(uri, body, originator)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def perform_request(uri, body, originator)
|
|
71
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
72
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
73
|
+
request['Content-Type'] = 'application/octet-stream'
|
|
74
|
+
request['Origin'] = originator if originator && !originator.empty?
|
|
75
|
+
request.body = body
|
|
76
|
+
response = http.request(request)
|
|
77
|
+
raise "HTTPWalletWire: HTTP #{response.code} from #{uri}" unless response.is_a?(Net::HTTPSuccess)
|
|
78
|
+
|
|
79
|
+
response.body || ''
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Substrates
|
|
6
|
+
# BRC-100 wallet Interface implementation that transmits calls over a binary wire transport.
|
|
7
|
+
#
|
|
8
|
+
# Serialises each Interface method call into a binary wire frame via
|
|
9
|
+
# {BSV::Wallet::Wire::Serializer}, transmits it via a wire transport (any object
|
|
10
|
+
# responding to `#transmit_to_wallet`), then deserialises the response.
|
|
11
|
+
#
|
|
12
|
+
# The wire transport is duck-typed — any object that accepts
|
|
13
|
+
# `transmit_to_wallet(message)` where +message+ is an Array of byte integers
|
|
14
|
+
# and returns an Array of byte integers qualifies. The canonical wire transport
|
|
15
|
+
# is {HTTPWalletWire}.
|
|
16
|
+
#
|
|
17
|
+
# @example Using with HTTPWalletWire
|
|
18
|
+
# wire = BSV::Wallet::Substrates::HTTPWalletWire.new('http://localhost:3301')
|
|
19
|
+
# wallet = BSV::Wallet::Substrates::WalletWireTransceiver.new(wire, originator: 'myapp.example.com')
|
|
20
|
+
# result = wallet.get_public_key({ identity_key: true })
|
|
21
|
+
# # => { public_key: '02abc...' }
|
|
22
|
+
class WalletWireTransceiver
|
|
23
|
+
include BSV::Wallet::Interface
|
|
24
|
+
|
|
25
|
+
# @param wire [#transmit_to_wallet] wire transport (duck-typed)
|
|
26
|
+
# @param originator [String, nil] default FQDN of the originating application;
|
|
27
|
+
# may be overridden per-call via the method-level originator keyword argument
|
|
28
|
+
def initialize(wire, originator: nil)
|
|
29
|
+
@wire = wire
|
|
30
|
+
@originator = originator
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_action(args, originator: nil)
|
|
34
|
+
transmit(:create_action, args, originator || @originator)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def sign_action(args, originator: nil)
|
|
38
|
+
transmit(:sign_action, args, originator || @originator)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def abort_action(args, originator: nil)
|
|
42
|
+
transmit(:abort_action, args, originator || @originator)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def list_actions(args, originator: nil)
|
|
46
|
+
transmit(:list_actions, args, originator || @originator)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def internalize_action(args, originator: nil)
|
|
50
|
+
transmit(:internalize_action, args, originator || @originator)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def list_outputs(args, originator: nil)
|
|
54
|
+
transmit(:list_outputs, args, originator || @originator)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def relinquish_output(args, originator: nil)
|
|
58
|
+
transmit(:relinquish_output, args, originator || @originator)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get_public_key(args, originator: nil)
|
|
62
|
+
transmit(:get_public_key, args, originator || @originator)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def reveal_counterparty_key_linkage(args, originator: nil)
|
|
66
|
+
transmit(:reveal_counterparty_key_linkage, args, originator || @originator)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def reveal_specific_key_linkage(args, originator: nil)
|
|
70
|
+
transmit(:reveal_specific_key_linkage, args, originator || @originator)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def encrypt(args, originator: nil)
|
|
74
|
+
transmit(:encrypt, args, originator || @originator)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def decrypt(args, originator: nil)
|
|
78
|
+
transmit(:decrypt, args, originator || @originator)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create_hmac(args, originator: nil)
|
|
82
|
+
transmit(:create_hmac, args, originator || @originator)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def verify_hmac(args, originator: nil)
|
|
86
|
+
transmit(:verify_hmac, args, originator || @originator)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def create_signature(args, originator: nil)
|
|
90
|
+
transmit(:create_signature, args, originator || @originator)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def verify_signature(args, originator: nil)
|
|
94
|
+
transmit(:verify_signature, args, originator || @originator)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def acquire_certificate(args, originator: nil)
|
|
98
|
+
transmit(:acquire_certificate, args, originator || @originator)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def list_certificates(args, originator: nil)
|
|
102
|
+
transmit(:list_certificates, args, originator || @originator)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def prove_certificate(args, originator: nil)
|
|
106
|
+
transmit(:prove_certificate, args, originator || @originator)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def relinquish_certificate(args, originator: nil)
|
|
110
|
+
transmit(:relinquish_certificate, args, originator || @originator)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def discover_by_identity_key(args, originator: nil)
|
|
114
|
+
transmit(:discover_by_identity_key, args, originator || @originator)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def discover_by_attributes(args, originator: nil)
|
|
118
|
+
transmit(:discover_by_attributes, args, originator || @originator)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def is_authenticated(args = {}, originator: nil)
|
|
122
|
+
transmit(:is_authenticated, args, originator || @originator)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def wait_for_authentication(args = {}, originator: nil)
|
|
126
|
+
transmit(:wait_for_authentication, args, originator || @originator)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def get_height(args = {}, originator: nil)
|
|
130
|
+
transmit(:get_height, args, originator || @originator)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def get_header_for_height(args, originator: nil)
|
|
134
|
+
transmit(:get_header_for_height, args, originator || @originator)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def get_network(args = {}, originator: nil)
|
|
138
|
+
transmit(:get_network, args, originator || @originator)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def get_version(args = {}, originator: nil)
|
|
142
|
+
transmit(:get_version, args, originator || @originator)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Serialises +method_name+ and +args+ into a binary wire frame, transmits it
|
|
148
|
+
# via the wire transport, and deserialises the response.
|
|
149
|
+
#
|
|
150
|
+
# Error responses (non-zero first byte) are parsed and raised as {WalletError}
|
|
151
|
+
# by {BSV::Wallet::Wire::Serializer.deserialize_response}.
|
|
152
|
+
#
|
|
153
|
+
# @param method_name [Symbol] BRC-100 method name (snake_case)
|
|
154
|
+
# @param args [Hash] method arguments
|
|
155
|
+
# @param orig [String, nil] FQDN of the originating application
|
|
156
|
+
# @return [Hash] deserialised result
|
|
157
|
+
def transmit(method_name, args, orig)
|
|
158
|
+
frame = BSV::Wallet::Wire::Serializer.serialize_request(
|
|
159
|
+
method_name, args || {}, originator: orig.to_s
|
|
160
|
+
)
|
|
161
|
+
response_bytes = @wire.transmit_to_wallet(frame.bytes.to_a)
|
|
162
|
+
response_binary = response_bytes.pack('C*')
|
|
163
|
+
BSV::Wallet::Wire::Serializer.deserialize_response(method_name, response_binary)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
# BRC-100 wallet substrates — alternative transport layers for the wallet interface.
|
|
6
|
+
#
|
|
7
|
+
# Substrates are raw transport adapters. They are not full Wallet::Interface implementations;
|
|
8
|
+
# instead they provide the low-level connectivity that a WalletWireTransceiver or
|
|
9
|
+
# HTTPWalletJSON wraps to expose the full interface.
|
|
10
|
+
module Substrates
|
|
11
|
+
autoload :HTTPWalletJSON, 'bsv/wallet_interface/substrates/http_wallet_json'
|
|
12
|
+
autoload :HTTPWalletWire, 'bsv/wallet_interface/substrates/http_wallet_wire'
|
|
13
|
+
autoload :WalletWireTransceiver, 'bsv/wallet_interface/substrates/wallet_wire_transceiver'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -45,6 +45,9 @@ module BSV
|
|
|
45
45
|
# @return [BroadcastQueue] the broadcast queue used to dispatch transactions
|
|
46
46
|
attr_reader :broadcast_queue
|
|
47
47
|
|
|
48
|
+
# @return [Interface, nil] the optional substrate for remote wallet delegation
|
|
49
|
+
attr_reader :substrate
|
|
50
|
+
|
|
48
51
|
# @param key [BSV::Primitives::PrivateKey, String, KeyDeriver] signing key
|
|
49
52
|
# @param storage [StorageAdapter] persistence adapter (default: FileStore).
|
|
50
53
|
# Use +storage: MemoryStore.new+ for tests.
|
|
@@ -54,6 +57,10 @@ module BSV
|
|
|
54
57
|
# @param http_client [#request, nil] injectable HTTP client for certificate issuance
|
|
55
58
|
# @param broadcaster [#broadcast, nil] optional broadcaster; any object responding to #broadcast(tx)
|
|
56
59
|
# @param broadcast_queue [BroadcastQueue, nil] optional broadcast queue; defaults to InlineQueue
|
|
60
|
+
# @param substrate [Interface, nil] optional remote wallet substrate; when set, all Interface
|
|
61
|
+
# methods delegate to the substrate instead of using local storage and key derivation.
|
|
62
|
+
# Accepts any object implementing {Interface} (e.g. {Substrates::HTTPWalletJSON},
|
|
63
|
+
# {Substrates::WalletWireTransceiver}).
|
|
57
64
|
def initialize(
|
|
58
65
|
key,
|
|
59
66
|
storage: FileStore.new,
|
|
@@ -65,9 +72,11 @@ module BSV
|
|
|
65
72
|
coin_selector: nil,
|
|
66
73
|
change_generator: nil,
|
|
67
74
|
broadcaster: nil,
|
|
68
|
-
broadcast_queue: nil
|
|
75
|
+
broadcast_queue: nil,
|
|
76
|
+
substrate: nil
|
|
69
77
|
)
|
|
70
78
|
super(key)
|
|
79
|
+
@substrate = substrate
|
|
71
80
|
@storage = storage
|
|
72
81
|
@network = network
|
|
73
82
|
@chain_provider = chain_provider
|
|
@@ -110,7 +119,9 @@ module BSV
|
|
|
110
119
|
# UTXOs and generates change; requires +:outputs+ and no +:inputs+
|
|
111
120
|
# @param _originator [String, nil] FQDN of the originating application
|
|
112
121
|
# @return [Hash] finalised result or signable_transaction
|
|
113
|
-
def create_action(args,
|
|
122
|
+
def create_action(args, originator: nil)
|
|
123
|
+
return @substrate.create_action(args, originator: originator) if @substrate
|
|
124
|
+
|
|
114
125
|
validate_create_action!(args)
|
|
115
126
|
|
|
116
127
|
send_with_txids = Array(args.dig(:options, :send_with))
|
|
@@ -156,7 +167,9 @@ module BSV
|
|
|
156
167
|
# @option args [String] :reference base64 reference from create_action
|
|
157
168
|
# @param _originator [String, nil] FQDN of the originating application
|
|
158
169
|
# @return [Hash] with :txid and :tx (BEEF bytes)
|
|
159
|
-
def sign_action(args,
|
|
170
|
+
def sign_action(args, originator: nil)
|
|
171
|
+
return @substrate.sign_action(args, originator: originator) if @substrate
|
|
172
|
+
|
|
160
173
|
reference = args[:reference]
|
|
161
174
|
pending = @pending[reference]
|
|
162
175
|
raise WalletError, 'Transaction not found for the given reference' unless pending
|
|
@@ -185,7 +198,9 @@ module BSV
|
|
|
185
198
|
# @option args [String] :reference base64 reference to abort
|
|
186
199
|
# @param _originator [String, nil] FQDN of the originating application
|
|
187
200
|
# @return [Hash] { aborted: true }
|
|
188
|
-
def abort_action(args,
|
|
201
|
+
def abort_action(args, originator: nil)
|
|
202
|
+
return @substrate.abort_action(args, originator: originator) if @substrate
|
|
203
|
+
|
|
189
204
|
reference = args[:reference]
|
|
190
205
|
raise WalletError, 'Transaction not found for the given reference' unless @pending.key?(reference)
|
|
191
206
|
|
|
@@ -210,12 +225,14 @@ module BSV
|
|
|
210
225
|
# @option args [Integer] :offset results to skip (default 0)
|
|
211
226
|
# @param _originator [String, nil] FQDN of the originating application
|
|
212
227
|
# @return [Hash] { total_actions: Integer, actions: Array }
|
|
213
|
-
def list_actions(args,
|
|
228
|
+
def list_actions(args, originator: nil)
|
|
229
|
+
return @substrate.list_actions(args, originator: originator) if @substrate
|
|
230
|
+
|
|
214
231
|
validate_list_actions!(args)
|
|
215
232
|
query = build_action_query(args)
|
|
216
233
|
total = @storage.count_actions(query)
|
|
217
234
|
actions = @storage.find_actions(query)
|
|
218
|
-
{ total_actions: total, actions: actions }
|
|
235
|
+
{ total_actions: total, actions: strip_action_fields(actions, args) }
|
|
219
236
|
end
|
|
220
237
|
|
|
221
238
|
# Lists spendable outputs in a basket.
|
|
@@ -228,12 +245,14 @@ module BSV
|
|
|
228
245
|
# @option args [Integer] :offset results to skip (default 0)
|
|
229
246
|
# @param _originator [String, nil] FQDN of the originating application
|
|
230
247
|
# @return [Hash] { total_outputs: Integer, outputs: Array }
|
|
231
|
-
def list_outputs(args,
|
|
248
|
+
def list_outputs(args, originator: nil)
|
|
249
|
+
return @substrate.list_outputs(args, originator: originator) if @substrate
|
|
250
|
+
|
|
232
251
|
validate_list_outputs!(args)
|
|
233
252
|
query = build_output_query(args)
|
|
234
253
|
total = @storage.count_outputs(query)
|
|
235
254
|
outputs = @storage.find_outputs(query)
|
|
236
|
-
{ total_outputs: total, outputs: outputs }
|
|
255
|
+
{ total_outputs: total, outputs: strip_output_fields(outputs, args) }
|
|
237
256
|
end
|
|
238
257
|
|
|
239
258
|
# Removes an output from basket tracking.
|
|
@@ -243,7 +262,9 @@ module BSV
|
|
|
243
262
|
# @option args [String] :output outpoint string
|
|
244
263
|
# @param _originator [String, nil] FQDN of the originating application
|
|
245
264
|
# @return [Hash] { relinquished: true }
|
|
246
|
-
def relinquish_output(args,
|
|
265
|
+
def relinquish_output(args, originator: nil)
|
|
266
|
+
return @substrate.relinquish_output(args, originator: originator) if @substrate
|
|
267
|
+
|
|
247
268
|
Validators.validate_basket!(args[:basket])
|
|
248
269
|
Validators.validate_outpoint!(args[:output])
|
|
249
270
|
raise WalletError, 'Output not found' unless @storage.delete_output(args[:output])
|
|
@@ -264,7 +285,9 @@ module BSV
|
|
|
264
285
|
# @option args [Array<String>] :labels optional labels
|
|
265
286
|
# @param _originator [String, nil] FQDN of the originating application
|
|
266
287
|
# @return [Hash] { accepted: true }
|
|
267
|
-
def internalize_action(args,
|
|
288
|
+
def internalize_action(args, originator: nil)
|
|
289
|
+
return @substrate.internalize_action(args, originator: originator) if @substrate
|
|
290
|
+
|
|
268
291
|
validate_internalize_action!(args)
|
|
269
292
|
beef_binary = args[:tx].pack('C*')
|
|
270
293
|
beef = BSV::Transaction::Beef.from_binary(beef_binary)
|
|
@@ -290,7 +313,9 @@ module BSV
|
|
|
290
313
|
#
|
|
291
314
|
# @param _args [Hash] unused (empty hash)
|
|
292
315
|
# @return [Hash] { height: Integer }
|
|
293
|
-
def get_height(
|
|
316
|
+
def get_height(args = {}, originator: nil)
|
|
317
|
+
return @substrate.get_height(args, originator: originator) if @substrate
|
|
318
|
+
|
|
294
319
|
{ height: @chain_provider.get_height }
|
|
295
320
|
end
|
|
296
321
|
|
|
@@ -299,7 +324,9 @@ module BSV
|
|
|
299
324
|
# @param args [Hash]
|
|
300
325
|
# @option args [Integer] :height block height
|
|
301
326
|
# @return [Hash] { header: String } 80-byte hex-encoded block header
|
|
302
|
-
def get_header_for_height(args,
|
|
327
|
+
def get_header_for_height(args, originator: nil)
|
|
328
|
+
return @substrate.get_header_for_height(args, originator: originator) if @substrate
|
|
329
|
+
|
|
303
330
|
raise InvalidParameterError.new('height', 'a positive Integer') unless args[:height].is_a?(Integer) && args[:height].positive?
|
|
304
331
|
|
|
305
332
|
{ header: @chain_provider.get_header(args[:height]) }
|
|
@@ -309,7 +336,9 @@ module BSV
|
|
|
309
336
|
#
|
|
310
337
|
# @param _args [Hash] unused (empty hash)
|
|
311
338
|
# @return [Hash] { network: String } 'mainnet' or 'testnet'
|
|
312
|
-
def get_network(
|
|
339
|
+
def get_network(args = {}, originator: nil)
|
|
340
|
+
return @substrate.get_network(args, originator: originator) if @substrate
|
|
341
|
+
|
|
313
342
|
{ network: @network }
|
|
314
343
|
end
|
|
315
344
|
|
|
@@ -317,8 +346,10 @@ module BSV
|
|
|
317
346
|
#
|
|
318
347
|
# @param _args [Hash] unused (empty hash)
|
|
319
348
|
# @return [Hash] { version: String } in vendor-major.minor.patch format
|
|
320
|
-
def get_version(
|
|
321
|
-
|
|
349
|
+
def get_version(args = {}, originator: nil)
|
|
350
|
+
return @substrate.get_version(args, originator: originator) if @substrate
|
|
351
|
+
|
|
352
|
+
{ version: "bsv-wallet-#{BSV::Wallet::VERSION}" }
|
|
322
353
|
end
|
|
323
354
|
|
|
324
355
|
# Discovers on-chain UTXOs for the wallet's identity address and imports
|
|
@@ -427,7 +458,9 @@ module BSV
|
|
|
427
458
|
#
|
|
428
459
|
# @param _args [Hash] unused (empty hash)
|
|
429
460
|
# @return [Hash] { authenticated: Boolean }
|
|
430
|
-
def is_authenticated(
|
|
461
|
+
def is_authenticated(args = {}, originator: nil)
|
|
462
|
+
return @substrate.is_authenticated(args, originator: originator) if @substrate
|
|
463
|
+
|
|
431
464
|
{ authenticated: true }
|
|
432
465
|
end
|
|
433
466
|
|
|
@@ -436,7 +469,9 @@ module BSV
|
|
|
436
469
|
#
|
|
437
470
|
# @param _args [Hash] unused (empty hash)
|
|
438
471
|
# @return [Hash] { authenticated: true }
|
|
439
|
-
def wait_for_authentication(
|
|
472
|
+
def wait_for_authentication(args = {}, originator: nil)
|
|
473
|
+
return @substrate.wait_for_authentication(args, originator: originator) if @substrate
|
|
474
|
+
|
|
440
475
|
{ authenticated: true }
|
|
441
476
|
end
|
|
442
477
|
|
|
@@ -458,7 +493,9 @@ module BSV
|
|
|
458
493
|
# @option args [String] :keyring_revealer pubkey hex or 'certifier' (required for direct)
|
|
459
494
|
# @option args [Hash] :keyring_for_subject field_name => base64 key (required for direct)
|
|
460
495
|
# @return [Hash] the stored certificate
|
|
461
|
-
def acquire_certificate(args,
|
|
496
|
+
def acquire_certificate(args, originator: nil)
|
|
497
|
+
return @substrate.acquire_certificate(args, originator: originator) if @substrate
|
|
498
|
+
|
|
462
499
|
validate_acquire_certificate!(args)
|
|
463
500
|
|
|
464
501
|
cert = if args[:acquisition_protocol] == 'issuance'
|
|
@@ -479,7 +516,9 @@ module BSV
|
|
|
479
516
|
# @option args [Integer] :limit max results (default 10)
|
|
480
517
|
# @option args [Integer] :offset number to skip (default 0)
|
|
481
518
|
# @return [Hash] { total_certificates:, certificates: [...] }
|
|
482
|
-
def list_certificates(args,
|
|
519
|
+
def list_certificates(args, originator: nil)
|
|
520
|
+
return @substrate.list_certificates(args, originator: originator) if @substrate
|
|
521
|
+
|
|
483
522
|
raise InvalidParameterError.new('certifiers', 'a non-empty Array') unless args[:certifiers].is_a?(Array) && !args[:certifiers].empty?
|
|
484
523
|
raise InvalidParameterError.new('types', 'a non-empty Array') unless args[:types].is_a?(Array) && !args[:types].empty?
|
|
485
524
|
|
|
@@ -505,7 +544,9 @@ module BSV
|
|
|
505
544
|
# @option args [Array<String>] :fields_to_reveal field names to reveal
|
|
506
545
|
# @option args [String] :verifier verifier public key hex
|
|
507
546
|
# @return [Hash] { keyring_for_verifier: { field_name => Array<Integer> } }
|
|
508
|
-
def prove_certificate(args,
|
|
547
|
+
def prove_certificate(args, originator: nil)
|
|
548
|
+
return @substrate.prove_certificate(args, originator: originator) if @substrate
|
|
549
|
+
|
|
509
550
|
cert_arg = args[:certificate]
|
|
510
551
|
fields_to_reveal = args[:fields_to_reveal]
|
|
511
552
|
verifier = args[:verifier]
|
|
@@ -528,8 +569,8 @@ module BSV
|
|
|
528
569
|
# Encrypt the keyring entry for the verifier
|
|
529
570
|
encrypted = encrypt({
|
|
530
571
|
plaintext: key_value.bytes,
|
|
531
|
-
protocol_id: [2, 'certificate field
|
|
532
|
-
key_id: "#{cert_arg[:
|
|
572
|
+
protocol_id: [2, 'certificate field encryption'],
|
|
573
|
+
key_id: "#{cert_arg[:serial_number]} #{field_name}",
|
|
533
574
|
counterparty: verifier
|
|
534
575
|
})
|
|
535
576
|
keyring_for_verifier[field_name] = encrypted[:ciphertext]
|
|
@@ -545,7 +586,9 @@ module BSV
|
|
|
545
586
|
# @option args [String] :serial_number serial number
|
|
546
587
|
# @option args [String] :certifier certifier public key hex
|
|
547
588
|
# @return [Hash] { relinquished: true }
|
|
548
|
-
def relinquish_certificate(args,
|
|
589
|
+
def relinquish_certificate(args, originator: nil)
|
|
590
|
+
return @substrate.relinquish_certificate(args, originator: originator) if @substrate
|
|
591
|
+
|
|
549
592
|
deleted = @storage.delete_certificate(
|
|
550
593
|
type: args[:type],
|
|
551
594
|
serial_number: args[:serial_number],
|
|
@@ -566,7 +609,9 @@ module BSV
|
|
|
566
609
|
# @option args [Integer] :limit max results (default 10)
|
|
567
610
|
# @option args [Integer] :offset number to skip (default 0)
|
|
568
611
|
# @return [Hash] { total_certificates:, certificates: [...] }
|
|
569
|
-
def discover_by_identity_key(args,
|
|
612
|
+
def discover_by_identity_key(args, originator: nil)
|
|
613
|
+
return @substrate.discover_by_identity_key(args, originator: originator) if @substrate
|
|
614
|
+
|
|
570
615
|
Validators.validate_pub_key_hex!(args[:identity_key], 'identity_key')
|
|
571
616
|
|
|
572
617
|
query = { subject: args[:identity_key], limit: args[:limit] || 10, offset: args[:offset] || 0 }
|
|
@@ -585,7 +630,9 @@ module BSV
|
|
|
585
630
|
# @option args [Integer] :limit max results (default 10)
|
|
586
631
|
# @option args [Integer] :offset number to skip (default 0)
|
|
587
632
|
# @return [Hash] { total_certificates:, certificates: [...] }
|
|
588
|
-
def discover_by_attributes(args,
|
|
633
|
+
def discover_by_attributes(args, originator: nil)
|
|
634
|
+
return @substrate.discover_by_attributes(args, originator: originator) if @substrate
|
|
635
|
+
|
|
589
636
|
raise InvalidParameterError.new('attributes', 'a non-empty Hash') unless args[:attributes].is_a?(Hash) && !args[:attributes].empty?
|
|
590
637
|
|
|
591
638
|
query = { attributes: args[:attributes], limit: args[:limit] || 10, offset: args[:offset] || 0 }
|
|
@@ -594,6 +641,65 @@ module BSV
|
|
|
594
641
|
{ total_certificates: total, certificates: certs.map { |c| cert_without_keyring(c) } }
|
|
595
642
|
end
|
|
596
643
|
|
|
644
|
+
# --- ProtoWallet crypto method overrides for substrate delegation ---
|
|
645
|
+
#
|
|
646
|
+
# When a substrate is configured, these methods delegate to it rather than
|
|
647
|
+
# performing local key derivation and cryptographic operations.
|
|
648
|
+
|
|
649
|
+
def get_public_key(args, originator: nil)
|
|
650
|
+
return @substrate.get_public_key(args, originator: originator) if @substrate
|
|
651
|
+
|
|
652
|
+
super
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def reveal_counterparty_key_linkage(args, originator: nil)
|
|
656
|
+
return @substrate.reveal_counterparty_key_linkage(args, originator: originator) if @substrate
|
|
657
|
+
|
|
658
|
+
super
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def reveal_specific_key_linkage(args, originator: nil)
|
|
662
|
+
return @substrate.reveal_specific_key_linkage(args, originator: originator) if @substrate
|
|
663
|
+
|
|
664
|
+
super
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
def encrypt(args, originator: nil)
|
|
668
|
+
return @substrate.encrypt(args, originator: originator) if @substrate
|
|
669
|
+
|
|
670
|
+
super
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def decrypt(args, originator: nil)
|
|
674
|
+
return @substrate.decrypt(args, originator: originator) if @substrate
|
|
675
|
+
|
|
676
|
+
super
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
def create_hmac(args, originator: nil)
|
|
680
|
+
return @substrate.create_hmac(args, originator: originator) if @substrate
|
|
681
|
+
|
|
682
|
+
super
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def verify_hmac(args, originator: nil)
|
|
686
|
+
return @substrate.verify_hmac(args, originator: originator) if @substrate
|
|
687
|
+
|
|
688
|
+
super
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def create_signature(args, originator: nil)
|
|
692
|
+
return @substrate.create_signature(args, originator: originator) if @substrate
|
|
693
|
+
|
|
694
|
+
super
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def verify_signature(args, originator: nil)
|
|
698
|
+
return @substrate.verify_signature(args, originator: originator) if @substrate
|
|
699
|
+
|
|
700
|
+
super
|
|
701
|
+
end
|
|
702
|
+
|
|
597
703
|
# Maximum ancestor depth to traverse when wiring source transactions.
|
|
598
704
|
# Guards against stack overflow on pathologically deep or cyclic chains.
|
|
599
705
|
ANCESTOR_DEPTH_CAP = 64
|
|
@@ -1436,6 +1542,47 @@ module BSV
|
|
|
1436
1542
|
query
|
|
1437
1543
|
end
|
|
1438
1544
|
|
|
1545
|
+
# --- Include-flag stripping ---
|
|
1546
|
+
|
|
1547
|
+
def strip_action_fields(actions, args)
|
|
1548
|
+
actions.map do |action|
|
|
1549
|
+
a = action.dup
|
|
1550
|
+
a.delete(:labels) unless args[:include_labels] == true
|
|
1551
|
+
a.delete(:inputs) unless args[:include_inputs] == true
|
|
1552
|
+
|
|
1553
|
+
if a.key?(:inputs)
|
|
1554
|
+
strip_src = args[:include_input_source_locking_scripts] != true
|
|
1555
|
+
strip_unlock = args[:include_input_unlocking_scripts] != true
|
|
1556
|
+
if strip_src || strip_unlock
|
|
1557
|
+
a[:inputs] = a[:inputs].map do |i|
|
|
1558
|
+
d = i.dup
|
|
1559
|
+
d.delete(:source_locking_script) if strip_src
|
|
1560
|
+
d.delete(:unlocking_script) if strip_unlock
|
|
1561
|
+
d
|
|
1562
|
+
end
|
|
1563
|
+
end
|
|
1564
|
+
end
|
|
1565
|
+
|
|
1566
|
+
a.delete(:outputs) unless args[:include_outputs] == true
|
|
1567
|
+
|
|
1568
|
+
if a.key?(:outputs) && args[:include_output_locking_scripts] != true
|
|
1569
|
+
a[:outputs] = a[:outputs].map { |o| o.dup.tap { |h| h.delete(:locking_script) } }
|
|
1570
|
+
end
|
|
1571
|
+
|
|
1572
|
+
a
|
|
1573
|
+
end
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
def strip_output_fields(outputs, args)
|
|
1577
|
+
outputs.map do |output|
|
|
1578
|
+
o = output.dup
|
|
1579
|
+
o.delete(:tags) unless args[:include_tags] == true
|
|
1580
|
+
o.delete(:labels) unless args[:include_labels] == true
|
|
1581
|
+
o.delete(:custom_instructions) unless args[:include_custom_instructions] == true
|
|
1582
|
+
o
|
|
1583
|
+
end
|
|
1584
|
+
end
|
|
1585
|
+
|
|
1439
1586
|
# --- Internalize helpers ---
|
|
1440
1587
|
|
|
1441
1588
|
def store_proofs_from_beef(beef)
|
|
@@ -1580,20 +1727,19 @@ module BSV
|
|
|
1580
1727
|
end
|
|
1581
1728
|
|
|
1582
1729
|
def acquire_via_issuance(args)
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
code = response.code.to_i
|
|
1730
|
+
response = auth_fetch_client.fetch(
|
|
1731
|
+
args[:certifier_url],
|
|
1732
|
+
method: 'POST',
|
|
1733
|
+
headers: { 'content-type' => 'application/json' },
|
|
1734
|
+
body: JSON.generate({
|
|
1735
|
+
type: args[:type],
|
|
1736
|
+
subject: @key_deriver.identity_key,
|
|
1737
|
+
certifier: args[:certifier],
|
|
1738
|
+
fields: args[:fields]
|
|
1739
|
+
})
|
|
1740
|
+
)
|
|
1595
1741
|
|
|
1596
|
-
raise WalletError, "Certificate issuance failed: HTTP #{
|
|
1742
|
+
raise WalletError, "Certificate issuance failed: HTTP #{response.status}" unless (200..299).cover?(response.status)
|
|
1597
1743
|
|
|
1598
1744
|
body = JSON.parse(response.body)
|
|
1599
1745
|
|
|
@@ -1619,6 +1765,10 @@ module BSV
|
|
|
1619
1765
|
raise WalletError, 'Certificate issuance failed: invalid JSON response'
|
|
1620
1766
|
end
|
|
1621
1767
|
|
|
1768
|
+
def auth_fetch_client
|
|
1769
|
+
@auth_fetch_client ||= BSV::Auth::AuthFetch.new(wallet: self)
|
|
1770
|
+
end
|
|
1771
|
+
|
|
1622
1772
|
def execute_http(uri, request)
|
|
1623
1773
|
if @http_client
|
|
1624
1774
|
@http_client.request(uri, request)
|
data/lib/bsv/wallet_interface.rb
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module BSV
|
|
4
|
-
module WalletInterface
|
|
5
|
-
autoload :VERSION, 'bsv/wallet_interface/version'
|
|
6
|
-
end
|
|
7
|
-
|
|
8
4
|
module Wallet
|
|
5
|
+
autoload :VERSION, 'bsv/wallet_interface/version'
|
|
6
|
+
|
|
9
7
|
# BRC-100 Interface
|
|
10
8
|
autoload :Interface, 'bsv/wallet_interface/interface'
|
|
11
9
|
autoload :KeyDeriver, 'bsv/wallet_interface/key_deriver'
|
|
@@ -23,6 +21,7 @@ module BSV
|
|
|
23
21
|
autoload :WhatsOnChainProvider, 'bsv/wallet_interface/whats_on_chain_provider'
|
|
24
22
|
autoload :WalletClient, 'bsv/wallet_interface/wallet_client'
|
|
25
23
|
autoload :Wire, 'bsv/wallet_interface/wire'
|
|
24
|
+
autoload :Substrates, 'bsv/wallet_interface/substrates'
|
|
26
25
|
autoload :CertificateSignature, 'bsv/wallet_interface/certificate_signature'
|
|
27
26
|
autoload :FeeModel, 'bsv/wallet_interface/fee_model'
|
|
28
27
|
autoload :FeeEstimator, 'bsv/wallet_interface/fee_estimator'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bsv-wallet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Bettison
|
|
@@ -29,7 +29,7 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.11.0
|
|
33
33
|
- - "<"
|
|
34
34
|
- !ruby/object:Gem::Version
|
|
35
35
|
version: '1.0'
|
|
@@ -39,7 +39,7 @@ dependencies:
|
|
|
39
39
|
requirements:
|
|
40
40
|
- - ">="
|
|
41
41
|
- !ruby/object:Gem::Version
|
|
42
|
-
version: 0.
|
|
42
|
+
version: 0.11.0
|
|
43
43
|
- - "<"
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
45
|
version: '1.0'
|
|
@@ -75,6 +75,10 @@ files:
|
|
|
75
75
|
- lib/bsv/wallet_interface/proof_store.rb
|
|
76
76
|
- lib/bsv/wallet_interface/proto_wallet.rb
|
|
77
77
|
- lib/bsv/wallet_interface/storage_adapter.rb
|
|
78
|
+
- lib/bsv/wallet_interface/substrates.rb
|
|
79
|
+
- lib/bsv/wallet_interface/substrates/http_wallet_json.rb
|
|
80
|
+
- lib/bsv/wallet_interface/substrates/http_wallet_wire.rb
|
|
81
|
+
- lib/bsv/wallet_interface/substrates/wallet_wire_transceiver.rb
|
|
78
82
|
- lib/bsv/wallet_interface/validators.rb
|
|
79
83
|
- lib/bsv/wallet_interface/version.rb
|
|
80
84
|
- lib/bsv/wallet_interface/wallet_client.rb
|