btcruby 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.travis.yml +7 -0
  4. data/FAQ.md +7 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +18 -0
  7. data/HOWTO.md +17 -0
  8. data/LICENSE +19 -0
  9. data/README.md +59 -0
  10. data/Rakefile +6 -0
  11. data/TODO.txt +40 -0
  12. data/bin/console +19 -0
  13. data/btcruby.gemspec +20 -0
  14. data/documentation/address.md +73 -0
  15. data/documentation/base58.md +52 -0
  16. data/documentation/block.md +127 -0
  17. data/documentation/block_header.md +120 -0
  18. data/documentation/constants.md +88 -0
  19. data/documentation/data.md +54 -0
  20. data/documentation/diagnostics.md +90 -0
  21. data/documentation/extensions.md +76 -0
  22. data/documentation/hash_functions.md +58 -0
  23. data/documentation/hash_id.md +22 -0
  24. data/documentation/index.md +230 -0
  25. data/documentation/key.md +177 -0
  26. data/documentation/keychain.md +180 -0
  27. data/documentation/network.md +75 -0
  28. data/documentation/opcode.md +220 -0
  29. data/documentation/openssl.md +7 -0
  30. data/documentation/p2pkh.md +71 -0
  31. data/documentation/p2sh.md +64 -0
  32. data/documentation/proof_of_work.md +84 -0
  33. data/documentation/script.md +280 -0
  34. data/documentation/signature.md +71 -0
  35. data/documentation/transaction.md +213 -0
  36. data/documentation/transaction_builder.md +188 -0
  37. data/documentation/transaction_input.md +133 -0
  38. data/documentation/transaction_output.md +130 -0
  39. data/documentation/wif.md +72 -0
  40. data/documentation/wire_format.md +70 -0
  41. data/lib/btcruby/address.rb +296 -0
  42. data/lib/btcruby/base58.rb +108 -0
  43. data/lib/btcruby/big_number.rb +47 -0
  44. data/lib/btcruby/block.rb +170 -0
  45. data/lib/btcruby/block_header.rb +231 -0
  46. data/lib/btcruby/constants.rb +59 -0
  47. data/lib/btcruby/currency_formatter.rb +64 -0
  48. data/lib/btcruby/data.rb +98 -0
  49. data/lib/btcruby/diagnostics.rb +92 -0
  50. data/lib/btcruby/errors.rb +8 -0
  51. data/lib/btcruby/extensions.rb +65 -0
  52. data/lib/btcruby/hash_functions.rb +54 -0
  53. data/lib/btcruby/hash_id.rb +18 -0
  54. data/lib/btcruby/key.rb +517 -0
  55. data/lib/btcruby/keychain.rb +464 -0
  56. data/lib/btcruby/network.rb +73 -0
  57. data/lib/btcruby/opcode.rb +197 -0
  58. data/lib/btcruby/open_assets/asset.rb +35 -0
  59. data/lib/btcruby/open_assets/asset_address.rb +49 -0
  60. data/lib/btcruby/open_assets/asset_definition.rb +75 -0
  61. data/lib/btcruby/open_assets/asset_id.rb +24 -0
  62. data/lib/btcruby/open_assets/asset_marker.rb +94 -0
  63. data/lib/btcruby/open_assets/asset_processor.rb +377 -0
  64. data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
  65. data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
  66. data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
  67. data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
  68. data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
  69. data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
  70. data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
  71. data/lib/btcruby/open_assets.rb +26 -0
  72. data/lib/btcruby/openssl.rb +536 -0
  73. data/lib/btcruby/proof_of_work.rb +110 -0
  74. data/lib/btcruby/safety.rb +26 -0
  75. data/lib/btcruby/script.rb +733 -0
  76. data/lib/btcruby/signature_hashtype.rb +37 -0
  77. data/lib/btcruby/transaction.rb +511 -0
  78. data/lib/btcruby/transaction_builder/errors.rb +15 -0
  79. data/lib/btcruby/transaction_builder/provider.rb +54 -0
  80. data/lib/btcruby/transaction_builder/result.rb +73 -0
  81. data/lib/btcruby/transaction_builder/signer.rb +28 -0
  82. data/lib/btcruby/transaction_builder.rb +520 -0
  83. data/lib/btcruby/transaction_input.rb +298 -0
  84. data/lib/btcruby/transaction_outpoint.rb +30 -0
  85. data/lib/btcruby/transaction_output.rb +315 -0
  86. data/lib/btcruby/version.rb +3 -0
  87. data/lib/btcruby/wif.rb +118 -0
  88. data/lib/btcruby/wire_format.rb +362 -0
  89. data/lib/btcruby.rb +44 -2
  90. data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
  91. data/sample_code/creating_a_transaction_manually.rb +44 -0
  92. data/sample_code/generating_an_address.rb +20 -0
  93. data/sample_code/using_transaction_builder.rb +49 -0
  94. data/spec/address_spec.rb +206 -0
  95. data/spec/all.rb +6 -0
  96. data/spec/base58_spec.rb +83 -0
  97. data/spec/block_header_spec.rb +18 -0
  98. data/spec/block_spec.rb +18 -0
  99. data/spec/currency_formatter_spec.rb +46 -0
  100. data/spec/data_spec.rb +50 -0
  101. data/spec/diagnostics_spec.rb +41 -0
  102. data/spec/key_spec.rb +205 -0
  103. data/spec/keychain_spec.rb +261 -0
  104. data/spec/network_spec.rb +48 -0
  105. data/spec/open_assets/asset_address_spec.rb +33 -0
  106. data/spec/open_assets/asset_id_spec.rb +15 -0
  107. data/spec/open_assets/asset_marker_spec.rb +47 -0
  108. data/spec/open_assets/asset_processor_spec.rb +567 -0
  109. data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
  110. data/spec/open_assets/asset_transaction_spec.rb +70 -0
  111. data/spec/proof_of_work_spec.rb +53 -0
  112. data/spec/script_spec.rb +66 -0
  113. data/spec/spec_helper.rb +8 -0
  114. data/spec/transaction_builder_spec.rb +338 -0
  115. data/spec/transaction_spec.rb +162 -0
  116. data/spec/wire_format_spec.rb +283 -0
  117. metadata +141 -7
