Class: BSV::Wallet::Client
- Inherits:
-
Object
- Object
- BSV::Wallet::Client
- Includes:
- Authentication, Crypto, Identity, Network, Transaction, Interface::BRC100
- Defined in:
- lib/bsv/wallet/client.rb,
lib/bsv/wallet/client/brc100/crypto.rb,
lib/bsv/wallet/client/brc100/network.rb,
lib/bsv/wallet/client/brc100/identity.rb,
lib/bsv/wallet/client/brc100/transaction.rb,
lib/bsv/wallet/client/brc100/authentication.rb
Overview
BRC-100 wallet implementation.
All 28 BRC-100 methods are implemented directly — the 9 crypto operations are inlined here, with substrate delegation wired at the top of each method. No inheritance; behaviour is fully self-contained.
Defined Under Namespace
Modules: Authentication, Crypto, Identity, Network, Transaction
Constant Summary
Constants included from Transaction
Transaction::ANCESTOR_DEPTH_CAP, Transaction::STALE_CHECK_INTERVAL
Instance Attribute Summary collapse
-
#broadcast_queue ⇒ BroadcastQueue
readonly
The broadcast queue used to dispatch transactions.
-
#broadcaster ⇒ #broadcast?
readonly
The optional broadcaster (responds to #broadcast(tx)).
-
#chain_data_source ⇒ #current_height, ...
readonly
Optional chain data source for SPV and block header lookups.
-
#key_deriver ⇒ KeyDeriver
readonly
The underlying key deriver.
-
#network ⇒ String
readonly
The network (‘mainnet’ or ‘testnet’).
-
#proof_store ⇒ ProofStore
readonly
The merkle proof persistence store.
-
#storage ⇒ Store
readonly
The underlying persistence adapter.
-
#substrate ⇒ Interface?
readonly
The optional substrate for remote wallet delegation.
Instance Method Summary collapse
-
#balance(basket: nil) ⇒ Integer
Returns the total spendable satoshis across all baskets (or a named basket).
-
#broadcast_enabled? ⇒ Boolean
Returns
truewhen broadcast is available. -
#initialize(key, storage: Store::File.new, network: 'mainnet', proof_store: nil, http_client: nil, fee_estimator: nil, coin_selector: nil, change_generator: nil, broadcaster: nil, broadcast_queue: nil, substrate: nil, chain_data_source: nil, allow_memory_store: false) ⇒ Client
constructor
A new instance of Client.
-
#set_wallet_change_params(count:, satoshis:) ⇒ Object
Configures the target UTXO pool parameters for change generation.
-
#spendable_balance(basket: nil) ⇒ Integer
Returns the total satoshis of outputs the wallet can automatically spend.
-
#sync_utxos ⇒ Integer
Discovers UTXOs on-chain for the wallet’s identity address and imports any that are not already in local storage.
-
#utxo_pool(name:, target_count: 20, target_satoshis: 10_000, low_water_mark: 0.5) ⇒ LocalPool
Creates a UTXO pool for high-frequency transaction pre-allocation.
Methods included from Transaction
#abort_action, #create_action, #internalize_action, #list_actions, #list_outputs, #relinquish_output, #sign_action
Methods included from Network
#get_header_for_height, #get_height, #get_network, #get_version
Methods included from Identity
#acquire_certificate, #discover_by_attributes, #discover_by_identity_key, #list_certificates, #prove_certificate, #relinquish_certificate
Methods included from Crypto
#create_hmac, #create_signature, #decrypt, #encrypt, #get_public_key, #reveal_counterparty_key_linkage, #reveal_specific_key_linkage, #verify_hmac, #verify_signature
Methods included from Authentication
#is_authenticated, #wait_for_authentication
Methods included from Interface::BRC100
#abort_action, #acquire_certificate, #create_action, #create_hmac, #create_signature, #decrypt, #discover_by_attributes, #discover_by_identity_key, #encrypt, #get_header_for_height, #get_height, #get_network, #get_public_key, #get_version, #internalize_action, #is_authenticated, #list_actions, #list_certificates, #list_outputs, #prove_certificate, #relinquish_certificate, #relinquish_output, #reveal_counterparty_key_linkage, #reveal_specific_key_linkage, #sign_action, #verify_hmac, #verify_signature, #wait_for_authentication
Constructor Details
#initialize(key, storage: Store::File.new, network: 'mainnet', proof_store: nil, http_client: nil, fee_estimator: nil, coin_selector: nil, change_generator: nil, broadcaster: nil, broadcast_queue: nil, substrate: nil, chain_data_source: nil, allow_memory_store: false) ⇒ Client
Returns a new instance of Client.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/bsv/wallet/client.rb', line 71 def initialize( key, storage: Store::File.new, network: 'mainnet', proof_store: nil, http_client: nil, fee_estimator: nil, coin_selector: nil, change_generator: nil, broadcaster: nil, broadcast_queue: nil, substrate: nil, chain_data_source: nil, allow_memory_store: false ) if storage.is_a?(Store::Memory) && !storage.is_a?(Store::File) && !allow_memory_store raise ArgumentError, 'MemoryStore is not a safe storage adapter for wallets. ' \ 'See: https://sgbett.github.io/bsv-ruby-sdk/gems/wallet/#storage-adapters' end @key_deriver = key.is_a?(KeyDeriver) ? key : KeyDeriver.new(key) @substrate = substrate @chain_data_source = chain_data_source @storage = storage @network = network @proof_store = proof_store || LocalProofStore.new(storage) @http_client = http_client @broadcaster = broadcaster @pending = {} @pending_by_txid = {} @injected_fee_estimator = fee_estimator @injected_coin_selector = coin_selector @injected_change_generator = change_generator @broadcast_queue = broadcast_queue || BroadcastQueue::Inline.new( storage: @storage, broadcaster: @broadcaster ) end |
Instance Attribute Details
#broadcast_queue ⇒ BroadcastQueue (readonly)
Returns the broadcast queue used to dispatch transactions.
48 49 50 |
# File 'lib/bsv/wallet/client.rb', line 48 def broadcast_queue @broadcast_queue end |
#broadcaster ⇒ #broadcast? (readonly)
Returns the optional broadcaster (responds to #broadcast(tx)).
45 46 47 |
# File 'lib/bsv/wallet/client.rb', line 45 def broadcaster @broadcaster end |
#chain_data_source ⇒ #current_height, ... (readonly)
Returns optional chain data source for SPV and block header lookups.
55 56 57 |
# File 'lib/bsv/wallet/client.rb', line 55 def chain_data_source @chain_data_source end |
#key_deriver ⇒ KeyDeriver (readonly)
Returns the underlying key deriver.
33 34 35 |
# File 'lib/bsv/wallet/client.rb', line 33 def key_deriver @key_deriver end |
#network ⇒ String (readonly)
Returns the network (‘mainnet’ or ‘testnet’).
39 40 41 |
# File 'lib/bsv/wallet/client.rb', line 39 def network @network end |
#proof_store ⇒ ProofStore (readonly)
Returns the merkle proof persistence store.
42 43 44 |
# File 'lib/bsv/wallet/client.rb', line 42 def proof_store @proof_store end |
#storage ⇒ Store (readonly)
Returns the underlying persistence adapter.
36 37 38 |
# File 'lib/bsv/wallet/client.rb', line 36 def storage @storage end |
#substrate ⇒ Interface? (readonly)
Returns the optional substrate for remote wallet delegation.
51 52 53 |
# File 'lib/bsv/wallet/client.rb', line 51 def substrate @substrate end |
Instance Method Details
#balance(basket: nil) ⇒ Integer
Returns the total spendable satoshis across all baskets (or a named basket).
186 187 188 |
# File 'lib/bsv/wallet/client.rb', line 186 def balance(basket: nil) @storage.find_spendable_outputs(basket: basket).sum { |o| o[:satoshis].to_i } end |
#broadcast_enabled? ⇒ Boolean
Returns true when broadcast is available.
112 113 114 |
# File 'lib/bsv/wallet/client.rb', line 112 def broadcast_enabled? @broadcast_queue.broadcast_enabled? end |
#set_wallet_change_params(count:, satoshis:) ⇒ Object
Configures the target UTXO pool parameters for change generation.
204 205 206 207 208 209 |
# File 'lib/bsv/wallet/client.rb', line 204 def set_wallet_change_params(count:, satoshis:) raise InvalidParameterError.new('count', 'a positive Integer') unless count.is_a?(Integer) && count.positive? raise InvalidParameterError.new('satoshis', 'a positive Integer') unless satoshis.is_a?(Integer) && satoshis.positive? @storage.store_setting('change_params', { count: count, satoshis: satoshis }) end |
#spendable_balance(basket: nil) ⇒ Integer
Returns the total satoshis of outputs the wallet can automatically spend.
194 195 196 197 198 |
# File 'lib/bsv/wallet/client.rb', line 194 def spendable_balance(basket: nil) @storage.find_spendable_outputs(basket: basket) .select { |o| (o[:derivation_prefix] && o[:derivation_suffix] && o[:sender_identity_key]) || o[:derivation_type]&.to_s == 'identity' } .sum { |o| o[:satoshis].to_i } end |
#sync_utxos ⇒ Integer
Discovers UTXOs on-chain for the wallet’s identity address and imports any that are not already in local storage.
Requires either a substrate or a chain_data_source. When both are present, the substrate takes priority.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/bsv/wallet/client.rb', line 125 def sync_utxos return @substrate.sync_utxos if @substrate raise UnsupportedActionError, 'sync_utxos requires a chain_data_source or remote substrate' unless @chain_data_source address = identity_address utxos = @chain_data_source.fetch_utxos(address) return 0 if utxos.empty? # Group UTXOs by tx_hash to minimise WoC API calls — rate limiting # is aggressive and penalties are harsh, so one fetch per transaction # is far better than one fetch per UTXO. tx_cache = {} new_utxos = utxos.uniq { |u| "#{u.tx_hash}.#{u.tx_pos}" } .reject { |u| output_exists?("#{u.tx_hash}.#{u.tx_pos}") } return 0 if new_utxos.empty? new_utxos.each do |utxo| tx = tx_cache[utxo.tx_hash] ||= @chain_data_source.fetch_transaction(utxo.tx_hash) pos = utxo.tx_pos unless pos.is_a?(Integer) && pos >= 0 && pos < tx.outputs.length raise WalletError, "Invalid tx_pos #{pos.inspect} for #{utxo.tx_hash} (#{tx.outputs.length} outputs)" end output = tx.outputs[pos] output_satoshis = output.satoshis # The transaction output is the authoritative source for satoshis — # a mismatch with the UTXO API would produce invalid sighashes. if !utxo.satoshis.nil? && utxo.satoshis != output_satoshis raise WalletError, "UTXO value mismatch for #{utxo.tx_hash}.#{pos}: " \ "chain reported #{utxo.satoshis}, tx output is #{output_satoshis}" end locking_script_hex = output.locking_script.to_hex outpoint = "#{utxo.tx_hash}.#{utxo.tx_pos}" @storage.store_output({ outpoint: outpoint, satoshis: output_satoshis, locking_script: locking_script_hex, basket: 'default', tags: [], derivation_type: :identity, state: :spendable, source_tx_hex: tx.to_hex }) @storage.store_transaction(utxo.tx_hash, tx.to_hex) end new_utxos.length end |
#utxo_pool(name:, target_count: 20, target_satoshis: 10_000, low_water_mark: 0.5) ⇒ LocalPool
Creates a UTXO pool for high-frequency transaction pre-allocation.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/bsv/wallet/client.rb', line 218 def utxo_pool(name:, target_count: 20, target_satoshis: 10_000, low_water_mark: 0.5) basket = "pool:#{name}" Validators.validate_basket!(basket) raise WalletError, 'utxo_pool requires a broadcaster for replenishment' unless broadcast_enabled? threshold = (target_count * low_water_mark).ceil pool = LocalPool.new( name: name, storage: @storage, wallet_client: self, target_count: target_count, target_satoshis: target_satoshis, low_water_mark: threshold ) worker = ReplenishmentWorker.new( pool: pool, wallet_client: self ) pool.replenisher = worker worker.start pool end |