bsv-wallet 0.1.2 → 0.2.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42266cef3c2585982df666f123097ea4fb4edd9f6619aacf37ed50628148ae1f
|
|
4
|
+
data.tar.gz: cec92d7893bba6a8b25439f5fd9c4d203ae7fce0cc2f63370adfc1a01d6bed61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b45b5c376c8c2b20ec7afffd1f57a15e89d19509ad6afecce8da8f3cdd69b9f7d8af20b5157ca380b8d0a65d9db5dc9a6aed2b8ac1518542775207534e576825
|
|
7
|
+
data.tar.gz: aae7eaec036fd2bd620551645e5a111bccfa39ac948ccb6541f24dc5e187acd1c9458a2576b4bca3adc5e31c0ec73d83af270cee8861d940af9952da429fe36c
|
|
@@ -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-wallet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.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
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0.
|
|
32
|
+
version: '0.4'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0.
|
|
39
|
+
version: '0.4'
|
|
40
40
|
description: Implements the BRC-100 standard wallet-to-application interface for the
|
|
41
41
|
BSV Blockchain.
|
|
42
42
|
executables: []
|
|
@@ -52,6 +52,7 @@ files:
|
|
|
52
52
|
- lib/bsv/wallet_interface/errors/invalid_signature_error.rb
|
|
53
53
|
- lib/bsv/wallet_interface/errors/unsupported_action_error.rb
|
|
54
54
|
- lib/bsv/wallet_interface/errors/wallet_error.rb
|
|
55
|
+
- lib/bsv/wallet_interface/file_store.rb
|
|
55
56
|
- lib/bsv/wallet_interface/interface.rb
|
|
56
57
|
- lib/bsv/wallet_interface/key_deriver.rb
|
|
57
58
|
- lib/bsv/wallet_interface/memory_store.rb
|