@@ -0,0 +1,298 @@
1
+ # Transaction input (aka "txin") represents a reference to another transaction's output.
2
+ # Reference is defined by tx hash + tx output index.
3
+ # Signature script is used to prove ownership of the corresponding tx output.
4
+ # Sequence is used to require different signatures when tx is updated. It is only relevant when tx lock_time > 0.
5
+ module BTC
6
+ class TransactionInput
7
+
8
+ INVALID_INDEX = 0xFFFFFFFF # aka "(unsigned int) -1" in BitcoinQT.
9
+ MAX_SEQUENCE = 0xFFFFFFFF
10
+ ZERO_HASH256 = "\x00".b*32
11
+
12
+ # Hash of the previous transaction (raw binary hash)
13
+ attr_accessor :previous_hash
14
+
15
+ # ID of a previous transaction (reversed hash in hex)
16
+ attr_accessor :previous_id
17
+
18
+ # Index of the previous transaction's output (uint32_t).
19
+ attr_accessor :previous_index
20
+
21
+ # BTC::Script instance that proves ownership of the previous transaction output.
22
+ # We intentionally do not call it "script" to avoid accidental confusion with
23
+ # TransactionOutput#script.
24
+ attr_accessor :signature_script
25
+
26
+ # Binary String contained in signature script in coinbase input.
27
+ # Returns nil if it is not a coinbase input.
28
+ attr_accessor :coinbase_data
29
+
30
+ # Input sequence (uint32_t). Default is maximum value 0xFFFFFFFF.
31
+ # Sequence is used to update a timelocked tx stored in memory of the nodes. It is only relevant when tx lock_time > 0.
32
+ # Currently, for DoS and security reasons, nodes do not store timelocked transactions making the sequence number meaningless.
33
+ attr_accessor :sequence
34
+
35
+ # Binary representation of the input in wire format (aka payload).
36
+ attr_reader :data
37
+
38
+ # Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
39
+ attr_reader :dictionary
40
+
41
+ # Optional reference to the owning transaction.
42
+ # It is set in `tx.add_input` and reset to nil in `tx.remove_all_inputs`.
43
+ # Default is nil.
44
+ attr_accessor :transaction
45
+
46
+ # Optional index within owning transaction.
47
+ # It is set in `tx.add_input` and reset to nil in `tx.remove_all_inputs`.
48
+ # Default is nil.
49
+ attr_accessor :index
50
+
51
+ # Optional attribute referencing an output that this input is spending.
52
+ attr_accessor :transaction_output
53
+
54
+ # Optional attribute containing a value in the corresponding output (in satoshis).
55
+ # Default is transaction_output.value or nil.
56
+ attr_accessor :value
57
+
58
+ # Initializes transaction input with its attributes. Every attribute has a valid default value.
59
+ def initialize(data: nil,
60
+ stream: nil,
61
+ dictionary: nil,
62
+ previous_hash: ZERO_HASH256,
63
+ previous_id: nil,
64
+ previous_index: INVALID_INDEX,
65
+ coinbase_data: nil,
66
+ signature_script: BTC::Script.new,
67
+ sequence: MAX_SEQUENCE,
68
+
69
+ # optional attributes
70
+ transaction: nil,
71
+ transaction_output: nil,
72
+ value: nil)
73
+ if stream || data
74
+ init_with_stream(stream || StringIO.new(data))
75
+ elsif dictionary
76
+ init_with_dictionary(dictionary)
77
+ else
78
+ @previous_hash = previous_hash || ZERO_HASH256
79
+ @previous_hash = BTC.hash_from_id(previous_id) if previous_id
80
+ @previous_index = previous_index || INVALID_INDEX
81
+ @coinbase_data = coinbase_data
82
+ @signature_script = signature_script || BTC::Script.new
83
+ @sequence = sequence || MAX_SEQUENCE
84
+ end
85
+
86
+ @transaction = transaction
87
+
88
+ @transaction_output = transaction_output
89
+ # Try to set outpoint data based on transaction output.
90
+ if @transaction_output
91
+ if @previous_hash == ZERO_HASH256
92
+ @previous_hash = @transaction_output.transaction_hash || ZERO_HASH256
93
+ end
94
+ if @previous_index == INVALID_INDEX
95
+ @previous_index = @transaction_output.index || INVALID_INDEX
96
+ end
97
+ end
98
+ @value = value
99
+ end
100
+
101
+ def init_with_stream(stream)
102
+ if stream.eof?
103
+ raise ArgumentError, "Can't parse transaction input from stream because it is already closed."
104
+ end
105
+
106
+ if !(@previous_hash = stream.read(32)) || @previous_hash.bytesize != 32
107
+ raise ArgumentError, "Failed to read 32-byte previous_hash from stream."
108
+ end
109
+
110
+ if !(@previous_index = BTC::WireFormat.read_uint32le(stream: stream).first)
111
+ raise ArgumentError, "Failed to read previous_index from stream."
112
+ end
113
+
114
+ is_coinbase = (@previous_hash == ZERO_HASH256 && @previous_index == INVALID_INDEX)
115
+
116
+ if !(scriptdata = BTC::WireFormat.read_string(stream: stream).first)
117
+ raise ArgumentError, "Failed to read signature_script data from stream."
118
+ end
119
+
120
+ @coinbase_data = nil
121
+ @signature_script = nil
122
+
123
+ if is_coinbase
124
+ @coinbase_data = scriptdata
125
+ else
126
+ @signature_script = BTC::Script.new(data: scriptdata)
127
+ end
128
+
129
+ if !(@sequence = BTC::WireFormat.read_uint32le(stream: stream).first)
130
+ raise ArgumentError, "Failed to read sequence from stream."
131
+ end
132
+ end
133
+
134
+ def init_with_dictionary(dict)
135
+ raise ArgumentError, "Dictionary is missing" if !dict
136
+
137
+ # Supports bitcoin-QT RPC format
138
+
139
+ if dict["prev_out"] && !dict["prev_out"].is_a?(Hash)
140
+ raise ArgumentError, "prev_out is not a dictionary."
141
+ end
142
+
143
+ prevhash = ZERO_HASH256
144
+ previndex = INVALID_INDEX
145
+ script = nil
146
+ seq = MAX_SEQUENCE
147
+
148
+ if dict["prev_out"]
149
+ if hashhex = dict["prev_out"]["hash"]
150
+ prevhash = BTC::Data.data_from_hex(hashhex)
151
+ if prevhash.bytesize != 32
152
+ raise ArgumentError, "prev_out.hash is not 32 bytes long."
153
+ end
154
+ end
155
+
156
+ if n = dict["prev_out"]["n"]
157
+ index = n.to_i
158
+ if index < 0 || index > 0xffffffff
159
+ raise ArgumentError, "prev_out.n is out of bounds (#{index})."
160
+ end
161
+ previndex = index
162
+ end
163
+ end
164
+
165
+ coinbase_data = nil
166
+ script = nil
167
+ if hex = dict["coinbase"]
168
+ coinbase_data = BTC::Data.data_from_hex(hex)
169
+ elsif dict["scriptSig"]
170
+ if dict["scriptSig"].is_a?(Hash)
171
+ if hex = dict["scriptSig"]["hex"]
172
+ script = BTC::Script.new(data: BTC::Data.data_from_hex(hex))
173
+ end
174
+ end
175
+ end
176
+
177
+ if dict["sequence"]
178
+ seq = dict["sequence"].to_i
179
+ if seq < 0 || seq > MAX_SEQUENCE
180
+ raise ArgumentError, "sequence is out of bounds (#{index})."
181
+ end
182
+ end
183
+
184
+ @previous_hash = prevhash
185
+ @previous_index = previndex,
186
+ @coinbase_data = coinbase_data
187
+ @signature_script = script
188
+ @sequence = seq
189
+ end
190
+
191
+ def self.with_data(data)
192
+ raise ArgumentError, "Use TransactionInput.new(data: ...) instead"
193
+ end
194
+
195
+ def self.with_stream(stream)
196
+ raise ArgumentError, "Use TransactionInput.new(stream: ...) instead"
197
+ end
198
+
199
+ def self.with_dictionary(dict)
200
+ raise ArgumentError, "Use TransactionInput.new(dictionary: ...) instead"
201
+ end
202
+
203
+ # Returns true if this input is a coinbase input.
204
+ def coinbase?
205
+ return self.previous_index == INVALID_INDEX && self.previous_hash == ZERO_HASH256
206
+ end
207
+
208
+ def previous_id
209
+ BTC.id_from_hash(self.previous_hash)
210
+ end
211
+
212
+ def previous_id=(txid)
213
+ self.previous_hash = BTC.hash_from_id(txid)
214
+ end
215
+
216
+ def value
217
+ return @value if @value
218
+ return @transaction_output.value if @transaction_output
219
+ return nil
220
+ end
221
+
222
+ def data
223
+ data = "".b
224
+ data << BTC::Data.ensure_binary_encoding(self.previous_hash)
225
+ data << BTC::WireFormat.encode_uint32le(self.previous_index)
226
+ if coinbase?
227
+ data << BTC::WireFormat.encode_string(self.coinbase_data)
228
+ else
229
+ data << BTC::WireFormat.encode_string(self.signature_script.data)
230
+ end
231
+ data << BTC::WireFormat.encode_uint32le(self.sequence)
232
+ data
233
+ end
234
+
235
+ def dictionary
236
+ dict = {}
237
+
238
+ dict["prev_out"] = {
239
+ "hash" => BTC::Data.hex_from_data(self.previous_hash),
240
+ "n" => self.previous_index
241
+ }
242
+
243
+ if self.coinbase?
244
+ dict["coinbase"] = BTC::Data.hex_from_data(self.coinbase_data)
245
+ else
246
+ dict["scriptSig"] = {
247
+ "asm" => self.signature_script.to_s,
248
+ "hex" => BTC::Data.hex_from_data(self.signature_script.data)
249
+ }
250
+ end
251
+
252
+ dict["sequence"] = self.sequence
253
+
254
+ dict
255
+ end
256
+
257
+ def to_h
258
+ self.dictionary
259
+ end
260
+
261
+ def to_s
262
+ BTC::Data.hex_from_data(self.data)
263
+ end
264
+
265
+ def ==(other)
266
+ return true if super(other)
267
+ return true if self.data == other.data
268
+ return false
269
+ end
270
+
271
+ # Makes a deep copy of a transaction input
272
+ def dup
273
+ TransactionInput.new(previous_hash: @previous_hash.dup,
274
+ previous_index: @previous_index,
275
+ signature_script: @signature_script ? @signature_script.dup : nil,
276
+ coinbase_data: @coinbase_data,
277
+ sequence: @sequence,
278
+ transaction: @transaction,
279
+ transaction_output: @transaction_output, # not dup-ing txout because it's a transient object without #==
280
+ value: @value)
281
+ end
282
+
283
+ def inspect(style = :full)
284
+ if style == :full
285
+ %{#<#{self.class.name} prev:#{self.previous_id}[#{self.previous_index}]} +
286
+ %{ script:#{self.signature_script.to_s.inspect}} +
287
+ %{ seq:#{self.sequence}} +
288
+ %{>}
289
+ else
290
+ %{#<#{self.class.name} prev:#{self.previous_id[0,10]}[#{self.previous_index}]} +
291
+ %{ script:#{self.signature_script.to_s.inspect}} +
292
+ (self.sequence != MAX_SEQUENCE ? %{ seq:#{self.sequence}} : %{}) +
293
+ %{>}
294
+ end
295
+ end
296
+
297
+ end
298
+ end
@@ -0,0 +1,30 @@
1
+ module BTC
2
+ # Represents a reference to a previous transaction.
3
+ class TransactionOutpoint
4
+ attr_accessor :transaction_hash
5
+ attr_accessor :transaction_id
6
+ attr_accessor :index
7
+
8
+ def initialize(transaction_hash: nil, transaction_id: nil, index: 0)
9
+ @transaction_hash = transaction_hash
10
+ self.transaction_id = transaction_id if transaction_id
11
+ @index = index
12
+ end
13
+
14
+ def transaction_id=(txid)
15
+ self.transaction_hash = BTC.hash_from_id(txid)
16
+ end
17
+
18
+ def transaction_id
19
+ BTC.id_from_hash(self.transaction_hash)
20
+ end
21
+
22
+ def outpoint_id
23
+ %{#{transaction_id}:#{index}}
24
+ end
25
+
26
+ def to_s
27
+ outpoint_id
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,315 @@
1
+ # Transaction output (aka "tx out") is a value with rules attached in form of a script.
2
+ # To spend money one need to choose a transaction output and provide an appropriate
3
+ # input which makes the script execute with success.
4
+ module BTC
5
+ class TransactionOutput
6
+
7
+ # Core attributes.
8
+
9
+ # Value of output in satoshis.
10
+ attr_accessor :value
11
+
12
+ # BTC::Script defining redemption rules for this output (aka scriptPubKey or pk_script)
13
+ attr_accessor :script
14
+
15
+
16
+ # Derived attributes.
17
+
18
+ # Serialized binary form of the output (payload)
19
+ attr_reader :data
20
+
21
+ # Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
22
+ attr_reader :dictionary
23
+
24
+
25
+ # Optional attributes.
26
+
27
+ # These are not derived from tx data, but attached externally (e.g. via external APIs).
28
+ # 'index', 'confirmations' and 'transaction_hash' are optional attributes updated in certain context.
29
+ # E.g. when loading unspent outputs from Chain.com, all these attributes will be set.
30
+ # index and transaction_hash are kept up to date when output is added/removed from the transaction.
31
+
32
+ # Reference to the owning transaction. It is set on tx.add_output() and
33
+ # reset to nil on tx.remove_all_outputs. Default is nil.
34
+ attr_accessor :transaction
35
+
36
+ # Identifier of the transaction. Default is nil.
37
+ attr_accessor :transaction_hash
38
+
39
+ # Transaction ID. Always in sync with transaction_hash. Default is nil.
40
+ attr_accessor :transaction_id
41
+
42
+ # Index of this output in its transaction. Default is nil (unknown).
43
+ attr_accessor :index
44
+
45
+ # Binary hash of the block at which transaction was included.
46
+ # If not confirmed or not available, equals nil.
47
+ attr_accessor :block_hash
48
+ attr_accessor :block_id
49
+
50
+ # Height of the block at which transaction was included.
51
+ # If not confirmed equals -1.
52
+ # Note: `block_height` might not be provided by some APIs while `confirmations` may be.
53
+ # Default value is derived from `transaction` if possible or equals nil.
54
+ attr_accessor :block_height
55
+
56
+ # Time of the block at which tx was included (::Time instance or nil).
57
+ # Default value is derived from `transaction` if possible or equals nil.
58
+ attr_accessor :block_time
59
+
60
+ # Number of confirmations.
61
+ # Default value is derived from `transaction` if possible or equals nil.
62
+ attr_accessor :confirmations
63
+
64
+ # If available, returns whether this output is spent (true or false).
65
+ # Default is nil.
66
+ # See also `spent_confirmations`.
67
+ attr_accessor :spent
68
+
69
+ # If this transaction is spent, contains number of confirmations of the spending transaction.
70
+ # Returns nil if not available or output is not spent.
71
+ # Returns 0 if spending transaction is unconfirmed.
72
+ attr_accessor :spent_confirmations
73
+
74
+ def initialize(data: nil,
75
+ stream: nil,
76
+ dictionary: nil,
77
+ value: 0,
78
+ script: BTC::Script.new,
79
+
80
+ # optional attributes
81
+ transaction: nil,
82
+ transaction_hash: nil,
83
+ transaction_id: nil,
84
+ index: nil,
85
+ block_hash: nil,
86
+ block_id: nil,
87
+ block_height: nil,
88
+ block_time: nil,
89
+ confirmations: nil,
90
+ spent: nil,
91
+ spent_confirmations: nil)
92
+
93
+ if stream || data
94
+ init_with_stream(stream || StringIO.new(data))
95
+ elsif dictionary
96
+ init_with_dictionary(dictionary)
97
+ else
98
+ @value = value || 0
99
+ @script = script || BTC::Script.new
100
+ end
101
+
102
+ @transaction = transaction
103
+ @transaction_hash = transaction_hash
104
+ @transaction_hash = BTC.hash_from_id(transaction_id) if transaction_id
105
+ @index = index
106
+ @block_hash = block_hash
107
+ @block_hash = BTC.hash_from_id(block_id) if block_id
108
+ @block_height = block_height
109
+ @block_time = block_time
110
+ @confirmations = confirmations
111
+ @spent = spent
112
+ @spent_confirmations = spent_confirmations
113
+ end
114
+
115
+ def init_with_stream(stream)
116
+ if stream.eof?
117
+ raise ArgumentError, "Can't parse transaction output from stream because it is already closed."
118
+ end
119
+
120
+ # Read value
121
+ if !(@value = BTC::WireFormat.read_int64le(stream: stream).first)
122
+ raise ArgumentError, "Failed to read output value from stream."
123
+ end
124
+
125
+ # Read script
126
+ if !(scriptdata = BTC::WireFormat.read_string(stream: stream).first)
127
+ raise ArgumentError, "Failed to read output script data from stream."
128
+ end
129
+
130
+ @script = BTC::Script.new(data: scriptdata)
131
+ end
132
+
133
+ def init_with_dictionary(dict)
134
+ @value = 0
135
+ if amount_string = dict["value"]
136
+ @value = CurrencyFormatter.btc_long_formatter.number_from_string(amount_string)
137
+ if !@value
138
+ raise ArgumentError, "Failed to parse bitcoin amount from dictionary 'value': #{amount_string.inspect}"
139
+ end
140
+ end
141
+
142
+ @script = nil
143
+ if dict["scriptPubKey"] && dict["scriptPubKey"].is_a?(Hash)
144
+ if hex = dict["scriptPubKey"]["hex"]
145
+ @script = Script.new(data: BTC::Data.data_from_hex(hex))
146
+ if !@script
147
+ raise ArgumentError, "Failed to parse script from scriptPubKey.hex"
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ def self.with_data(data)
154
+ raise ArgumentError, "Use TransactionOutput.new(data: ...) instead"
155
+ end
156
+
157
+ def self.with_stream(stream)
158
+ raise ArgumentError, "Use TransactionOutput.new(stream: ...) instead"
159
+ end
160
+
161
+ def self.with_dictionary(dict)
162
+ raise ArgumentError, "Use TransactionOutput.new(dictionary: ...) instead"
163
+ end
164
+
165
+ def data
166
+ data = "".b
167
+ data << BTC::WireFormat.encode_int64le(self.value)
168
+ data << BTC::WireFormat.encode_string(self.script.data)
169
+ data
170
+ end
171
+
172
+ def dictionary
173
+ {
174
+ "value" => CurrencyFormatter.btc_long_formatter.string_from_number(self.value),
175
+ "scriptPubKey" => {
176
+ "asm" => self.script.to_s,
177
+ "hex" => BTC::Data.hex_from_data(self.script.data)
178
+ }
179
+ }
180
+ end
181
+
182
+ def transaction=(tx)
183
+ @transaction = tx
184
+ @transaction_hash = nil
185
+ @outpoint = nil
186
+ end
187
+
188
+ def index=(i)
189
+ @index = i
190
+ @outpoint = nil
191
+ end
192
+
193
+ def transaction_hash
194
+ return @transaction_hash if @transaction_hash
195
+ return @transaction.transaction_hash if @transaction
196
+ return nil
197
+ end
198
+
199
+ def transaction_hash=(txhash)
200
+ @transaction_hash = txhash
201
+ @outpoint = nil
202
+ end
203
+
204
+ def transaction_id=(txid)
205
+ self.transaction_hash = BTC.hash_from_id(txid)
206
+ end
207
+
208
+ def transaction_id
209
+ BTC.id_from_hash(self.transaction_hash)
210
+ end
211
+
212
+ def outpoint
213
+ return @outpoint if @outpoint
214
+ if transaction_hash && index
215
+ @outpoint = TransactionOutpoint.new(transaction_hash: transaction_hash, index: index)
216
+ end
217
+ @outpoint
218
+ end
219
+
220
+ def outpoint_id
221
+ outpoint.outpoint_id
222
+ end
223
+
224
+ def block_id
225
+ BTC.id_from_hash(self.block_hash)
226
+ end
227
+
228
+ def block_id=(block_id)
229
+ self.block_hash = BTC.hash_from_id(block_id)
230
+ end
231
+
232
+ def block_hash
233
+ return @block_hash if @block_hash
234
+ return @transaction.block_hash if @transaction
235
+ return nil
236
+ end
237
+
238
+ def block_height
239
+ return @block_height if @block_height
240
+ return @transaction.block_height if @transaction
241
+ return nil
242
+ end
243
+
244
+ def block_time
245
+ return @block_time if @block_time
246
+ return @transaction.block_time if @transaction
247
+ return nil
248
+ end
249
+
250
+ def confirmations
251
+ return @confirmations if @confirmations
252
+ return @transaction.confirmations if @transaction
253
+ return nil
254
+ end
255
+
256
+ # Returns `true` if this transaction output contains an Open Assets marker.
257
+ # Does not perform expensive validation.
258
+ # Use this method to quickly filter out non-asset transactions.
259
+ def open_assets_marker?
260
+ self.script.open_assets_marker?
261
+ end
262
+
263
+ def dust?(relay_fee_rate = Transaction::DEFAULT_RELAY_FEE_RATE)
264
+ return self.value < self.dust_limit(relay_fee_rate)
265
+ end
266
+
267
+ def dust_limit(relay_fee_rate = Transaction::DEFAULT_RELAY_FEE_RATE)
268
+ # "Dust" is defined in terms of Transaction::DEFAULT_RELAY_FEE_RATE,
269
+ # which has units satoshis-per-kilobyte.
270
+ # If you'd pay more than 1/3 in fees
271
+ # to spend something, then we consider it dust.
272
+ # A typical txout is 34 bytes big, and will
273
+ # need a TransactionInput of at least 148 bytes to spend:
274
+ # so dust is a txout less than 546 satoshis (3*(34+148))
275
+ # with default relay_fee_rate.
276
+ size = self.data.bytesize + 148
277
+ return 3*Transaction.compute_fee(size, fee_rate: relay_fee_rate)
278
+ end
279
+
280
+ def to_h
281
+ self.dictionary
282
+ end
283
+
284
+ def to_s
285
+ BTC::Data.hex_from_data(self.data)
286
+ end
287
+
288
+ def ==(other)
289
+ return true if super(other)
290
+ return true if self.data == other.data
291
+ return false
292
+ end
293
+
294
+ # Makes a deep copy of a transaction output
295
+ def dup
296
+ TransactionOutput.new(value: @value,
297
+ script: @script.dup,
298
+ transaction: @transaction,
299
+ transaction_hash: @transaction_hash,
300
+ index: @index,
301
+ block_hash: @block_hash,
302
+ block_height: @block_height,
303
+ block_time: @block_time,
304
+ confirmations: @confirmations,
305
+ spent: @spent,
306
+ spent_confirmations: @spent_confirmations)
307
+ end
308
+
309
+ def inspect(style = :full)
310
+ %{#<#{self.class.name} value:#{CurrencyFormatter.btc_long_formatter.string_from_number(self.value)}} +
311
+ %{ script:#{self.script.to_s.inspect}>}
312
+ end
313
+
314
+ end
315
+ end
@@ -0,0 +1,3 @@
1
+ module BTC
2
+ VERSION = "1.0.0".freeze
3
+ end