monacoin-openassets-ruby 0.1.3

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 (49) 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 +420 -0
  11. data/Rakefile +5 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/exe/monacoin-openassets +66 -0
  15. data/lib/monacoin-openassets.rb +23 -0
  16. data/lib/openassets/api.rb +423 -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 +43 -0
  27. data/lib/openassets/protocol/http_asset_definition_loader.rb +27 -0
  28. data/lib/openassets/protocol/marker_output.rb +128 -0
  29. data/lib/openassets/protocol/output_type.rb +27 -0
  30. data/lib/openassets/protocol/transaction_output.rb +157 -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 +123 -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 +12 -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/out_point.rb +22 -0
  42. data/lib/openassets/transaction/spendable_output.rb +38 -0
  43. data/lib/openassets/transaction/transaction_build_error.rb +9 -0
  44. data/lib/openassets/transaction/transaction_builder.rb +319 -0
  45. data/lib/openassets/transaction/transfer_parameters.rb +41 -0
  46. data/lib/openassets/util.rb +173 -0
  47. data/lib/openassets/version.rb +3 -0
  48. data/openassets-ruby.gemspec +38 -0
  49. metadata +246 -0
data/Rakefile ADDED
@@ -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
data/bin/console ADDED
@@ -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
data/bin/setup ADDED
@@ -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,423 @@
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_string}
61
+ groups = sorted_outputs.group_by{|o| o.script.to_string}
62
+ result = groups.map{|k, v|
63
+ btc_address = script_to_address(v[0].script)
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? ? 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::Protocol::Tx] The Bitcoin::Protocol::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::Protocol: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::Protocol: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::Protocol: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::Protocol: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
+ unspent = provider.list_unspent(addresses, @config[:min_confirmation], @config[:max_confirmation])
204
+ result = unspent.map{|item|
205
+ output_result = get_output(item['txid'], item['vout'])
206
+ output_result.account = item['account']
207
+ output = OpenAssets::Transaction::SpendableOutput.new(
208
+ OpenAssets::Transaction::OutPoint.new(item['txid'], 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
+ def get_output(txid, output_index)
218
+ if output_cache
219
+ cached = output_cache.get(txid, output_index)
220
+ return cached unless cached.nil?
221
+ end
222
+ decode_tx = load_cached_tx(txid)
223
+ tx = Bitcoin::Protocol::Tx.new(decode_tx.htb)
224
+ colored_outputs = get_color_outputs_from_tx(tx)
225
+ colored_outputs.each_with_index { |o, index| output_cache.put(txid, index, o)} if output_cache
226
+ colored_outputs[output_index]
227
+ end
228
+
229
+ def get_color_outputs_from_tx(tx)
230
+ unless tx.is_coinbase?
231
+ tx.outputs.each_with_index { |out, i|
232
+ marker_output_payload = OpenAssets::Protocol::MarkerOutput.parse_script(out.pk_script)
233
+ unless marker_output_payload.nil?
234
+ marker_output = OpenAssets::Protocol::MarkerOutput.deserialize_payload(marker_output_payload)
235
+ prev_outs = tx.inputs.map {|input|get_output(input.previous_output, input.prev_out_index)}
236
+ asset_ids = compute_asset_ids(prev_outs, i, tx, marker_output.asset_quantities)
237
+ return asset_ids unless asset_ids.nil?
238
+ end
239
+ }
240
+ end
241
+ tx.outputs.map{|out| OpenAssets::Protocol::TransactionOutput.new(out.value, out.parsed_script, nil, 0, OpenAssets::Protocol::OutputType::UNCOLORED)}
242
+ end
243
+
244
+ # Get tx outputs.
245
+ # @param[String] txid Transaction ID.
246
+ # @param[Boolean] use_cache If specified true use cache.(default value is false)
247
+ # @return[Array] Return array of the transaction output Hash with coloring information.
248
+ def get_outputs_from_txid(txid, use_cache = false)
249
+ tx = get_tx(txid, use_cache)
250
+ outputs = get_color_outputs_from_tx(tx)
251
+ outputs.map.with_index{|out, i|out.to_hash.merge({'txid' => tx.hash, 'vout' => i})}
252
+ end
253
+
254
+ # Get tx. (This method returns plain Bitcoin::Protocol::Tx object, so it not contains open asset information.)
255
+ # @param[String] txid Transaction ID.
256
+ # @return[Bitcoin::Protocol::Tx] Return the Bitcoin::Protocol::Tx.
257
+ def get_tx(txid, use_cache = true)
258
+ decode_tx = use_cache ? load_cached_tx(txid) : load_tx(txid)
259
+ Bitcoin::Protocol::Tx.new(decode_tx.htb)
260
+ end
261
+
262
+ private
263
+ # @param [Array[OpenAssets::Protocol::TransactionOutput] prev_outs The outputs referenced by the inputs of the transaction.
264
+ # @param [Integer] marker_output_index The position of the marker output in the transaction.
265
+ # @param [Bitcoin::Protocol::Tx] tx The transaction.
266
+ # @param [Array[OpenAssets::Protocol::TransactionOutput]] asset_quantities The list of asset quantities of the outputs.
267
+ def compute_asset_ids(prev_outs, marker_output_index, tx, asset_quantities)
268
+ outputs = tx.outputs
269
+ return nil if asset_quantities.length > outputs.length - 1 || prev_outs.length == 0
270
+ result = []
271
+
272
+ marker_output = outputs[marker_output_index]
273
+
274
+ # Add the issuance outputs
275
+ issuance_asset_id = script_to_asset_id(prev_outs[0].script.to_payload.bth)
276
+
277
+ for i in (0..marker_output_index-1)
278
+ value = outputs[i].value
279
+ script = outputs[i].parsed_script
280
+ if i < asset_quantities.length && asset_quantities[i] > 0
281
+ payload = OpenAssets::Protocol::MarkerOutput.parse_script(marker_output.parsed_script.to_payload)
282
+ metadata = OpenAssets::Protocol::MarkerOutput.deserialize_payload(payload).metadata
283
+ if (metadata.nil? || metadata.length == 0) && prev_outs[0].script.is_p2sh?
284
+ metadata = parse_issuance_p2sh_pointer(tx.in[0].script_sig)
285
+ end
286
+ metadata = '' unless metadata
287
+ output = OpenAssets::Protocol::TransactionOutput.new(value, script, issuance_asset_id, asset_quantities[i], OpenAssets::Protocol::OutputType::ISSUANCE, metadata)
288
+ else
289
+ output = OpenAssets::Protocol::TransactionOutput.new(value, script, nil, 0, OpenAssets::Protocol::OutputType::ISSUANCE)
290
+ end
291
+ result << output
292
+ end
293
+
294
+ # Add the marker output
295
+ result << OpenAssets::Protocol::TransactionOutput.new(marker_output.value, marker_output.parsed_script, nil, 0, OpenAssets::Protocol::OutputType::MARKER_OUTPUT)
296
+
297
+ # remove invalid marker
298
+ remove_outputs = []
299
+ for i in (marker_output_index + 1)..(outputs.length-1)
300
+ marker_output_payload = OpenAssets::Protocol::MarkerOutput.parse_script(outputs[i].pk_script)
301
+ unless marker_output_payload.nil?
302
+ remove_outputs << outputs[i]
303
+ result << OpenAssets::Protocol::TransactionOutput.new(
304
+ outputs[i].value, outputs[i].parsed_script, nil, 0, OpenAssets::Protocol::OutputType::MARKER_OUTPUT)
305
+ next
306
+ end
307
+ end
308
+ remove_outputs.each{|o|outputs.delete(o)}
309
+
310
+ # Add the transfer outputs
311
+ input_enum = prev_outs.each
312
+ input_units_left = 0
313
+ index = 0
314
+ for i in (marker_output_index + 1)..(outputs.length-1)
315
+ output_asset_quantity = (i <= asset_quantities.length) ? asset_quantities[i-1] : 0
316
+ output_units_left = output_asset_quantity
317
+ asset_id,metadata = nil
318
+ while output_units_left > 0
319
+ index += 1
320
+ if input_units_left == 0
321
+ begin
322
+ current_input = input_enum.next
323
+ input_units_left = current_input.asset_quantity
324
+ rescue StopIteration => e
325
+ return nil
326
+ end
327
+ end
328
+ unless current_input.asset_id.nil?
329
+ progress = [input_units_left, output_units_left].min
330
+ output_units_left -= progress
331
+ input_units_left -= progress
332
+ if asset_id.nil?
333
+ # This is the first input to map to this output
334
+ asset_id = current_input.asset_id
335
+ metadata = current_input.metadata
336
+ elsif asset_id != current_input.asset_id
337
+ return nil
338
+ end
339
+ end
340
+ end
341
+ result << OpenAssets::Protocol::TransactionOutput.new(outputs[i].value, outputs[i].parsed_script,
342
+ asset_id, output_asset_quantity, OpenAssets::Protocol::OutputType::TRANSFER, metadata)
343
+ end
344
+ result
345
+ end
346
+
347
+ def process_transaction(tx, mode)
348
+ if mode == 'broadcast' || mode == 'signed'
349
+ # sign the transaction
350
+ signed_tx = provider.sign_transaction(tx.to_payload.bth)
351
+ if mode == 'broadcast'
352
+ puts provider.send_transaction(signed_tx.to_payload.bth)
353
+ end
354
+ signed_tx
355
+ else
356
+ tx
357
+ end
358
+ end
359
+
360
+ def change_network
361
+ case @config[:network]
362
+ when 'testnet'
363
+ Bitcoin.network = :testnet3
364
+ when 'regtest'
365
+ Bitcoin.network = :regtest
366
+ when 'litecoin'
367
+ Bitcoin.network = :litecoin
368
+ when 'litecoin_testnet'
369
+ Bitcoin.network = :litecoin_testnet
370
+ when 'monacoin'
371
+ Bitcoin.network = :monacoin
372
+ when 'monacoin_testnet'
373
+ Bitcoin.network = :monacoin_testnet
374
+ else
375
+ Bitcoin.network = :bitcoin
376
+ end
377
+ end
378
+
379
+ def create_tx_builder
380
+ if @config[:default_fees] == :auto
381
+ # Estimate a transaction fee rate (satoshis/KB) if fee is specified by :auto
382
+ efr = coin_to_satoshi(provider.estimatefee(1).to_s).to_i
383
+ if efr < 0
384
+ # Negative efr means "estimatefee" of bitcoin-api returns false
385
+ # In this case, use default minimum fees rate (10_000 satoshis/KB)
386
+ efr = 10_000
387
+ end
388
+ OpenAssets::Transaction::TransactionBuilder.new(@config[:dust_limit], efr)
389
+ else
390
+ # If fee is specified by a fixed value (or the default value)
391
+ OpenAssets::Transaction::TransactionBuilder.new(@config[:dust_limit])
392
+ end
393
+
394
+ end
395
+
396
+ def load_tx(txid)
397
+ decode_tx = provider.get_transaction(txid, 0)
398
+ raise OpenAssets::Transaction::TransactionBuildError, "txid #{txid} could not be retrieved." if decode_tx.nil?
399
+ decode_tx
400
+ end
401
+
402
+ def load_cached_tx(txid)
403
+ return load_tx(txid) unless tx_cache
404
+ decode_tx = tx_cache.get(txid)
405
+ if decode_tx.nil?
406
+ decode_tx = load_tx(txid)
407
+ tx_cache.put(txid, decode_tx)
408
+ end
409
+ decode_tx
410
+ end
411
+
412
+ # parse issuance p2sh which contains asset definition pointer
413
+ def parse_issuance_p2sh_pointer(script_sig)
414
+ script = Bitcoin::Script.new(script_sig).chunks.last
415
+ redeem_script = Bitcoin::Script.new(script)
416
+ return nil unless redeem_script.chunks[1] == Bitcoin::Script::OP_DROP
417
+ asset_def = to_bytes(redeem_script.chunks[0].to_s.bth)[0..-1].map{|x|x.to_i(16).chr}.join
418
+ asset_def && asset_def.start_with?('u=') ? asset_def : nil
419
+ end
420
+
421
+ end
422
+
423
+ end