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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +420 -0
- data/Rakefile +5 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/monacoin-openassets +66 -0
- data/lib/monacoin-openassets.rb +23 -0
- data/lib/openassets/api.rb +423 -0
- data/lib/openassets/cache.rb +8 -0
- data/lib/openassets/cache/output_cache.rb +43 -0
- data/lib/openassets/cache/sqlite_base.rb +26 -0
- data/lib/openassets/cache/ssl_certificate_cache.rb +44 -0
- data/lib/openassets/cache/transaction_cache.rb +33 -0
- data/lib/openassets/error.rb +5 -0
- data/lib/openassets/medhod_filter.rb +55 -0
- data/lib/openassets/protocol.rb +10 -0
- data/lib/openassets/protocol/asset_definition.rb +118 -0
- data/lib/openassets/protocol/asset_definition_loader.rb +43 -0
- data/lib/openassets/protocol/http_asset_definition_loader.rb +27 -0
- data/lib/openassets/protocol/marker_output.rb +128 -0
- data/lib/openassets/protocol/output_type.rb +27 -0
- data/lib/openassets/protocol/transaction_output.rb +157 -0
- data/lib/openassets/provider.rb +7 -0
- data/lib/openassets/provider/api_error.rb +9 -0
- data/lib/openassets/provider/bitcoin_core_provider.rb +123 -0
- data/lib/openassets/provider/block_chain_provider_base.rb +22 -0
- data/lib/openassets/send_asset_param.rb +17 -0
- data/lib/openassets/send_bitcoin_param.rb +13 -0
- data/lib/openassets/transaction.rb +12 -0
- data/lib/openassets/transaction/dust_output_error.rb +10 -0
- data/lib/openassets/transaction/insufficient_asset_quantity_error.rb +7 -0
- data/lib/openassets/transaction/insufficient_funds_error.rb +10 -0
- data/lib/openassets/transaction/out_point.rb +22 -0
- data/lib/openassets/transaction/spendable_output.rb +38 -0
- data/lib/openassets/transaction/transaction_build_error.rb +9 -0
- data/lib/openassets/transaction/transaction_builder.rb +319 -0
- data/lib/openassets/transaction/transfer_parameters.rb +41 -0
- data/lib/openassets/util.rb +173 -0
- data/lib/openassets/version.rb +3 -0
- data/openassets-ruby.gemspec +38 -0
- metadata +246 -0
data/Rakefile
ADDED
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,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
|