openassets 1.0.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +11 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +23 -0
  10. data/README.md +423 -0
  11. data/Rakefile +5 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/exe/openassets +66 -0
  15. data/lib/openassets.rb +23 -0
  16. data/lib/openassets/api.rb +430 -0
  17. data/lib/openassets/cache.rb +8 -0
  18. data/lib/openassets/cache/output_cache.rb +43 -0
  19. data/lib/openassets/cache/sqlite_base.rb +26 -0
  20. data/lib/openassets/cache/ssl_certificate_cache.rb +44 -0
  21. data/lib/openassets/cache/transaction_cache.rb +33 -0
  22. data/lib/openassets/error.rb +5 -0
  23. data/lib/openassets/medhod_filter.rb +55 -0
  24. data/lib/openassets/protocol.rb +10 -0
  25. data/lib/openassets/protocol/asset_definition.rb +118 -0
  26. data/lib/openassets/protocol/asset_definition_loader.rb +45 -0
  27. data/lib/openassets/protocol/http_asset_definition_loader.rb +27 -0
  28. data/lib/openassets/protocol/marker_output.rb +137 -0
  29. data/lib/openassets/protocol/output_type.rb +27 -0
  30. data/lib/openassets/protocol/transaction_output.rb +136 -0
  31. data/lib/openassets/provider.rb +7 -0
  32. data/lib/openassets/provider/api_error.rb +9 -0
  33. data/lib/openassets/provider/bitcoin_core_provider.rb +103 -0
  34. data/lib/openassets/provider/block_chain_provider_base.rb +22 -0
  35. data/lib/openassets/send_asset_param.rb +17 -0
  36. data/lib/openassets/send_bitcoin_param.rb +13 -0
  37. data/lib/openassets/transaction.rb +11 -0
  38. data/lib/openassets/transaction/dust_output_error.rb +10 -0
  39. data/lib/openassets/transaction/insufficient_asset_quantity_error.rb +7 -0
  40. data/lib/openassets/transaction/insufficient_funds_error.rb +10 -0
  41. data/lib/openassets/transaction/spendable_output.rb +39 -0
  42. data/lib/openassets/transaction/transaction_build_error.rb +9 -0
  43. data/lib/openassets/transaction/transaction_builder.rb +312 -0
  44. data/lib/openassets/transaction/transfer_parameters.rb +41 -0
  45. data/lib/openassets/util.rb +165 -0
  46. data/lib/openassets/version.rb +3 -0
  47. data/openassets.gemspec +33 -0
  48. metadata +244 -0
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new("spec")
5
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "openassets"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "openassets"
5
+ require 'optparse'
6
+ require 'json'
7
+
8
+ options = {}
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: openassets [options] [command]"
11
+
12
+ opts.on("-c PATH", "--config PATH", "config load path") do |path|
13
+ options[:config] = path
14
+ end
15
+ opts.on("-e", "--env", "load config from ENV variablse") do |env|
16
+ options[:env] = env
17
+ end
18
+ opts.on("-v", "--verbose", "verbose output") do |v|
19
+ options[:verbose] = v
20
+ end
21
+ end.parse!
22
+
23
+ puts "Welcome to OpenAssets\n\n"
24
+ if options[:env]
25
+ rpc = {
26
+ :host => ENV['OA_RPC_HOST'] || 'localhost',
27
+ :port => ENV['OA_RPC_PORT'] || 8332,
28
+ :user => ENV['OA_RPC_USER'] || '',
29
+ :password => ENV['OA_RPC_PASSWORD'] || '',
30
+ :schema => ENV['OA_RPC_SCHEMA'] || 'http'
31
+ }
32
+ config = {
33
+ :network => ENV['OA_NETWORK'] || 'mainnet',
34
+ :provider => 'bitcoind',
35
+ :cache => ENV['OA_CACHE'] || 'cache.db',
36
+ :dust_limit => (ENV['OA_DUST_LIMIT'] || 600).to_i,
37
+ :default_fees => (ENV['OA_DEFAULT_FEES'] || 10000).to_i,
38
+ :rpc => rpc
39
+ }
40
+ elsif options[:config]
41
+ if !File.exists?(options[:config])
42
+ puts "File not found: #{options[:config]}"
43
+ exit(1)
44
+ end
45
+ config = JSON.parse(File.read(options[:config]), {symbolize_names: true})
46
+ end
47
+
48
+ if options[:verbose]
49
+ puts "using config:"
50
+ puts config.inspect
51
+ end
52
+ $oa = $api = OpenAssets::Api.new(config)
53
+
54
+ command = ARGV.shift
55
+
56
+ if command == 'console'
57
+ require "irb"
58
+ puts "API is available as $oa:"
59
+ IRB.start
60
+ puts "bye, bye"
61
+ elsif command && $oa.respond_to?(command)
62
+ puts "running command '#{command}'"
63
+ puts $oa.send(command, *ARGV).inspect
64
+ else
65
+ puts "use 'openassets --help' for help"
66
+ end
@@ -0,0 +1,23 @@
1
+ require 'bitcoin'
2
+ require 'leb128'
3
+
4
+ module OpenAssets
5
+
6
+ autoload :Protocol, 'openassets/protocol'
7
+ autoload :Transaction, 'openassets/transaction'
8
+ autoload :VERSION, 'openassets/version'
9
+ autoload :Util, 'openassets/util'
10
+ autoload :MethodFilter, 'openassets/medhod_filter'
11
+ autoload :Api, 'openassets/api'
12
+ autoload :Provider, 'openassets/provider'
13
+ autoload :Error, 'openassets/error'
14
+ autoload :SendAssetParam, 'openassets/send_asset_param'
15
+ autoload :SendBitcoinParam, 'openassets/send_bitcoin_param'
16
+ autoload :Cache, 'openassets/cache'
17
+
18
+ class << self
19
+ attr_accessor :configuration
20
+ end
21
+
22
+ extend Util
23
+ end
@@ -0,0 +1,430 @@
1
+ # encoding: ascii-8bit
2
+
3
+ module OpenAssets
4
+
5
+ class Api
6
+
7
+ include Util
8
+ include MethodFilter
9
+
10
+ before_filter :change_network, {:include => [:list_unspent, :get_balance, :issue_asset, :send_asset, :send_assets, :send_bitcoin, :send_bitcoins]}
11
+
12
+ attr_reader :config
13
+ attr_reader :provider
14
+ attr_reader :tx_cache
15
+ attr_reader :output_cache
16
+
17
+ def initialize(config = nil)
18
+ @config = {:network => 'mainnet',
19
+ :provider => 'bitcoind', :cache => 'cache.db',
20
+ :dust_limit => 600, :default_fees => 10000, :min_confirmation => 1, :max_confirmation => 9999999,
21
+ :rpc => {:host => 'localhost', :port => 8332 , :user => '', :password => '', :wallet => '',
22
+ :schema => 'https', :timeout => 60, :open_timeout => 60}}
23
+ if config
24
+ @config.update(config)
25
+ end
26
+ OpenAssets.configuration = @config
27
+ if @config[:provider] == 'bitcoind'
28
+ @provider = Provider::BitcoinCoreProvider.new(@config[:rpc])
29
+ else
30
+ raise OpenAssets::Error, 'specified unsupported provider.'
31
+ end
32
+ @tx_cache = Cache::TransactionCache.new(@config[:cache])
33
+ @output_cache = Cache::OutputCache.new(@config[:cache])
34
+ change_network
35
+ end
36
+
37
+ def provider
38
+ @provider
39
+ end
40
+
41
+ def is_testnet?
42
+ @config[:network] == 'testnet'
43
+ end
44
+
45
+ # get UTXO for colored coins.
46
+ # @param [Array] oa_address_list Obtain the balance of this open assets address only, or all addresses if unspecified.
47
+ # @return [Array] Return array of the unspent information Hash.
48
+ def list_unspent(oa_address_list = [])
49
+ btc_address_list = oa_address_list.map { |oa_address| oa_address_to_address(oa_address)}
50
+ outputs = get_unspent_outputs(btc_address_list)
51
+ result = outputs.map{|out| out.to_hash}
52
+ result
53
+ end
54
+
55
+ # Returns the balance in both bitcoin and colored coin assets for all of the addresses available in your Bitcoin Core wallet.
56
+ # @param [String] address The open assets address. if unspecified nil.
57
+ def get_balance(address = nil)
58
+ outputs = get_unspent_outputs(address.nil? ? [] : [oa_address_to_address(address)])
59
+ colored_outputs = outputs.map{|o|o.output}
60
+ sorted_outputs = colored_outputs.sort_by { |o|o.script.to_s}
61
+ groups = sorted_outputs.group_by{|o| o.script.to_s}
62
+ result = groups.map{|k, v|
63
+ btc_address = v[0].script.addresses.first
64
+ sorted_script_outputs = v.sort_by{|o|o.asset_id unless o.asset_id}
65
+ group_assets = sorted_script_outputs.group_by{|o|o.asset_id}.select{|k,v| !k.nil?}
66
+ assets = group_assets.map{|asset_id, outputs|
67
+ {
68
+ 'asset_id' => asset_id,
69
+ 'quantity' => outputs.inject(0) { |sum, o| sum + o.asset_quantity }.to_s,
70
+ 'amount' => outputs.inject(0) { |sum, o| sum + o.asset_amount }.to_s,
71
+ 'asset_definition_url' => outputs[0].asset_definition_url,
72
+ 'proof_of_authenticity' => outputs[0].proof_of_authenticity
73
+ }
74
+ }
75
+ {
76
+ 'address' => btc_address,
77
+ 'oa_address' => (btc_address.nil? || btc_address.is_a?(Array)) ? nil : address_to_oa_address(btc_address),
78
+ 'value' => satoshi_to_coin(v.inject(0) { |sum, o|sum + o.value}),
79
+ 'assets' => assets,
80
+ 'account' => v[0].account
81
+ }
82
+ }
83
+ address.nil? ? result : result.select{|r|r['oa_address'] == address}
84
+ end
85
+
86
+ # Creates a transaction for issuing an asset.
87
+ # @param[String] from The open asset address to issue the asset from.
88
+ # @param[Integer] amount The amount of asset units to issue.
89
+ # @param[String] to The open asset address to send the asset to; if unspecified, the assets are sent back to the issuing address.
90
+ # @param[String] metadata The metadata to embed in the transaction. The asset definition pointer defined by this metadata.
91
+ # @param[Integer] fees The fess in satoshis for the transaction.
92
+ # @param[String] mode Specify the following mode.
93
+ # 'broadcast' (default) for signing and broadcasting the transaction,
94
+ # 'signed' for signing the transaction without broadcasting,
95
+ # 'unsigned' for getting the raw unsigned transaction without broadcasting"""='broadcast'
96
+ # @param[Integer] output_qty The number of divides the issue output. Default value is 1.
97
+ # Ex. amount = 125 and output_qty = 2, asset quantity = [62, 63] and issue TxOut is two.
98
+ # @return[Bitcoin::Tx] The Bitcoin::Tx object.
99
+ def issue_asset(from, amount, metadata = nil, to = nil, fees = nil, mode = 'broadcast', output_qty = 1)
100
+ to = from if to.nil?
101
+ colored_outputs = get_unspent_outputs([oa_address_to_address(from)])
102
+ issue_param = OpenAssets::Transaction::TransferParameters.new(colored_outputs, to, from, amount, output_qty)
103
+ tx = create_tx_builder.issue_asset(issue_param, metadata, fees.nil? ? @config[:default_fees]: fees)
104
+ tx = process_transaction(tx, mode)
105
+ tx
106
+ end
107
+
108
+ # Creates a transaction for sending an asset from an address to another.
109
+ # @param[String] from The open asset address to send the asset from.
110
+ # @param[String] asset_id The asset ID identifying the asset to send.
111
+ # @param[Integer] amount The amount of asset units to send.
112
+ # @param[String] to The open asset address to send the asset to.
113
+ # @param[Integer] fees The fess in satoshis for the transaction.
114
+ # @param[String] mode 'broadcast' (default) for signing and broadcasting the transaction,
115
+ # 'signed' for signing the transaction without broadcasting,
116
+ # 'unsigned' for getting the raw unsigned transaction without broadcasting"""='broadcast'
117
+ # @return[Bitcoin::Tx] The resulting transaction.
118
+ def send_asset(from, asset_id, amount, to, fees = nil, mode = 'broadcast', output_qty = 1)
119
+ colored_outputs = get_unspent_outputs([oa_address_to_address(from)])
120
+ asset_transfer_spec = OpenAssets::Transaction::TransferParameters.new(colored_outputs, to, from, amount, output_qty)
121
+ tx = create_tx_builder.transfer_asset(asset_id, asset_transfer_spec, from, fees.nil? ? @config[:default_fees]: fees)
122
+ tx = process_transaction(tx, mode)
123
+ tx
124
+ end
125
+
126
+ # Creates a transaction for sending multiple asset from an address to another.
127
+ # @param[String] from The open asset address to send the asset from when send_asset_param hasn't from.
128
+ # to send the bitcoins from, if needed. where to send bitcoin change, if any.
129
+ # @param[Array[OpenAssets::SendAssetParam]] send_asset_params The send Asset information(asset_id, amount, to, from).
130
+ # @param[Integer] fees The fess in satoshis for the transaction.
131
+ # @param[String] mode 'broadcast' (default) for signing and broadcasting the transaction,
132
+ # 'signed' for signing the transaction without broadcasting,
133
+ # 'unsigned' for getting the raw unsigned transaction without broadcasting"""='broadcast'
134
+ # @return[Bitcoin::Tx] The resulting transaction.
135
+ def send_assets(from, send_asset_params, fees = nil, mode = 'broadcast')
136
+ transfer_specs = send_asset_params.map{ |param|
137
+ colored_outputs = get_unspent_outputs([oa_address_to_address(param.from || from)])
138
+ [param.asset_id, OpenAssets::Transaction::TransferParameters.new(colored_outputs, param.to, param.from || from, param.amount)]
139
+ }
140
+ btc_transfer_spec = OpenAssets::Transaction::TransferParameters.new(
141
+ get_unspent_outputs([oa_address_to_address(from)]), nil, oa_address_to_address(from), 0)
142
+ tx = create_tx_builder.transfer_assets(transfer_specs, btc_transfer_spec, fees.nil? ? @config[:default_fees]: fees)
143
+ tx = process_transaction(tx, mode)
144
+ tx
145
+ end
146
+
147
+ # Creates a transaction for sending bitcoins from an address to another.
148
+ # @param[String] from The address to send the bitcoins from.
149
+ # @param[Integer] amount The amount of satoshis to send.
150
+ # @param[String] to The address to send the bitcoins to.
151
+ # @param[Integer] fees The fess in satoshis for the transaction.
152
+ # @param[String] mode 'broadcast' (default) for signing and broadcasting the transaction,
153
+ # 'signed' for signing the transaction without broadcasting,
154
+ # 'unsigned' for getting the raw unsigned transaction without broadcasting"""='broadcast'
155
+ # @param [Integer] output_qty The number of divides the issue output. Default value is 1.
156
+ # Ex. amount = 125 and output_qty = 2, asset quantity = [62, 63] and issue TxOut is two.
157
+ # @return[Bitcoin::Tx] The resulting transaction.
158
+ def send_bitcoin(from, amount, to, fees = nil, mode = 'broadcast', output_qty = 1)
159
+ validate_address([from, to])
160
+ colored_outputs = get_unspent_outputs([from])
161
+ btc_transfer_spec = OpenAssets::Transaction::TransferParameters.new(colored_outputs, to, from, amount, output_qty)
162
+ tx = create_tx_builder.transfer_btc(btc_transfer_spec, fees.nil? ? @config[:default_fees]: fees)
163
+ process_transaction(tx, mode)
164
+ end
165
+
166
+ # Creates a transaction for sending multiple bitcoins from an address to others.
167
+ # @param[String] from The address to send the bitcoins from.
168
+ # @param[Array[OpenAssets::SendBitcoinParam]] send_params The send information(amount of satoshis and to).
169
+ # @param[Integer] fees The fees in satoshis for the transaction.
170
+ # @param[String] mode 'broadcast' (default) for signing and broadcasting the transaction,
171
+ # 'signed' for signing the transaction without broadcasting,
172
+ # 'unsigned' for getting the raw unsigned transaction without broadcasting"""='broadcast'
173
+ # @return[Bitcoin::Tx] The resulting transaction.
174
+ def send_bitcoins(from, send_params, fees = nil, mode = 'broadcast')
175
+ colored_outputs = get_unspent_outputs([from])
176
+ btc_transfer_specs = send_params.map{|param|
177
+ OpenAssets::Transaction::TransferParameters.new(colored_outputs, param.to, from, param.amount)
178
+ }
179
+ tx = create_tx_builder.transfer_btcs(btc_transfer_specs, fees.nil? ? @config[:default_fees]: fees)
180
+ tx = process_transaction(tx, mode)
181
+ tx
182
+ end
183
+
184
+
185
+ # Creates a transaction for burn asset.
186
+ # @param[String] oa_address The open asset address to burn asset.
187
+ # @param[String] asset_id The asset ID identifying the asset to burn.
188
+ # @param[Integer] fees The fess in satoshis for the transaction.
189
+ # @param[String] mode 'broadcast' (default) for signing and broadcasting the transaction,
190
+ # 'signed' for signing the transaction without broadcasting,
191
+ # 'unsigned' for getting the raw unsigned transaction without broadcasting"""='broadcast'
192
+ def burn_asset(oa_address, asset_id, fees = nil, mode = 'broadcast')
193
+ unspents = get_unspent_outputs([oa_address_to_address(oa_address)])
194
+ tx = create_tx_builder.burn_asset(unspents, asset_id, fees.nil? ? @config[:default_fees]: fees)
195
+ process_transaction(tx, mode)
196
+ end
197
+
198
+ # Get unspent outputs.
199
+ # @param [Array] addresses The array of Bitcoin address.
200
+ # @return [Array[OpenAssets::Transaction::SpendableOutput]] The array of unspent outputs.
201
+ def get_unspent_outputs(addresses)
202
+ validate_address(addresses)
203
+ unspents = provider.list_unspent(addresses, @config[:min_confirmation], @config[:max_confirmation])
204
+ result = unspents.map{|item|
205
+ output_result = get_output(item['txid'], item['vout'])
206
+ output_result.account = item['account']
207
+ output = OpenAssets::Transaction::SpendableOutput.new(
208
+ Bitcoin::OutPoint.new(item['txid'].rhex, item['vout']), output_result)
209
+ output.confirmations = item['confirmations']
210
+ output.spendable = item['spendable']
211
+ output.solvable = item['solvable']
212
+ output
213
+ }
214
+ result
215
+ end
216
+
217
+ # get output
218
+ # @param [String] txid txid of output.
219
+ # @param [Integer] index index of output.
220
+ # @return
221
+ def get_output(txid, index)
222
+ if output_cache
223
+ cached = output_cache.get(txid, index)
224
+ return cached unless cached.nil?
225
+ end
226
+ decode_tx = load_cached_tx(txid)
227
+ tx = Bitcoin::Tx.parse_from_payload(decode_tx.htb)
228
+ colored_outputs = get_color_outputs_from_tx(tx)
229
+ colored_outputs.each_with_index { |o, index| output_cache.put(txid, index, o)} if output_cache
230
+ colored_outputs[index]
231
+ end
232
+
233
+ # get coloring outputs from tx.
234
+ # @param [Bitcoin::Tx] tx regular transaction.
235
+ # @return [Array[OpenAssets::Protocol::TransactionOutput]] array of coloring output.
236
+ def get_color_outputs_from_tx(tx)
237
+ unless tx.coinbase_tx?
238
+ tx.outputs.each_with_index { |out, i|
239
+ # check marker output.
240
+ marker_output_hex = OpenAssets::Protocol::MarkerOutput.parse_script(out.script_pubkey)
241
+ unless marker_output_hex.nil?
242
+ marker_output = OpenAssets::Protocol::MarkerOutput.parse_from_payload(marker_output_hex.htb)
243
+ prev_outs = tx.inputs.map {|input|get_output(input.out_point.txid, input.out_point.index)}
244
+ asset_ids = compute_asset_ids(prev_outs, i, tx, marker_output.asset_quantities)
245
+ return asset_ids unless asset_ids.nil?
246
+ end
247
+ }
248
+ end
249
+ tx.outputs.map{|out| OpenAssets::Protocol::TransactionOutput.new(out.value, out.script_pubkey, nil, 0, OpenAssets::Protocol::OutputType::UNCOLORED)}
250
+ end
251
+
252
+ # Get tx outputs.
253
+ # @param[String] txid Transaction ID.
254
+ # @param[Boolean] use_cache If specified true use cache.(default value is false)
255
+ # @return[Array] Return array of the transaction output Hash with coloring information.
256
+ def get_outputs_from_txid(txid, use_cache = false)
257
+ tx = get_tx(txid, use_cache)
258
+ outputs = get_color_outputs_from_tx(tx)
259
+ outputs.map.with_index{|out, i|out.to_hash.merge({'txid' => txid, 'vout' => i})}
260
+ end
261
+
262
+ # Get tx. (This method returns plain Bitcoin::Tx object, so it not contains open asset information.)
263
+ # @param[String] txid Transaction ID.
264
+ # @return[Bitcoin::Tx] Return the Bitcoin::Tx.
265
+ def get_tx(txid, use_cache = true)
266
+ decode_tx = use_cache ? load_cached_tx(txid) : load_tx(txid)
267
+ Bitcoin::Tx.parse_from_payload(decode_tx.htb)
268
+ end
269
+
270
+ private
271
+ # @param [Array[OpenAssets::Protocol::TransactionOutput] prev_outs The outputs referenced by the inputs of the transaction.
272
+ # @param [Integer] marker_output_index The position of the marker output in the transaction.
273
+ # @param [Bitcoin::Tx] tx The transaction.
274
+ # @param [Array[OpenAssets::Protocol::TransactionOutput]] asset_quantities The list of asset quantities of the outputs.
275
+ def compute_asset_ids(prev_outs, marker_output_index, tx, asset_quantities)
276
+ outputs = tx.outputs
277
+ return nil if asset_quantities.length > outputs.length - 1 || prev_outs.length == 0
278
+ result = []
279
+
280
+ marker_output = outputs[marker_output_index]
281
+
282
+ # Add the issuance outputs
283
+ issuance_asset_id = script_to_asset_id(prev_outs[0].script.to_payload.bth)
284
+
285
+ for i in (0..marker_output_index-1)
286
+ value = outputs[i].value
287
+ script = outputs[i].script_pubkey
288
+ if i < asset_quantities.length && asset_quantities[i] > 0
289
+ hex = OpenAssets::Protocol::MarkerOutput.parse_script(marker_output.script_pubkey)
290
+ metadata = OpenAssets::Protocol::MarkerOutput.parse_from_payload(hex.htb).metadata
291
+ if (metadata.nil? || metadata.length == 0) && prev_outs[0].script.p2sh?
292
+ metadata = parse_issuance_p2sh_pointer(tx.in[0].script_sig.to_payload)
293
+ end
294
+ metadata = '' unless metadata
295
+ output = OpenAssets::Protocol::TransactionOutput.new(value, script, issuance_asset_id, asset_quantities[i], OpenAssets::Protocol::OutputType::ISSUANCE, metadata)
296
+ else
297
+ output = OpenAssets::Protocol::TransactionOutput.new(value, script, nil, 0, OpenAssets::Protocol::OutputType::ISSUANCE)
298
+ end
299
+ result << output
300
+ end
301
+
302
+ # Add the marker output
303
+ result << OpenAssets::Protocol::TransactionOutput.new(marker_output.value, marker_output.script_pubkey, nil, 0, OpenAssets::Protocol::OutputType::MARKER_OUTPUT)
304
+
305
+ # remove invalid marker
306
+ remove_outputs = []
307
+ for i in (marker_output_index + 1)..(outputs.length-1)
308
+ marker_output_payload = OpenAssets::Protocol::MarkerOutput.parse_script(outputs[i].script_pubkey)
309
+ unless marker_output_payload.nil?
310
+ remove_outputs << outputs[i]
311
+ result << OpenAssets::Protocol::TransactionOutput.new(
312
+ outputs[i].value, outputs[i].script_pubkey, nil, 0, OpenAssets::Protocol::OutputType::MARKER_OUTPUT)
313
+ next
314
+ end
315
+ end
316
+ remove_outputs.each{|o|outputs.delete(o)}
317
+
318
+ # Add the transfer outputs
319
+ input_enum = prev_outs.each
320
+ input_units_left = 0
321
+ index = 0
322
+ for i in (marker_output_index + 1)..(outputs.length-1)
323
+ output_asset_quantity = (i <= asset_quantities.length) ? asset_quantities[i-1] : 0
324
+ output_units_left = output_asset_quantity
325
+ asset_id,metadata = nil
326
+ while output_units_left > 0
327
+ index += 1
328
+ if input_units_left == 0
329
+ begin
330
+ current_input = input_enum.next
331
+ input_units_left = current_input.asset_quantity
332
+ rescue StopIteration => e
333
+ return nil
334
+ end
335
+ end
336
+ unless current_input.asset_id.nil?
337
+ progress = [input_units_left, output_units_left].min
338
+ output_units_left -= progress
339
+ input_units_left -= progress
340
+ if asset_id.nil?
341
+ # This is the first input to map to this output
342
+ asset_id = current_input.asset_id
343
+ metadata = current_input.metadata
344
+ elsif asset_id != current_input.asset_id
345
+ return nil
346
+ end
347
+ end
348
+ end
349
+ result << OpenAssets::Protocol::TransactionOutput.new(outputs[i].value, outputs[i].script_pubkey,
350
+ asset_id, output_asset_quantity, OpenAssets::Protocol::OutputType::TRANSFER, metadata)
351
+ end
352
+ result
353
+ end
354
+
355
+ def process_transaction(tx, mode)
356
+ if mode == 'broadcast' || mode == 'signed'
357
+ # sign the transaction
358
+ signed_tx = provider.sign_transaction(tx.to_payload.bth)
359
+ if mode == 'broadcast'
360
+ puts provider.send_transaction(signed_tx.to_payload.bth)
361
+ end
362
+ signed_tx
363
+ else
364
+ tx
365
+ end
366
+ end
367
+
368
+ def change_network
369
+ case @config[:network]
370
+ when 'testnet'
371
+ Bitcoin.chain_params = :testnet
372
+ when 'regtest'
373
+ Bitcoin.chain_params = :regtest
374
+ when 'litecoin'
375
+ Bitcoin.chain_params = :litecoin
376
+ when 'litecoin_testnet'
377
+ Bitcoin.chain_params = :litecoin_testnet
378
+ else
379
+ Bitcoin.chain_params = :mainnet
380
+ end
381
+ end
382
+
383
+ def create_tx_builder
384
+ if @config[:default_fees] == :auto
385
+ # Estimate a transaction fee rate (satoshis/KB) if fee is specified by :auto
386
+ efr = coin_to_satoshi(provider.estimatefee(1).to_s).to_i
387
+ if efr < 0
388
+ # Negative efr means "estimatefee" of bitcoin-api returns false
389
+ # In this case, use default minimum fees rate (10_000 satoshis/KB)
390
+ efr = 10_000
391
+ end
392
+ OpenAssets::Transaction::TransactionBuilder.new(@config[:dust_limit], efr)
393
+ else
394
+ # If fee is specified by a fixed value (or the default value)
395
+ OpenAssets::Transaction::TransactionBuilder.new(@config[:dust_limit])
396
+ end
397
+
398
+ end
399
+
400
+ def load_tx(txid)
401
+ decode_tx = provider.get_transaction(txid, 0)
402
+ raise OpenAssets::Transaction::TransactionBuildError, "txid #{txid} could not be retrieved." if decode_tx.nil?
403
+ decode_tx
404
+ end
405
+
406
+ def load_cached_tx(txid)
407
+ return load_tx(txid) unless tx_cache
408
+ decode_tx = tx_cache.get(txid)
409
+ if decode_tx.nil?
410
+ decode_tx = load_tx(txid)
411
+ tx_cache.put(txid, decode_tx)
412
+ end
413
+ decode_tx
414
+ end
415
+
416
+ # parse issuance p2sh which contains asset definition pointer
417
+ # @param [String] script_sig script_sig with binary format.
418
+ # @return [String] Return asset definition pointer string i f +script_sig+ has asset definition pointer, otherwise nil
419
+ def parse_issuance_p2sh_pointer(script_sig)
420
+ script = Bitcoin::Script.parse_from_payload(script_sig).chunks.last.pushed_data
421
+ redeem_script = Bitcoin::Script.parse_from_payload(script)
422
+ return nil unless redeem_script.chunks[1].ord == Bitcoin::Opcodes::OP_DROP
423
+ return nil unless redeem_script.chunks[0].pushdata?
424
+ asset_def = to_bytes(redeem_script.chunks[0].pushed_data.bth)[0..-1].map{|x|x.to_i(16).chr}.join
425
+ asset_def && asset_def.start_with?('u=') ? asset_def : nil
426
+ end
427
+
428
+ end
429
+
430
+ end