bsv-sdk 0.13.0 → 0.14.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 +23 -0
- data/lib/bsv/auth/certificate.rb +1 -1
- data/lib/bsv/network/whats_on_chain.rb +41 -2
- data/lib/bsv/transaction/transaction.rb +52 -7
- data/lib/bsv/transaction/verification_error.rb +11 -0
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv-sdk.rb +1 -2
- metadata +1 -5
- data/lib/bsv/messages.rb +0 -16
- data/lib/bsv/wallet/insufficient_funds_error.rb +0 -15
- data/lib/bsv/wallet/wallet.rb +0 -120
- data/lib/bsv/wallet.rb +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4532a163439721455da80290d8b02342e597a01bb1e00b259d13343f8f9bd592
|
|
4
|
+
data.tar.gz: b7cfda4a8084b96c28f34afb6829fcba9f5be0dabb01fca536147bfe2c66bad8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed0b97db3cec8503eba509f232a463d8566373b34ccaf5fe7cd130a72e712cde17b083b4ce94ef64fa84786d6e6aec8965423ca6b8a7471ada70ebd522956e2a
|
|
7
|
+
data.tar.gz: 0747a93bef059a8eccf0f504e703d20ddc875f8c562db8f01d3af4050bf7562deb262eb4494db4d98b78665f91274ed77754fa76e99254b8592b7e0f1526aa13
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to the `bsv-sdk` 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.14.0 — 2026-04-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `BSV::Network::WhatsOnChain` expanded with `current_height`,
|
|
12
|
+
`get_block_header(height)`, and `valid_root_for_height?(root, height)` —
|
|
13
|
+
a single WhatsOnChain instance now serves as a complete chain data source
|
|
14
|
+
(#596)
|
|
15
|
+
- BEEF-based SPV verification conformance tests (#607)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `Transaction#verify` now raises `VerificationError` for all failure modes:
|
|
19
|
+
`:script_failure` (wraps `ScriptError` with cause chaining),
|
|
20
|
+
`:missing_source` (replaces `ArgumentError`), `:invalid_merkle_proof`,
|
|
21
|
+
`:insufficient_fee`, and `:output_overflow`. Code rescuing `ArgumentError`
|
|
22
|
+
or `ScriptError` from `verify` must switch to `VerificationError` (#608)
|
|
23
|
+
- Removed dead code: `BSV::Wallet::Wallet` (superseded by bsv-wallet gem's
|
|
24
|
+
`Client`), `BSV::Messages` (unused re-export alias), and duplicate
|
|
25
|
+
`BSV::Wallet::InsufficientFundsError` (#594)
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- WhatsOnChain `valid_root_for_height?` YARD doc now correctly documents
|
|
29
|
+
that 404 returns `false` rather than raising
|
|
30
|
+
|
|
8
31
|
## 0.13.0 — 2026-04-21
|
|
9
32
|
|
|
10
33
|
### Added
|
data/lib/bsv/auth/certificate.rb
CHANGED
|
@@ -176,7 +176,7 @@ module BSV
|
|
|
176
176
|
def verify(verifier_wallet = nil)
|
|
177
177
|
raise ArgumentError, 'certificate has no signature to verify' if @signature.nil? || @signature.empty?
|
|
178
178
|
|
|
179
|
-
verifier_wallet ||= BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new)
|
|
179
|
+
verifier_wallet ||= BSV::Wallet::Client.new('anyone', storage: BSV::Wallet::Store::Memory.new, allow_memory_store: true)
|
|
180
180
|
preimage = to_binary(include_signature: false)
|
|
181
181
|
sig_bytes = [@signature].pack('H*').unpack('C*')
|
|
182
182
|
|
|
@@ -5,8 +5,11 @@ module BSV
|
|
|
5
5
|
# WhatsOnChain chain data provider for reading transactions and UTXOs
|
|
6
6
|
# from the BSV network.
|
|
7
7
|
#
|
|
8
|
-
# Any object responding to #fetch_utxos(address)
|
|
9
|
-
# #fetch_transaction(txid)
|
|
8
|
+
# Any object responding to #fetch_utxos(address),
|
|
9
|
+
# #fetch_transaction(txid), #current_height,
|
|
10
|
+
# #get_block_header(height), and optionally
|
|
11
|
+
# #valid_root_for_height?(root_hex, height) can serve as a chain
|
|
12
|
+
# data source;
|
|
10
13
|
# this class implements that contract by delegating to
|
|
11
14
|
# Protocols::WoCREST.
|
|
12
15
|
#
|
|
@@ -62,6 +65,42 @@ module BSV
|
|
|
62
65
|
BSV::Transaction::Transaction.from_hex(result.data)
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
# Return the current blockchain height.
|
|
69
|
+
# @return [Integer]
|
|
70
|
+
# @raise [BSV::Network::ChainProviderError] on network or API error
|
|
71
|
+
def current_height
|
|
72
|
+
result = @protocol.call(:current_height)
|
|
73
|
+
raise_on_error(result)
|
|
74
|
+
|
|
75
|
+
result.data
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Fetch the block header for a given height.
|
|
79
|
+
# @param height [Integer] block height
|
|
80
|
+
# @return [Hash] parsed block header JSON
|
|
81
|
+
# @raise [BSV::Network::ChainProviderError] on network or API error
|
|
82
|
+
def get_block_header(height)
|
|
83
|
+
result = @protocol.call(:get_block_header, height)
|
|
84
|
+
raise_on_error(result)
|
|
85
|
+
|
|
86
|
+
result.data
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Verify that a merkle root is valid for the given block height.
|
|
90
|
+
# Returns +false+ when the block is not found (404); raises on other errors.
|
|
91
|
+
# @param root [String] expected merkle root as hex
|
|
92
|
+
# @param height [Integer] block height
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
# @raise [BSV::Network::ChainProviderError] on network or non-404 API error
|
|
95
|
+
def valid_root_for_height?(root, height)
|
|
96
|
+
result = @protocol.call(:valid_root, root, height)
|
|
97
|
+
return false if result.not_found?
|
|
98
|
+
|
|
99
|
+
raise_on_error(result)
|
|
100
|
+
|
|
101
|
+
result.data == true
|
|
102
|
+
end
|
|
103
|
+
|
|
65
104
|
private
|
|
66
105
|
|
|
67
106
|
# Translates a non-success Protocol result into a raised ChainProviderError.
|
|
@@ -550,12 +550,30 @@ module BSV
|
|
|
550
550
|
# provided fee model.
|
|
551
551
|
# 4. Checks that total outputs do not exceed total inputs.
|
|
552
552
|
#
|
|
553
|
+
# == Semantic Divergences from Reference SDKs
|
|
554
|
+
#
|
|
555
|
+
# This implementation raises +VerificationError+ for all failure modes.
|
|
556
|
+
# The TypeScript and Python SDKs return +false+ for script failures and
|
|
557
|
+
# output overflow. The Go SDK propagates errors (not booleans) for script
|
|
558
|
+
# failures, aligning with the Ruby approach.
|
|
559
|
+
#
|
|
560
|
+
# Rationale: raising provides structured error information (+#code+,
|
|
561
|
+
# +#message+, +#cause+) that a boolean cannot convey. Consumers can
|
|
562
|
+
# rescue +VerificationError+ and inspect +#code+ for specifics.
|
|
563
|
+
#
|
|
564
|
+
# Divergence summary:
|
|
565
|
+
# - +:output_overflow+ — Ruby raises; TS/Python return +false+; Go omits the check
|
|
566
|
+
# - +:script_failure+ — Ruby raises; TS/Python return +false+; Go also propagates errors
|
|
567
|
+
# - +:missing_source+ — Ruby raises; consistent with TS/Go/Python (all raise/error)
|
|
568
|
+
#
|
|
553
569
|
# @param chain_tracker [ChainTracker] chain tracker for merkle root validation
|
|
554
570
|
# @param fee_model [FeeModel, nil] optional fee model to validate the root transaction's fee
|
|
555
571
|
# @return [true] on successful verification
|
|
556
|
-
# @raise [
|
|
557
|
-
# @raise [
|
|
558
|
-
# @raise [VerificationError]
|
|
572
|
+
# @raise [VerificationError] with code +:invalid_merkle_proof+ if a merkle proof is invalid
|
|
573
|
+
# @raise [VerificationError] with code +:insufficient_fee+ if the fee is below the model's threshold
|
|
574
|
+
# @raise [VerificationError] with code +:output_overflow+ if outputs exceed inputs
|
|
575
|
+
# @raise [VerificationError] with code +:script_failure+ if script execution fails
|
|
576
|
+
# @raise [VerificationError] with code +:missing_source+ if an input is missing required source data
|
|
559
577
|
def verify(chain_tracker:, fee_model: nil)
|
|
560
578
|
verified = {}
|
|
561
579
|
queue = [self]
|
|
@@ -581,8 +599,26 @@ module BSV
|
|
|
581
599
|
|
|
582
600
|
# Verify each input
|
|
583
601
|
tx.inputs.each_with_index do |input, index|
|
|
602
|
+
# Populate source data from source_transaction when not already set.
|
|
603
|
+
# Matches the TS SDK pattern: sourceOutput is read directly from
|
|
604
|
+
# source_transaction.outputs[sourceOutputIndex] during verify.
|
|
605
|
+
if input.source_transaction
|
|
606
|
+
source_output = input.source_transaction.outputs[input.prev_tx_out_index]
|
|
607
|
+
if source_output
|
|
608
|
+
input.source_locking_script ||= source_output.locking_script
|
|
609
|
+
input.source_satoshis ||= source_output.satoshis
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
|
|
584
613
|
verify_input_requirements(tx, input, index)
|
|
585
|
-
|
|
614
|
+
begin
|
|
615
|
+
tx.verify_input(index)
|
|
616
|
+
rescue BSV::Script::ScriptError => e
|
|
617
|
+
raise VerificationError.new(
|
|
618
|
+
:script_failure,
|
|
619
|
+
"script verification failed for input #{index} of transaction #{tx.txid_hex}: #{e.message}"
|
|
620
|
+
)
|
|
621
|
+
end
|
|
586
622
|
|
|
587
623
|
# Enqueue source transaction for verification if not yet verified
|
|
588
624
|
source_tx = input.source_transaction
|
|
@@ -742,9 +778,18 @@ module BSV
|
|
|
742
778
|
|
|
743
779
|
def verify_input_requirements(tx, input, index)
|
|
744
780
|
tx_id = tx.txid_hex
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
781
|
+
if input.unlocking_script.nil?
|
|
782
|
+
raise VerificationError.new(:missing_source,
|
|
783
|
+
"input #{index} of transaction #{tx_id} has no unlocking script")
|
|
784
|
+
end
|
|
785
|
+
if input.source_locking_script.nil?
|
|
786
|
+
raise VerificationError.new(:missing_source,
|
|
787
|
+
"input #{index} of transaction #{tx_id} has no source locking script")
|
|
788
|
+
end
|
|
789
|
+
return unless input.source_satoshis.nil?
|
|
790
|
+
|
|
791
|
+
raise VerificationError.new(:missing_source,
|
|
792
|
+
"input #{index} of transaction #{tx_id} has no source satoshis")
|
|
748
793
|
end
|
|
749
794
|
|
|
750
795
|
def verify_fee(fee_model)
|
|
@@ -7,6 +7,15 @@ module BSV
|
|
|
7
7
|
# Carries a machine-readable code alongside a human-readable message,
|
|
8
8
|
# matching the typed error pattern used by the Go SDK
|
|
9
9
|
# (ErrInvalidMerklePath, ErrFeeTooLow, ErrScriptVerificationFailed).
|
|
10
|
+
#
|
|
11
|
+
# == Error Codes
|
|
12
|
+
#
|
|
13
|
+
# - +:invalid_merkle_proof+ — merkle path verification failed against the chain tracker
|
|
14
|
+
# - +:insufficient_fee+ — transaction fee is below the fee model's requirement
|
|
15
|
+
# - +:output_overflow+ — total outputs exceed total inputs
|
|
16
|
+
# - +:script_failure+ — script interpreter rejected an input (original +ScriptError+ in +#cause+)
|
|
17
|
+
# - +:missing_source+ — required input data (unlocking script, source locking script,
|
|
18
|
+
# or source satoshis) is absent
|
|
10
19
|
class VerificationError < StandardError
|
|
11
20
|
# @return [Symbol] the error code
|
|
12
21
|
attr_reader :code
|
|
@@ -14,6 +23,8 @@ module BSV
|
|
|
14
23
|
INVALID_MERKLE_PROOF = :invalid_merkle_proof
|
|
15
24
|
INSUFFICIENT_FEE = :insufficient_fee
|
|
16
25
|
OUTPUT_OVERFLOW = :output_overflow
|
|
26
|
+
SCRIPT_FAILURE = :script_failure
|
|
27
|
+
MISSING_SOURCE = :missing_source
|
|
17
28
|
|
|
18
29
|
# @param code [Symbol] error code
|
|
19
30
|
# @param message [String] human-readable description
|
data/lib/bsv/version.rb
CHANGED
data/lib/bsv-sdk.rb
CHANGED
|
@@ -7,12 +7,11 @@ module BSV
|
|
|
7
7
|
autoload :Script, 'bsv/script'
|
|
8
8
|
autoload :Transaction, 'bsv/transaction'
|
|
9
9
|
autoload :Network, 'bsv/network'
|
|
10
|
-
require_relative 'bsv/wallet' # eager — BSV::Wallet may be pre-defined by bsv-wallet gemspec
|
|
11
10
|
autoload :Auth, 'bsv/auth'
|
|
12
11
|
autoload :Overlay, 'bsv/overlay'
|
|
13
12
|
autoload :Identity, 'bsv/identity'
|
|
14
13
|
autoload :Registry, 'bsv/registry'
|
|
15
14
|
autoload :MCP, 'bsv/mcp'
|
|
16
|
-
|
|
15
|
+
|
|
17
16
|
autoload :WireFormat, 'bsv/wire_format'
|
|
18
17
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bsv-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Bettison
|
|
@@ -67,7 +67,6 @@ files:
|
|
|
67
67
|
- lib/bsv/mcp/tools/fetch_utxos.rb
|
|
68
68
|
- lib/bsv/mcp/tools/generate_key.rb
|
|
69
69
|
- lib/bsv/mcp/tools/helpers.rb
|
|
70
|
-
- lib/bsv/messages.rb
|
|
71
70
|
- lib/bsv/network.rb
|
|
72
71
|
- lib/bsv/network/arc.rb
|
|
73
72
|
- lib/bsv/network/broadcast_error.rb
|
|
@@ -163,9 +162,6 @@ files:
|
|
|
163
162
|
- lib/bsv/transaction/var_int.rb
|
|
164
163
|
- lib/bsv/transaction/verification_error.rb
|
|
165
164
|
- lib/bsv/version.rb
|
|
166
|
-
- lib/bsv/wallet.rb
|
|
167
|
-
- lib/bsv/wallet/insufficient_funds_error.rb
|
|
168
|
-
- lib/bsv/wallet/wallet.rb
|
|
169
165
|
- lib/bsv/wire_format.rb
|
|
170
166
|
homepage: https://github.com/sgbett/bsv-ruby-sdk
|
|
171
167
|
licenses:
|
data/lib/bsv/messages.rb
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BSV
|
|
4
|
-
# Namespace providing TS SDK naming parity for messaging primitives.
|
|
5
|
-
#
|
|
6
|
-
# Re-exports {BSV::Primitives::SignedMessage} and {BSV::Primitives::EncryptedMessage}
|
|
7
|
-
# under the +BSV::Messages+ namespace, matching the structure of the TypeScript SDK
|
|
8
|
-
# (ts-sdk/src/messages/index.ts).
|
|
9
|
-
#
|
|
10
|
-
# The canonical implementations remain in +BSV::Primitives+; this module is a
|
|
11
|
-
# lightweight re-export only.
|
|
12
|
-
module Messages
|
|
13
|
-
SignedMessage = BSV::Primitives::SignedMessage
|
|
14
|
-
EncryptedMessage = BSV::Primitives::EncryptedMessage
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BSV
|
|
4
|
-
module Wallet
|
|
5
|
-
class InsufficientFundsError < StandardError
|
|
6
|
-
attr_reader :required, :available
|
|
7
|
-
|
|
8
|
-
def initialize(message = nil, required: nil, available: nil)
|
|
9
|
-
@required = required
|
|
10
|
-
@available = available
|
|
11
|
-
super(message || "insufficient funds: need #{required}, have #{available}")
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
data/lib/bsv/wallet/wallet.rb
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BSV
|
|
4
|
-
module Wallet
|
|
5
|
-
# Holds a private key, sources UTXOs via a chain data provider, and
|
|
6
|
-
# funds and signs P2PKH transactions.
|
|
7
|
-
#
|
|
8
|
-
# The provider is duck-typed — any object responding to
|
|
9
|
-
# #fetch_utxos(address) and #fetch_transaction(txid) qualifies.
|
|
10
|
-
class Wallet
|
|
11
|
-
DUST_THRESHOLD = 1
|
|
12
|
-
|
|
13
|
-
attr_reader :private_key, :provider
|
|
14
|
-
|
|
15
|
-
def initialize(private_key:, provider:)
|
|
16
|
-
@private_key = private_key
|
|
17
|
-
@provider = provider
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def address(network: :mainnet)
|
|
21
|
-
@private_key.public_key.address(network: network)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def balance(network: :mainnet)
|
|
25
|
-
@provider.fetch_utxos(address(network: network)).sum(&:satoshis)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def fund(tx, network: :mainnet, satoshis_per_byte: 0.1)
|
|
29
|
-
utxos = @provider.fetch_utxos(address(network: network))
|
|
30
|
-
output_total = tx.total_output_satoshis
|
|
31
|
-
|
|
32
|
-
# Add a dummy change output so fee estimation accounts for its size
|
|
33
|
-
dummy_change = BSV::Transaction::TransactionOutput.new(
|
|
34
|
-
satoshis: 0, locking_script: locking_script
|
|
35
|
-
)
|
|
36
|
-
tx.add_output(dummy_change)
|
|
37
|
-
|
|
38
|
-
input_total = tx.total_input_satoshis
|
|
39
|
-
funded = false
|
|
40
|
-
|
|
41
|
-
utxos.each do |utxo|
|
|
42
|
-
tx.add_input(build_input(utxo))
|
|
43
|
-
input_total += utxo.satoshis
|
|
44
|
-
|
|
45
|
-
fee = tx.estimated_fee(satoshis_per_byte: satoshis_per_byte)
|
|
46
|
-
if input_total >= output_total + fee
|
|
47
|
-
funded = true
|
|
48
|
-
break
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Remove the dummy change output
|
|
53
|
-
tx.outputs.delete(dummy_change)
|
|
54
|
-
|
|
55
|
-
unless funded
|
|
56
|
-
fee = tx.estimated_fee(satoshis_per_byte: satoshis_per_byte)
|
|
57
|
-
raise InsufficientFundsError.new(required: output_total + fee, available: input_total)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
add_change_if_needed(tx, input_total, output_total, satoshis_per_byte)
|
|
61
|
-
|
|
62
|
-
tx
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def sign(tx)
|
|
66
|
-
tx.sign_all(@private_key)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def fund_and_sign(tx, network: :mainnet, satoshis_per_byte: 0.1)
|
|
70
|
-
fund(tx, network: network, satoshis_per_byte: satoshis_per_byte)
|
|
71
|
-
sign(tx)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def locking_script
|
|
77
|
-
@locking_script ||= BSV::Script::Script.p2pkh_lock(@private_key.public_key.hash160)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def build_input(utxo)
|
|
81
|
-
input = BSV::Transaction::TransactionInput.new(
|
|
82
|
-
prev_tx_id: BSV::Transaction::TransactionInput.txid_from_hex(utxo.tx_hash),
|
|
83
|
-
prev_tx_out_index: utxo.tx_pos
|
|
84
|
-
)
|
|
85
|
-
input.source_satoshis = utxo.satoshis
|
|
86
|
-
input.source_locking_script = locking_script
|
|
87
|
-
input.unlocking_script_template = BSV::Transaction::P2PKH.new(@private_key)
|
|
88
|
-
input
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def add_change_if_needed(tx, input_total, output_total, satoshis_per_byte)
|
|
92
|
-
fee_without_change = tx.estimated_fee(satoshis_per_byte: satoshis_per_byte)
|
|
93
|
-
remainder = input_total - output_total - fee_without_change
|
|
94
|
-
|
|
95
|
-
return if remainder < DUST_THRESHOLD
|
|
96
|
-
|
|
97
|
-
change_output = BSV::Transaction::TransactionOutput.new(
|
|
98
|
-
satoshis: remainder, locking_script: locking_script
|
|
99
|
-
)
|
|
100
|
-
tx.add_output(change_output)
|
|
101
|
-
|
|
102
|
-
# Recalculate: adding the change output increases fee slightly
|
|
103
|
-
new_fee = tx.estimated_fee(satoshis_per_byte: satoshis_per_byte)
|
|
104
|
-
fee_increase = new_fee - fee_without_change
|
|
105
|
-
final_change = remainder - fee_increase
|
|
106
|
-
|
|
107
|
-
if final_change >= DUST_THRESHOLD
|
|
108
|
-
tx.outputs.delete(change_output)
|
|
109
|
-
tx.add_output(
|
|
110
|
-
BSV::Transaction::TransactionOutput.new(
|
|
111
|
-
satoshis: final_change, locking_script: locking_script
|
|
112
|
-
)
|
|
113
|
-
)
|
|
114
|
-
else
|
|
115
|
-
tx.outputs.delete(change_output) # change absorbed by fee
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|