bsv-sdk 0.3.2 → 0.4.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 +65 -0
- data/lib/bsv/primitives/ecies.rb +82 -0
- data/lib/bsv/transaction/fee_models/live_policy.rb +14 -2
- data/lib/bsv/transaction/fee_models/satoshis_per_kilobyte.rb +2 -2
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/wallet.rb +2 -2
- data/lib/bsv/wallet_interface/file_store.rb +170 -0
- data/lib/bsv/wallet_interface/version.rb +1 -1
- data/lib/bsv/wallet_interface/wallet_client.rb +3 -2
- data/lib/bsv/wallet_interface.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d32b117209fc6509e5020811725c5b5bb5e776c57fbd0f3cb0afa8cf3cc12ca
|
|
4
|
+
data.tar.gz: 2fa913c8e3fe270b53a1748e2ff4bdb8055945b625a9e3075eb293b78d581fe4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82e8ca0cf9e266d7c80fe8be42be40450e5a3495db083f90aa098aaf7ff0186ecc4a63b3a1efd9b7447738d857b930f655be291ad098a83ef7a6d0095df0b4d0
|
|
7
|
+
data.tar.gz: 103b3246361a57c3b1161c789c097c147b6c7070781094587f59bdee6caf3141bb5b22934a89e1bfdd4e514b4f45eb2edd1ae218fc92f23e2d520a3a56f72113
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,71 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-04-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
#### Primitives
|
|
13
|
+
|
|
14
|
+
- **Bitcore ECIES** — `ECIES.bitcore_encrypt` / `ECIES.bitcore_decrypt`. AES-256-CBC with random IV, SHA-512(X-coordinate) key derivation. Matches ts-sdk and go-sdk Bitcore variants.
|
|
15
|
+
|
|
16
|
+
#### Transaction
|
|
17
|
+
|
|
18
|
+
- **LivePolicy.default** — one-line convenience for live fee queries via GorillaPool ARC with 5-minute cache and 100 sat/kB fallback.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Default fee rate**: `SatoshisPerKilobyte` default changed from 50 to 100 sat/kB (matches ts-sdk LivePolicy fallback). `Wallet#fund` default changed from 0.5 to 0.1 sat/byte.
|
|
23
|
+
|
|
24
|
+
### bsv-wallet v0.2.0
|
|
25
|
+
|
|
26
|
+
- **FileStore** — JSON file-backed persistent storage, now the default for `WalletClient`. Data survives process restarts. MemoryStore becomes explicit opt-in for tests.
|
|
27
|
+
- **File permissions** — directory created with 0700, files with 0600. Warns via Logger on startup if permissions are too open.
|
|
28
|
+
- **BRC-31 Auth/Peer** — mutual authentication with nonce-based challenges, ECDSA signatures, and session management.
|
|
29
|
+
- **Wire protocol** — binary ABI serialisation for all 28 BRC-100 methods (call codes 1-28, VarInt encoding).
|
|
30
|
+
- **Certificate issuance** — `acquire_certificate` with `'issuance'` protocol (POST to certifier URL).
|
|
31
|
+
- **OpCat template** — OP_CAT concatenation script template with lock/unlock constructors.
|
|
32
|
+
- **Live fee policy** — `LivePolicy` fee model fetching from ARC `/v1/policy`.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Subject and certifier pinned in certificate issuance response (not overridable by remote certifier)
|
|
37
|
+
- Wire reader negative privileged_reason length crash
|
|
38
|
+
- PUSHDATA1/2/4 bounds check (silent data corruption on truncated scripts)
|
|
39
|
+
- Extended key path validation (reject non-numeric indices)
|
|
40
|
+
|
|
41
|
+
## [0.3.0] - 2026-03-27
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
#### Primitives
|
|
46
|
+
|
|
47
|
+
- **SymmetricKey** — AES-256-GCM encryption/decryption with 32-byte IV (cross-SDK compatible). Construct from random, ECDH, or raw bytes.
|
|
48
|
+
- **BRC-77 SignedMessage** — authenticated message signing and verification using BRC-42 derived keys. Supports targeted (specific verifier) and "anyone" modes.
|
|
49
|
+
- **BRC-78 EncryptedMessage** — end-to-end encrypted messaging using ECDH-derived symmetric keys.
|
|
50
|
+
- **Schnorr ZKP (BRC-94)** — zero-knowledge proof of ECDH shared secret knowledge. `Schnorr.generate_proof` / `Schnorr.verify_proof`.
|
|
51
|
+
- **Shamir's Secret Sharing** — split private keys into threshold shares (`PrivateKey#to_key_shares`) with Lagrange interpolation reconstruction. Backup format with integrity check.
|
|
52
|
+
|
|
53
|
+
#### Script
|
|
54
|
+
|
|
55
|
+
- **PushDrop template** — data carrier with P2PK spending. `Script.pushdrop_lock` / `Script.pushdrop_unlock` with field extraction.
|
|
56
|
+
- **RPuzzle template** — R-puzzle hash-based spending with 6 hash type variants (raw, SHA1, SHA256, RIPEMD160, HASH160, HASH256).
|
|
57
|
+
|
|
58
|
+
#### Transaction
|
|
59
|
+
|
|
60
|
+
- **Benford's law change distribution** — privacy-preserving change output splitting using Benford's first-digit distribution.
|
|
61
|
+
- **ARC X-WaitFor** — `ARC#broadcast` gains `wait_for:` parameter for `X-WaitFor` header (RECEIVED, STORED, ANNOUNCED_TO_NETWORK, SEEN_ON_NETWORK, MINED).
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- Empty plaintext/ciphertext handling on older OpenSSL versions
|
|
66
|
+
- PushDrop detection for minimally-encoded fields
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- `Transaction#fee` change distribution uses Benford's law (was equal split)
|
|
71
|
+
- `LineLength` raised to 150
|
|
72
|
+
|
|
8
73
|
## [0.2.1] - 2026-03-07
|
|
9
74
|
|
|
10
75
|
### Fixed
|
data/lib/bsv/primitives/ecies.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'openssl'
|
|
4
|
+
require 'securerandom'
|
|
4
5
|
|
|
5
6
|
module BSV
|
|
6
7
|
module Primitives
|
|
@@ -95,6 +96,74 @@ module BSV
|
|
|
95
96
|
end
|
|
96
97
|
end
|
|
97
98
|
|
|
99
|
+
# Encrypt a message using the Bitcore ECIES variant.
|
|
100
|
+
#
|
|
101
|
+
# Differs from the Electrum variant: no magic prefix, AES-256-CBC
|
|
102
|
+
# (not AES-128), random IV prepended to ciphertext, and HMAC covers
|
|
103
|
+
# the ciphertext (not the ephemeral pubkey).
|
|
104
|
+
#
|
|
105
|
+
# Wire format: +ephemeral_pub(33) + IV(16) + ciphertext + HMAC(32)+
|
|
106
|
+
#
|
|
107
|
+
# @param message [String] the plaintext message
|
|
108
|
+
# @param public_key [PublicKey] the recipient's public key
|
|
109
|
+
# @param private_key [PrivateKey, nil] optional ephemeral key (random if omitted)
|
|
110
|
+
# @return [String] encrypted payload
|
|
111
|
+
def bitcore_encrypt(message, public_key, private_key: nil)
|
|
112
|
+
message = message.b if message.encoding != Encoding::ASCII_8BIT
|
|
113
|
+
|
|
114
|
+
ephemeral = private_key || PrivateKey.generate
|
|
115
|
+
key_e, key_m = derive_bitcore_keys(ephemeral, public_key)
|
|
116
|
+
|
|
117
|
+
iv = SecureRandom.random_bytes(16)
|
|
118
|
+
|
|
119
|
+
cipher = OpenSSL::Cipher.new('aes-256-cbc')
|
|
120
|
+
cipher.encrypt
|
|
121
|
+
cipher.key = key_e
|
|
122
|
+
cipher.iv = iv
|
|
123
|
+
ciphertext = message.empty? ? cipher.final : cipher.update(message) + cipher.final
|
|
124
|
+
|
|
125
|
+
c = iv + ciphertext
|
|
126
|
+
mac = Digest.hmac_sha256(key_m, c)
|
|
127
|
+
|
|
128
|
+
ephemeral.public_key.compressed + c + mac
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Decrypt a message encrypted with the Bitcore ECIES variant.
|
|
132
|
+
#
|
|
133
|
+
# @param data [String] the encrypted payload (Bitcore ECIES format)
|
|
134
|
+
# @param private_key [PrivateKey] the recipient's private key
|
|
135
|
+
# @return [String] the decrypted plaintext
|
|
136
|
+
# @raise [ArgumentError] if the data is too short
|
|
137
|
+
# @raise [DecryptionError] if HMAC verification or AES decryption fails
|
|
138
|
+
def bitcore_decrypt(data, private_key)
|
|
139
|
+
data = data.b if data.encoding != Encoding::ASCII_8BIT
|
|
140
|
+
|
|
141
|
+
# Minimum: ephemeral_pub(33) + IV(16) + AES block(16) + HMAC(32) = 97
|
|
142
|
+
raise ArgumentError, 'data too short' if data.bytesize < 97
|
|
143
|
+
|
|
144
|
+
ephemeral_pub = PublicKey.from_bytes(data[0, 33])
|
|
145
|
+
mac = data[-32, 32]
|
|
146
|
+
c = data[33...-32] # IV + ciphertext
|
|
147
|
+
|
|
148
|
+
key_e, key_m = derive_bitcore_keys(private_key, ephemeral_pub)
|
|
149
|
+
|
|
150
|
+
expected_mac = Digest.hmac_sha256(key_m, c)
|
|
151
|
+
raise DecryptionError, 'HMAC verification failed' unless secure_compare(mac, expected_mac)
|
|
152
|
+
|
|
153
|
+
iv = c[0, 16]
|
|
154
|
+
ciphertext = c[16..]
|
|
155
|
+
|
|
156
|
+
begin
|
|
157
|
+
cipher = OpenSSL::Cipher.new('aes-256-cbc')
|
|
158
|
+
cipher.decrypt
|
|
159
|
+
cipher.key = key_e
|
|
160
|
+
cipher.iv = iv
|
|
161
|
+
cipher.update(ciphertext) + cipher.final
|
|
162
|
+
rescue OpenSSL::Cipher::CipherError => e
|
|
163
|
+
raise DecryptionError, "decryption failed: #{e.message}"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
98
167
|
class << self
|
|
99
168
|
private
|
|
100
169
|
|
|
@@ -122,6 +191,19 @@ module BSV
|
|
|
122
191
|
|
|
123
192
|
[iv, key_e, key_m]
|
|
124
193
|
end
|
|
194
|
+
|
|
195
|
+
# Bitcore key derivation: SHA-512(ECDH X-coordinate) → key_e(32) + key_m(32)
|
|
196
|
+
def derive_bitcore_keys(private_key, public_key)
|
|
197
|
+
shared = private_key.derive_shared_secret(public_key)
|
|
198
|
+
# Bitcore uses raw X-coordinate (32 bytes BE), not compressed point
|
|
199
|
+
x_bytes = shared.point.to_octet_string(:uncompressed)[1, 32]
|
|
200
|
+
derived = Digest.sha512(x_bytes)
|
|
201
|
+
|
|
202
|
+
key_e = derived[0, 32]
|
|
203
|
+
key_m = derived[32, 32]
|
|
204
|
+
|
|
205
|
+
[key_e, key_m]
|
|
206
|
+
end
|
|
125
207
|
end
|
|
126
208
|
end
|
|
127
209
|
end
|
|
@@ -32,12 +32,24 @@ module BSV
|
|
|
32
32
|
# @return [Integer] cache TTL in seconds
|
|
33
33
|
attr_reader :cache_ttl
|
|
34
34
|
|
|
35
|
+
DEFAULT_ARC_URL = 'https://arc.gorillapool.io'
|
|
36
|
+
DEFAULT_FALLBACK_RATE = 100
|
|
37
|
+
|
|
38
|
+
# Returns a LivePolicy with sensible defaults (GorillaPool ARC,
|
|
39
|
+
# 100 sat/kB fallback, 5-minute cache).
|
|
40
|
+
#
|
|
41
|
+
# @param api_key [String, nil] optional ARC API key
|
|
42
|
+
# @return [LivePolicy]
|
|
43
|
+
def self.default(api_key: nil)
|
|
44
|
+
new(arc_url: DEFAULT_ARC_URL, fallback_rate: DEFAULT_FALLBACK_RATE, api_key: api_key)
|
|
45
|
+
end
|
|
46
|
+
|
|
35
47
|
# @param arc_url [String] ARC base URL (e.g. 'https://arc.gorillapool.io')
|
|
36
|
-
# @param fallback_rate [Integer] sat/kB to use when fetch fails (default:
|
|
48
|
+
# @param fallback_rate [Integer] sat/kB to use when fetch fails (default: 100)
|
|
37
49
|
# @param cache_ttl [Integer] seconds to cache a fetched rate (default: 300)
|
|
38
50
|
# @param api_key [String, nil] optional Bearer token for ARC authentication
|
|
39
51
|
# @param http_client [#request, nil] injectable HTTP client for testing
|
|
40
|
-
def initialize(arc_url:, fallback_rate:
|
|
52
|
+
def initialize(arc_url:, fallback_rate: DEFAULT_FALLBACK_RATE, cache_ttl: DEFAULT_CACHE_TTL, api_key: nil, http_client: nil)
|
|
41
53
|
super()
|
|
42
54
|
@arc_url = arc_url.chomp('/')
|
|
43
55
|
@fallback_rate = fallback_rate
|
|
@@ -15,8 +15,8 @@ module BSV
|
|
|
15
15
|
# @return [Integer] satoshis per kilobyte rate
|
|
16
16
|
attr_reader :value
|
|
17
17
|
|
|
18
|
-
# @param value [Integer] satoshis per kilobyte (default:
|
|
19
|
-
def initialize(value:
|
|
18
|
+
# @param value [Integer] satoshis per kilobyte (default: 100)
|
|
19
|
+
def initialize(value: 100)
|
|
20
20
|
super()
|
|
21
21
|
@value = value
|
|
22
22
|
end
|
data/lib/bsv/version.rb
CHANGED
data/lib/bsv/wallet/wallet.rb
CHANGED
|
@@ -25,7 +25,7 @@ module BSV
|
|
|
25
25
|
@provider.fetch_utxos(address(network: network)).sum(&:satoshis)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def fund(tx, network: :mainnet, satoshis_per_byte: 0.
|
|
28
|
+
def fund(tx, network: :mainnet, satoshis_per_byte: 0.1)
|
|
29
29
|
utxos = @provider.fetch_utxos(address(network: network))
|
|
30
30
|
output_total = tx.total_output_satoshis
|
|
31
31
|
|
|
@@ -66,7 +66,7 @@ module BSV
|
|
|
66
66
|
tx.sign_all(@private_key)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
def fund_and_sign(tx, network: :mainnet, satoshis_per_byte: 0.
|
|
69
|
+
def fund_and_sign(tx, network: :mainnet, satoshis_per_byte: 0.1)
|
|
70
70
|
fund(tx, network: network, satoshis_per_byte: satoshis_per_byte)
|
|
71
71
|
sign(tx)
|
|
72
72
|
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'logger'
|
|
6
|
+
|
|
7
|
+
module BSV
|
|
8
|
+
module Wallet
|
|
9
|
+
# JSON file-backed storage adapter.
|
|
10
|
+
#
|
|
11
|
+
# Persists actions, outputs, and certificates as JSON files in a
|
|
12
|
+
# configurable directory (default: +~/.bsv-wallet/+). Data survives
|
|
13
|
+
# process restarts.
|
|
14
|
+
#
|
|
15
|
+
# Inherits all filtering and pagination logic from {MemoryStore} and
|
|
16
|
+
# adds load-on-init / save-on-mutation.
|
|
17
|
+
#
|
|
18
|
+
# @example Default location
|
|
19
|
+
# store = BSV::Wallet::FileStore.new
|
|
20
|
+
# # Data written to ~/.bsv-wallet/
|
|
21
|
+
#
|
|
22
|
+
# @example Custom directory
|
|
23
|
+
# store = BSV::Wallet::FileStore.new(dir: '/var/lib/my-app/wallet')
|
|
24
|
+
class FileStore < MemoryStore
|
|
25
|
+
DEFAULT_DIR = File.expand_path('~/.bsv-wallet')
|
|
26
|
+
|
|
27
|
+
# @param dir [String] directory for JSON files
|
|
28
|
+
# (default: +~/.bsv-wallet/+ or +BSV_WALLET_DIR+ env var)
|
|
29
|
+
# @param dir [String] directory for JSON files
|
|
30
|
+
# @param logger [Logger, nil] logger for permission warnings (default: Logger to STDERR)
|
|
31
|
+
def initialize(dir: nil, logger: nil)
|
|
32
|
+
super()
|
|
33
|
+
@dir = dir || ENV.fetch('BSV_WALLET_DIR', DEFAULT_DIR)
|
|
34
|
+
@logger = logger || Logger.new($stderr, progname: 'bsv-wallet')
|
|
35
|
+
FileUtils.mkdir_p(@dir, mode: 0o700)
|
|
36
|
+
check_permissions
|
|
37
|
+
load_from_disk
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [String] the storage directory path
|
|
41
|
+
attr_reader :dir
|
|
42
|
+
|
|
43
|
+
# --- Mutations: delegate to super, then persist ---
|
|
44
|
+
|
|
45
|
+
def store_action(action_data)
|
|
46
|
+
result = super
|
|
47
|
+
save_actions
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def store_output(output_data)
|
|
52
|
+
result = super
|
|
53
|
+
save_outputs
|
|
54
|
+
result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def delete_output(outpoint)
|
|
58
|
+
result = super
|
|
59
|
+
save_outputs if result
|
|
60
|
+
result
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def store_certificate(cert_data)
|
|
64
|
+
result = super
|
|
65
|
+
save_certificates
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def delete_certificate(type:, serial_number:, certifier:)
|
|
70
|
+
result = super
|
|
71
|
+
save_certificates if result
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def check_permissions
|
|
78
|
+
dir_mode = File.stat(@dir).mode & 0o777
|
|
79
|
+
if dir_mode != 0o700
|
|
80
|
+
@logger.warn("Wallet directory #{@dir} has permissions #{format('%04o', dir_mode)} (expected 0700). " \
|
|
81
|
+
'Other users may be able to access wallet data.')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
[actions_path, outputs_path, certificates_path].each do |path|
|
|
85
|
+
next unless File.exist?(path)
|
|
86
|
+
|
|
87
|
+
file_mode = File.stat(path).mode & 0o777
|
|
88
|
+
next if file_mode == 0o600
|
|
89
|
+
|
|
90
|
+
@logger.warn("Wallet file #{path} has permissions #{format('%04o', file_mode)} (expected 0600). " \
|
|
91
|
+
'Other users may be able to read wallet data.')
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def actions_path
|
|
96
|
+
File.join(@dir, 'actions.json')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def outputs_path
|
|
100
|
+
File.join(@dir, 'outputs.json')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def certificates_path
|
|
104
|
+
File.join(@dir, 'certificates.json')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def load_from_disk
|
|
108
|
+
@actions = load_file(actions_path)
|
|
109
|
+
@outputs = load_file(outputs_path)
|
|
110
|
+
@certificates = load_file(certificates_path)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def load_file(path)
|
|
114
|
+
return [] unless File.exist?(path)
|
|
115
|
+
|
|
116
|
+
data = JSON.parse(File.read(path))
|
|
117
|
+
return [] unless data.is_a?(Array)
|
|
118
|
+
|
|
119
|
+
# Symbolise top-level keys for consistency with MemoryStore
|
|
120
|
+
data.map { |entry| symbolise_keys(entry) }
|
|
121
|
+
rescue JSON::ParserError
|
|
122
|
+
[]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def save_actions
|
|
126
|
+
write_file(actions_path, @actions)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def save_outputs
|
|
130
|
+
write_file(outputs_path, @outputs)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def save_certificates
|
|
134
|
+
write_file(certificates_path, @certificates)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def write_file(path, data)
|
|
138
|
+
json = JSON.pretty_generate(stringify_keys_deep(data))
|
|
139
|
+
tmp = "#{path}.tmp"
|
|
140
|
+
File.open(tmp, File::WRONLY | File::CREAT | File::TRUNC, 0o600) { |f| f.write(json) }
|
|
141
|
+
File.rename(tmp, path)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def symbolise_keys(hash)
|
|
145
|
+
return hash unless hash.is_a?(Hash)
|
|
146
|
+
|
|
147
|
+
hash.each_with_object({}) do |(k, v), result|
|
|
148
|
+
result[k.to_sym] = case v
|
|
149
|
+
when Hash then symbolise_keys(v)
|
|
150
|
+
when Array then v.map { |e| e.is_a?(Hash) ? symbolise_keys(e) : e }
|
|
151
|
+
else v
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def stringify_keys_deep(obj)
|
|
157
|
+
case obj
|
|
158
|
+
when Hash
|
|
159
|
+
obj.each_with_object({}) do |(k, v), result|
|
|
160
|
+
result[k.to_s] = stringify_keys_deep(v)
|
|
161
|
+
end
|
|
162
|
+
when Array
|
|
163
|
+
obj.map { |e| stringify_keys_deep(e) }
|
|
164
|
+
else
|
|
165
|
+
obj
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -36,11 +36,12 @@ module BSV
|
|
|
36
36
|
attr_reader :network
|
|
37
37
|
|
|
38
38
|
# @param key [BSV::Primitives::PrivateKey, String, KeyDeriver] signing key
|
|
39
|
-
# @param storage [StorageAdapter] persistence adapter (default:
|
|
39
|
+
# @param storage [StorageAdapter] persistence adapter (default: FileStore).
|
|
40
|
+
# Use +storage: MemoryStore.new+ for tests.
|
|
40
41
|
# @param network [String] 'mainnet' (default) or 'testnet'
|
|
41
42
|
# @param chain_provider [ChainProvider] blockchain data provider (default: NullChainProvider)
|
|
42
43
|
# @param http_client [#request, nil] injectable HTTP client for certificate issuance
|
|
43
|
-
def initialize(key, storage:
|
|
44
|
+
def initialize(key, storage: FileStore.new, network: 'mainnet', chain_provider: NullChainProvider.new, http_client: nil)
|
|
44
45
|
super(key)
|
|
45
46
|
@storage = storage
|
|
46
47
|
@network = network
|
data/lib/bsv/wallet_interface.rb
CHANGED
|
@@ -13,6 +13,7 @@ module BSV
|
|
|
13
13
|
autoload :Validators, 'bsv/wallet_interface/validators'
|
|
14
14
|
autoload :StorageAdapter, 'bsv/wallet_interface/storage_adapter'
|
|
15
15
|
autoload :MemoryStore, 'bsv/wallet_interface/memory_store'
|
|
16
|
+
autoload :FileStore, 'bsv/wallet_interface/file_store'
|
|
16
17
|
autoload :ChainProvider, 'bsv/wallet_interface/chain_provider'
|
|
17
18
|
autoload :NullChainProvider, 'bsv/wallet_interface/null_chain_provider'
|
|
18
19
|
autoload :WalletClient, 'bsv/wallet_interface/wallet_client'
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bsv-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Bettison
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: A Ruby library for interacting with the BSV Blockchain — keys, scripts,
|
|
13
13
|
transactions, and more.
|
|
@@ -105,6 +105,7 @@ files:
|
|
|
105
105
|
- lib/bsv/wallet_interface/errors/invalid_signature_error.rb
|
|
106
106
|
- lib/bsv/wallet_interface/errors/unsupported_action_error.rb
|
|
107
107
|
- lib/bsv/wallet_interface/errors/wallet_error.rb
|
|
108
|
+
- lib/bsv/wallet_interface/file_store.rb
|
|
108
109
|
- lib/bsv/wallet_interface/interface.rb
|
|
109
110
|
- lib/bsv/wallet_interface/key_deriver.rb
|
|
110
111
|
- lib/bsv/wallet_interface/memory_store.rb
|