openassets-ruby 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +75 -0
- data/Rakefile +5 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/openassets/api.rb +229 -0
- data/lib/openassets/error.rb +5 -0
- data/lib/openassets/protocol/marker_output.rb +82 -0
- data/lib/openassets/protocol/output_type.rb +17 -0
- data/lib/openassets/protocol/transaction_output.rb +29 -0
- data/lib/openassets/protocol.rb +7 -0
- data/lib/openassets/provider/api_error.rb +9 -0
- data/lib/openassets/provider/bitcoin_core_provider.rb +96 -0
- data/lib/openassets/provider/block_chain_provider_base.rb +22 -0
- data/lib/openassets/provider.rb +7 -0
- data/lib/openassets/transaction/dust_output_error.rb +10 -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 +24 -0
- data/lib/openassets/transaction/transaction_build_error.rb +9 -0
- data/lib/openassets/transaction/transaction_builder.rb +95 -0
- data/lib/openassets/transaction/transfer_parameters.rb +27 -0
- data/lib/openassets/transaction.rb +11 -0
- data/lib/openassets/util.rb +93 -0
- data/lib/openassets/version.rb +3 -0
- data/lib/openassets.rb +11 -0
- data/openassets-ruby.gemspec +28 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fa13af5ba17c13c7516e29819722ffc2a7a69766
|
4
|
+
data.tar.gz: d94cc426ab8af56d2e0967f415323c67de17a6f7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ebf299dbc652bffa5299bb23ab2cf32862f78cdadfcb010adf8d6a013a680d62b397fac19703de464612fa041ab01fd91959943414c65c9af04b869b3e07b20
|
7
|
+
data.tar.gz: d338cb28973172b73696a1acef698028d54904fef2fdebe7e5148478eff2acea596f65218c185808c35e1f76157b26d651b382347a11a7e991f80cad9831de32
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
openassets-ruby
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.2
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 HAW International Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 azuchi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# openassets-ruby
|
2
|
+
The implementation of the Open Assets Protocol for Ruby.
|
3
|
+
|
4
|
+
## Configuration
|
5
|
+
|
6
|
+
Initialize the connection information to the Bitcoin Core server.
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
require 'openassets'
|
10
|
+
|
11
|
+
api = OpenAssets::Api.new({:network => 'mainnet',
|
12
|
+
:provider => 'bitcoind',
|
13
|
+
:dust_limit => 600,
|
14
|
+
:rpc => {:user => 'xxx', :password => 'xxx', :schema => 'http', :port => 8332, :host => 'localhost'}})
|
15
|
+
```
|
16
|
+
|
17
|
+
## API
|
18
|
+
|
19
|
+
Currently openassets-ruby support the following API.
|
20
|
+
(The other API is in development. ex, send_asset)
|
21
|
+
|
22
|
+
* **list_unspent**
|
23
|
+
Returns an array of unspent transaction outputs, argument with the asset ID and quantity of each output.
|
24
|
+
```ruby
|
25
|
+
# get all unspent outputs in the wallet.
|
26
|
+
api.list_unspent
|
27
|
+
|
28
|
+
# specify th open asset address.
|
29
|
+
api.list_unspent(['akTfC7D825Cse4NvFiLCy7vr3B6x2Mpq8t6'])
|
30
|
+
```
|
31
|
+
|
32
|
+
* **get_balance**
|
33
|
+
Returns the balance in both bitcoin and colored coin assets for all of the addresses available in your Bitcoin Core wallet.
|
34
|
+
```ruby
|
35
|
+
# get all balance in the wallet.
|
36
|
+
api.get_balance
|
37
|
+
|
38
|
+
# specify the open asset address.
|
39
|
+
api.get_balance('akTfC7D825Cse4NvFiLCy7vr3B6x2Mpq8t6')
|
40
|
+
```
|
41
|
+
|
42
|
+
* **issue_asset**
|
43
|
+
Creates a transaction for issuing an asset.
|
44
|
+
```ruby
|
45
|
+
# issue asset
|
46
|
+
# api.issue_asset(<issuer open asset address>, <issuing asset quantity>, <metadata>, <to open asset address>, <fees (The fess in satoshis for the transaction. use 10000 satoshi if specified nil)>, <mode=('broadcast', 'signed', 'unsigned')>)
|
47
|
+
|
48
|
+
# example
|
49
|
+
address = 'akEJwzkzEFau4t2wjbXoMs7MwtZkB8xixmH'
|
50
|
+
api.issue_asset(address, 150, 'u=https://goo.gl/bmVEuw', address, nil, 'broadcast')
|
51
|
+
```
|
52
|
+
|
53
|
+
## License
|
54
|
+
|
55
|
+
The MIT License (MIT)
|
56
|
+
|
57
|
+
Copyright (c) 2015 HAW International Inc.
|
58
|
+
|
59
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
60
|
+
of this software and associated documentation files (the "Software"), to deal
|
61
|
+
in the Software without restriction, including without limitation the rights
|
62
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
63
|
+
copies of the Software, and to permit persons to whom the Software is
|
64
|
+
furnished to do so, subject to the following conditions:
|
65
|
+
|
66
|
+
The above copyright notice and this permission notice shall be included in all
|
67
|
+
copies or substantial portions of the Software.
|
68
|
+
|
69
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
70
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
71
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
72
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
73
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
74
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
75
|
+
SOFTWARE.
|
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,229 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
module OpenAssets
|
4
|
+
|
5
|
+
class Api
|
6
|
+
|
7
|
+
include OpenAssets::Util
|
8
|
+
|
9
|
+
attr_reader :config
|
10
|
+
attr_reader :provider
|
11
|
+
attr_reader :cache
|
12
|
+
|
13
|
+
def initialize(config = nil)
|
14
|
+
@cache = {}
|
15
|
+
@config = {:network => 'mainnet',
|
16
|
+
:provider => 'bitcoind',
|
17
|
+
:dust_limit => 600, :default_fees => 10000,
|
18
|
+
:rpc => { :host => 'localhost', :port => 8332 , :user => '', :password => '', :schema => 'https'}}
|
19
|
+
if config
|
20
|
+
@config.update(config)
|
21
|
+
end
|
22
|
+
if @config[:provider] == 'bitcoind'
|
23
|
+
@provider = Provider::BitcoinCoreProvider.new(@config[:rpc])
|
24
|
+
else
|
25
|
+
raise StandardError, 'specified unsupported provider.'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def provider
|
30
|
+
@provider
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_testnet?
|
34
|
+
@config[:network] == 'testnet'
|
35
|
+
end
|
36
|
+
|
37
|
+
# get UTXO for colored coins.
|
38
|
+
# @param [Array] oa_address Obtain the balance of this open assets address only, or all addresses if unspecified.
|
39
|
+
# @return [Array] Return array of the unspent information Hash.
|
40
|
+
def list_unspent(oa_address = [])
|
41
|
+
outputs = get_unspent_outputs([])
|
42
|
+
result = outputs.map {|out|
|
43
|
+
address = script_to_address(out.output.script)
|
44
|
+
script = out.output.script.to_payload.bth
|
45
|
+
{
|
46
|
+
'txid' => out.out_point.hash,
|
47
|
+
'vout' => out.out_point.index,
|
48
|
+
'address' => address,
|
49
|
+
'oa_address' => address.nil? ? nil : address_to_oa_address(address),
|
50
|
+
'script' => script,
|
51
|
+
'amount' => satoshi_to_coin(out.output.value),
|
52
|
+
'confirmations' => out.confirmations,
|
53
|
+
'asset_id' => out.output.asset_id,
|
54
|
+
'asset_quantity' => out.output.asset_quantity.to_s
|
55
|
+
}
|
56
|
+
}
|
57
|
+
oa_address.empty? ? result : result.select{|r|oa_address.include?(r['oa_address'])}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the balance in both bitcoin and colored coin assets for all of the addresses available in your Bitcoin Core wallet.
|
61
|
+
# @param [String] address The open assets address. if unspecified nil.
|
62
|
+
def get_balance(address = nil)
|
63
|
+
outputs = get_unspent_outputs(address.nil? ? [] : [oa_address_to_address(address)])
|
64
|
+
colored_outputs = outputs.map{|o|o.output}
|
65
|
+
sorted_outputs = colored_outputs.sort_by { |o|o.script.to_string}
|
66
|
+
groups = sorted_outputs.group_by{|o| o.script.to_string}
|
67
|
+
result = groups.map{|k, v|
|
68
|
+
btc_address = script_to_address(v[0].script)
|
69
|
+
sorted_script_outputs = v.sort_by{|o|o.asset_id unless o.asset_id}
|
70
|
+
group_assets = sorted_script_outputs.group_by{|o|o.asset_id}.select{|k,v| !k.nil?}
|
71
|
+
assets = group_assets.map{|asset_id, outputs|
|
72
|
+
{
|
73
|
+
'asset_id' => asset_id,
|
74
|
+
'quantity' => outputs.inject(0) { |sum, o| sum + o.asset_quantity }.to_s
|
75
|
+
}
|
76
|
+
}
|
77
|
+
{
|
78
|
+
'address' => btc_address,
|
79
|
+
'oa_address' => btc_address.nil? ? nil : address_to_oa_address(btc_address),
|
80
|
+
'value' => satoshi_to_coin(v.inject(0) { |sum, o|sum + o.value}),
|
81
|
+
'assets' => assets
|
82
|
+
}
|
83
|
+
}
|
84
|
+
address.nil? ? result : result.select{|r|r['oa_address'] == address}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates a transaction for issuing an asset.
|
88
|
+
# @param[String] from The open asset address to issue the asset from.
|
89
|
+
# @param[Integer] amount The amount of asset units to issue.
|
90
|
+
# @param[String] to The open asset address to send the asset to; if unspecified, the assets are sent back to the issuing address.
|
91
|
+
# @param[String] metadata The metadata to embed in the transaction. The asset definition pointer defined by this metadata.
|
92
|
+
# @param[Integer] fees The fess in satoshis for the transaction.
|
93
|
+
# @param[String] mode Specify the following mode.
|
94
|
+
# 'broadcast' (default) for signing and broadcasting the transaction,
|
95
|
+
# 'signed' for signing the transaction without broadcasting,
|
96
|
+
# 'unsigned' for getting the raw unsigned transaction without broadcasting"""='broadcast'
|
97
|
+
def issue_asset(from, amount, metadata = nil, to = nil, fees = nil, mode = 'broadcast')
|
98
|
+
to = from if to.nil?
|
99
|
+
builder = OpenAssets::Transaction::TransactionBuilder.new(@config[:dust_limit])
|
100
|
+
colored_outputs = get_unspent_outputs([oa_address_to_address(from)])
|
101
|
+
issue_param = OpenAssets::Transaction::TransferParameters.new(colored_outputs, to, from, amount)
|
102
|
+
tx = builder.issue_asset(issue_param, metadata, fees.nil? ? @config[:default_fees]: fees)
|
103
|
+
tx = process_transaction(tx, mode)
|
104
|
+
tx
|
105
|
+
end
|
106
|
+
|
107
|
+
# Get unspent outputs.
|
108
|
+
# @param [Array] addresses The array of Bitcoin address.
|
109
|
+
# @return [Array[OpenAssets::Transaction::SpendableOutput]] The array of unspent outputs.
|
110
|
+
def get_unspent_outputs(addresses)
|
111
|
+
validate_address(addresses)
|
112
|
+
unspent = provider.list_unspent(addresses)
|
113
|
+
result = unspent.map{|item|
|
114
|
+
output_result = get_output(item['txid'], item['vout'])
|
115
|
+
output = OpenAssets::Transaction::SpendableOutput.new(
|
116
|
+
OpenAssets::Transaction::OutPoint.new(item['txid'], item['vout']), output_result)
|
117
|
+
output.confirmations = item['confirmations']
|
118
|
+
output
|
119
|
+
}
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_output(txid, output_index)
|
124
|
+
cached_output = @cache[txid + output_index.to_s]
|
125
|
+
return cached_output if cached_output
|
126
|
+
decode_tx = provider.get_transaction(txid, 0)
|
127
|
+
raise OpenAssets::Transaction::TransactionBuildError, "txid #{txid} could not be retrieved." if decode_tx.nil?
|
128
|
+
tx = Bitcoin::Protocol::Tx.new(decode_tx.htb)
|
129
|
+
colored_outputs = get_color_transaction(tx)
|
130
|
+
colored_outputs.each_with_index { |o, index | @cache[txid + index.to_s] = o}
|
131
|
+
colored_outputs[output_index]
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_color_transaction(tx)
|
135
|
+
unless tx.is_coinbase?
|
136
|
+
tx.outputs.each_with_index { |out, i|
|
137
|
+
marker_output_payload = OpenAssets::Protocol::MarkerOutput.parse_script(out.pk_script)
|
138
|
+
unless marker_output_payload.nil?
|
139
|
+
marker_output = OpenAssets::Protocol::MarkerOutput.deserialize_payload(marker_output_payload)
|
140
|
+
inputs = tx.inputs.map {|input|
|
141
|
+
get_output(input.previous_output, input.prev_out_index)
|
142
|
+
}
|
143
|
+
asset_ids = compute_asset_ids(inputs, i, tx.outputs, marker_output.asset_quantities)
|
144
|
+
return asset_ids unless asset_ids.nil?
|
145
|
+
end
|
146
|
+
}
|
147
|
+
end
|
148
|
+
tx.outputs.map{|out| OpenAssets::Protocol::TransactionOutput.new(out.value, out.parsed_script, nil, 0, OpenAssets::Protocol::OutputType::UNCOLORED)}
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
# @param [Array[OpenAssets::Protocol::TransactionOutput] inputs The outputs referenced by the inputs of the transaction.
|
153
|
+
# @param [Integer] marker_output_index The position of the marker output in the transaction.
|
154
|
+
# @param [Array[Bitcoin::Protocol::TxOUt]] outputs The outputs of the transaction.
|
155
|
+
# @param [Array[Integer]] asset_quantities The list of asset quantities of the outputs.
|
156
|
+
def compute_asset_ids(inputs, marker_output_index, outputs, asset_quantities)
|
157
|
+
return nil if asset_quantities.length > outputs.length - 1 || inputs.length == 0
|
158
|
+
result = []
|
159
|
+
|
160
|
+
# Add the issuance outputs
|
161
|
+
issuance_asset_id = pubkey_hash_to_asset_id(inputs[0].script.get_hash160)
|
162
|
+
|
163
|
+
for i in (0..marker_output_index-1)
|
164
|
+
value = outputs[i].value
|
165
|
+
script = outputs[i].parsed_script
|
166
|
+
if i < asset_quantities.length && asset_quantities[i] > 0
|
167
|
+
output = OpenAssets::Protocol::TransactionOutput.new(value, script, issuance_asset_id, asset_quantities[i], OpenAssets::Protocol::OutputType::ISSUANCE)
|
168
|
+
else
|
169
|
+
output = OpenAssets::Protocol::TransactionOutput.new(value, script, nil, 0, OpenAssets::Protocol::OutputType::ISSUANCE)
|
170
|
+
end
|
171
|
+
result << output
|
172
|
+
end
|
173
|
+
|
174
|
+
# Add the marker output
|
175
|
+
marker_output = outputs[marker_output_index]
|
176
|
+
result << OpenAssets::Protocol::TransactionOutput.new(marker_output.value, marker_output.parsed_script, nil, 0, OpenAssets::Protocol::OutputType::MARKER_OUTPUT)
|
177
|
+
|
178
|
+
# Add the transfer outputs
|
179
|
+
input_enum = inputs.each
|
180
|
+
input_units_left = 0
|
181
|
+
index = 0
|
182
|
+
for i in (marker_output_index + 1)..(outputs.length-1)
|
183
|
+
output_asset_quantity = (i <= asset_quantities.length) ? asset_quantities[i-1] : 0
|
184
|
+
output_units_left = output_asset_quantity
|
185
|
+
asset_id = nil
|
186
|
+
while output_units_left > 0
|
187
|
+
index += 1
|
188
|
+
if input_units_left == 0
|
189
|
+
begin
|
190
|
+
current_input = input_enum.next
|
191
|
+
input_units_left = current_input.asset_quantity
|
192
|
+
rescue StopIteration => e
|
193
|
+
return nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
unless current_input.asset_id.nil?
|
197
|
+
progress = [input_units_left, output_units_left].min
|
198
|
+
output_units_left -= progress
|
199
|
+
input_units_left -= progress
|
200
|
+
if asset_id.nil?
|
201
|
+
# This is the first input to map to this output
|
202
|
+
asset_id = current_input.asset_id
|
203
|
+
elsif asset_id != current_input.asset_id
|
204
|
+
return nil
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
result << OpenAssets::Protocol::TransactionOutput.new(outputs[i].value, outputs[i].parsed_script,
|
209
|
+
asset_id, output_asset_quantity, OpenAssets::Protocol::OutputType::TRANSFER)
|
210
|
+
end
|
211
|
+
result
|
212
|
+
end
|
213
|
+
|
214
|
+
def process_transaction(tx, mode)
|
215
|
+
if mode == 'broadcast' || mode == 'signed'
|
216
|
+
# sign the transaction
|
217
|
+
signed_tx = provider.sign_transaction(tx.to_payload.bth)
|
218
|
+
if mode == 'broadcast'
|
219
|
+
puts provider.send_transaction(signed_tx.to_payload.bth)
|
220
|
+
end
|
221
|
+
signed_tx
|
222
|
+
else
|
223
|
+
tx
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
class MarkerOutput
|
5
|
+
include OpenAssets::Util
|
6
|
+
extend OpenAssets::Util
|
7
|
+
|
8
|
+
MAX_ASSET_QUANTITY = 2 ** 63 -1
|
9
|
+
|
10
|
+
# A tag indicating thath this transaction is an Open Assets transaction.
|
11
|
+
OAP_MARKER = "4f41"
|
12
|
+
# The major revision number of the Open Assets Protocol.(1=0x0100)
|
13
|
+
VERSION = "0100"
|
14
|
+
|
15
|
+
attr_accessor :asset_quantities
|
16
|
+
attr_accessor :metadata
|
17
|
+
|
18
|
+
# @param [Array] asset_quantities The asset quantity array
|
19
|
+
# @param [String] metadata The metadata in the marker output.
|
20
|
+
def initialize(asset_quantities, metadata)
|
21
|
+
@asset_quantities = asset_quantities
|
22
|
+
@metadata = metadata
|
23
|
+
end
|
24
|
+
|
25
|
+
# Serialize the marker output into a Open Assets Payload buffer.
|
26
|
+
# @return [String] The serialized payload.
|
27
|
+
def to_payload
|
28
|
+
payload = [OAP_MARKER, VERSION]
|
29
|
+
payload << Bitcoin::Protocol.pack_var_int(@asset_quantities.length).unpack("H*")
|
30
|
+
@asset_quantities.map{|q|payload << encode_leb128(q)}
|
31
|
+
payload << Bitcoin::Protocol.pack_var_int(@metadata.length).unpack("H*")
|
32
|
+
tmp = []
|
33
|
+
@metadata.bytes{|b| tmp << b.to_s(16)}
|
34
|
+
payload << tmp.join
|
35
|
+
payload.join
|
36
|
+
end
|
37
|
+
|
38
|
+
# Deserialize the marker output payload.
|
39
|
+
# @param [String] payload The Open Assets Payload.
|
40
|
+
# @return [OpenAssets::Protocol::MarkerOutput] The marker output object.
|
41
|
+
def self.deserialize_payload(payload)
|
42
|
+
payload = payload[8..-1] # exclude OAP_MARKER,VERSION
|
43
|
+
asset_quantity, payload = parse_asset_qty(payload)
|
44
|
+
list = to_bytes(payload).map{|x|(x.to_i(16)>=128 ? x : x+"|")}.join.split('|')[0..(asset_quantity - 1)].join
|
45
|
+
asset_quantities = decode_leb128(list)
|
46
|
+
meta = to_bytes(payload[list.size..-1])
|
47
|
+
metadata = meta[1..-1].map{|x|x.to_i(16).chr}.join
|
48
|
+
new(asset_quantities, metadata)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parses an output and returns the payload if the output matches the right pattern for a marker output,
|
52
|
+
# @param [Bitcoin::Script] output_script: The output script to be parsed.
|
53
|
+
# @return [String] The byte string of the marker output payload if the output fits the pattern, nil otherwise.
|
54
|
+
def self.parse_script(output_script)
|
55
|
+
data = Bitcoin::Script.new(output_script).get_op_return_data
|
56
|
+
return data if data.nil?
|
57
|
+
data.start_with?(OAP_MARKER) ? data : nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# Creates an output script containing an OP_RETURN and a PUSHDATA from payload.
|
61
|
+
# @return [Bitcoin::Script] the output script.
|
62
|
+
def build_script
|
63
|
+
Bitcoin::Script.from_string("OP_RETURN #{to_payload}")
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def self.parse_asset_qty(payload)
|
68
|
+
bytes = to_bytes(payload)
|
69
|
+
case bytes[0]
|
70
|
+
when "fd" then
|
71
|
+
[(bytes[1]+bytes[2]).to_i(16), payload[6..-1]]
|
72
|
+
when "fe" then
|
73
|
+
[(bytes[1]+bytes[2]+bytes[3]+bytes[4]).to_i(16),payload[10..-1]]
|
74
|
+
else
|
75
|
+
[bytes[0].to_i(16),payload[2..-1]]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Transaction output type enum
|
2
|
+
module OpenAssets
|
3
|
+
module Protocol
|
4
|
+
module OutputType
|
5
|
+
UNCOLORED = 0
|
6
|
+
MARKER_OUTPUT = 1
|
7
|
+
ISSUANCE = 2
|
8
|
+
TRANSFER = 3
|
9
|
+
|
10
|
+
# get all enum.
|
11
|
+
def self.all
|
12
|
+
self.constants.map{|name|self.const_get(name)}
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
# Represents a transaction output and its asset ID and asset quantity.
|
5
|
+
class TransactionOutput
|
6
|
+
attr_accessor :value
|
7
|
+
attr_accessor :script
|
8
|
+
attr_accessor :asset_id
|
9
|
+
attr_accessor :asset_quantity
|
10
|
+
attr_accessor :output_type
|
11
|
+
|
12
|
+
# @param [Integer] value The satoshi value of the output.
|
13
|
+
# @param [Bitcoin::Script] script The script controlling redemption of the output.
|
14
|
+
# @param [String] asset_id The asset ID of the output.
|
15
|
+
# @param [Integer] asset_quantity The asset quantity of the output.
|
16
|
+
# @param [OpenAssets::Transaction::OutPutType] output_type The type of the output.
|
17
|
+
def initialize(value, script, asset_id = nil, asset_quantity = 0, output_type = OutputType::UNCOLORED)
|
18
|
+
raise ArgumentError, "invalid output_type : #{output_type}" unless OutputType.all.include?(output_type)
|
19
|
+
raise ArgumentError, "invalid asset_quantity asset_quantity should be unsignd integer. " unless asset_quantity.between?(0, MarkerOutput::MAX_ASSET_QUANTITY)
|
20
|
+
@value = value
|
21
|
+
@script = script
|
22
|
+
@asset_id = asset_id
|
23
|
+
@asset_quantity = asset_quantity
|
24
|
+
@output_type = output_type
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
module OpenAssets
|
4
|
+
module Provider
|
5
|
+
|
6
|
+
# The implementation of BlockChain provider using Bitcoin Core.
|
7
|
+
class BitcoinCoreProvider < BlockChainProviderBase
|
8
|
+
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get an array of unspent transaction outputs belonging to this wallet.
|
16
|
+
# @param [Array] addresses If present, only outputs which pay an address in this array will be returned.
|
17
|
+
# @param [Integer] min The minimum number of confirmations the transaction containing an output must have in order to be returned. Default is 1.
|
18
|
+
# @param [Integer] max The maximum number of confirmations the transaction containing an output may have in order to be returned. Default is 9999999.
|
19
|
+
def list_unspent(addresses = [], min = 1 , max = 9999999)
|
20
|
+
request('listunspent', min, max, addresses)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get raw transaction.
|
24
|
+
# @param [String] transaction_hash The transaction hash.
|
25
|
+
# @param [String] verbose Whether to get the serialized or decoded transaction. 0: serialized transaction (Default). 1: decode transaction.
|
26
|
+
# @return [String] (if verbose=0)—the serialized transaction. (if verbose=1)—the decoded transaction. (if transaction not found)—nil.
|
27
|
+
def get_transaction(transaction_hash, verbose = 0)
|
28
|
+
begin
|
29
|
+
request('getrawtransaction', transaction_hash, verbose)
|
30
|
+
rescue OpenAssets::Provider::ApiError => e
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Signs a transaction in the serialized transaction format using private keys.
|
36
|
+
# @param [String] tx The serialized format transaction.
|
37
|
+
# @return [Bitcoin::Protocol::Tx] The signed transaction.
|
38
|
+
def sign_transaction(tx)
|
39
|
+
signed_tx = request('signrawtransaction', tx)
|
40
|
+
raise OpenAssets::Error, 'Could not sign the transaction.' unless signed_tx['complete']
|
41
|
+
Bitcoin::Protocol::Tx.new(signed_tx['hex'].htb)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Validates a transaction and broadcasts it to the peer-to-peer network.
|
45
|
+
# @param [String] tx The serialized format transaction.
|
46
|
+
# @return [String] The TXID or error message.
|
47
|
+
def send_transaction(tx)
|
48
|
+
request('sendrawtransaction', tx)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
# Convert decode tx string to Bitcion::Protocol::Tx
|
53
|
+
def decode_tx_to_btc_tx(tx)
|
54
|
+
hash = {
|
55
|
+
'version' => tx['version'],
|
56
|
+
'lock_time' => tx['locktime'],
|
57
|
+
'hex' => tx['hex'],
|
58
|
+
'txid' => tx['txid'],
|
59
|
+
'blockhash' => tx['blockhash'],
|
60
|
+
'confirmations' => tx['confirmations'],
|
61
|
+
'time' => tx['time'],
|
62
|
+
'blocktime' => tx['blocktime'],
|
63
|
+
'in' => tx['vin'].map{|input|
|
64
|
+
{'output_index' => input['vout'], 'previous_transaction_hash' => input['txid'], 'coinbase' => input['coinbase'],
|
65
|
+
'scriptSig' => input['scriptSig']['asm'], 'sequence' => input['sequence']}},
|
66
|
+
'out' => tx['vout'].map{|out|
|
67
|
+
{'amount' => out['value'], 'scriptPubKey' => out['scriptPubKey']['asm']}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
Bitcoin::Protocol::Tx.from_hash(hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def server_url
|
75
|
+
url = "#{@config[:schema]}://"
|
76
|
+
url.concat "#{@config[:user]}:#{@config[:password]}@"
|
77
|
+
url.concat "#{@config[:host]}:#{@config[:port]}"
|
78
|
+
url
|
79
|
+
end
|
80
|
+
|
81
|
+
def request(command, *params)
|
82
|
+
data = {
|
83
|
+
:method => command,
|
84
|
+
:params => params,
|
85
|
+
:id => 'jsonrpc'
|
86
|
+
}
|
87
|
+
RestClient.post(server_url, data.to_json, content_type: :json) do |respdata, request, result|
|
88
|
+
response = JSON.parse(respdata)
|
89
|
+
raise ApiError, response['error'] if response['error']
|
90
|
+
response['result']
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Provider
|
3
|
+
|
4
|
+
# The base class providing access to the Blockchain.
|
5
|
+
class BlockChainProviderBase
|
6
|
+
|
7
|
+
def list_unspent(addresses = [], min = 1 , max = 9999999)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_transaction(transaction_hash, verbose = 0)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def sign_transaction(tx)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Transaction
|
3
|
+
|
4
|
+
# The combination of a transaction hash and an index n into its vout
|
5
|
+
class OutPoint
|
6
|
+
|
7
|
+
attr_accessor :hash
|
8
|
+
attr_accessor :index
|
9
|
+
|
10
|
+
# @param [String] hash: 32 bytes transaction hash in vout.
|
11
|
+
# @param [Integer] index: index in vout.
|
12
|
+
def initialize(hash, index)
|
13
|
+
raise ArgumentError, 'hash must be exactly 32 bytes.' unless [hash].pack("H*").bytesize == 32
|
14
|
+
raise ArgumentError, 'index must be in range 0x0 to 0xffffffff.' unless index.between?(0x0, 0xffffffff)
|
15
|
+
@hash = hash
|
16
|
+
@index = index
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Transaction
|
3
|
+
|
4
|
+
# A transaction output with information about the asset ID and asset quantity associated to it.
|
5
|
+
class SpendableOutput
|
6
|
+
|
7
|
+
# An object that can be used to locate the output.
|
8
|
+
attr_accessor :out_point
|
9
|
+
# The actual output object.
|
10
|
+
attr_accessor :output
|
11
|
+
|
12
|
+
attr_accessor :confirmations
|
13
|
+
|
14
|
+
# @param [OpenAssets::Transaction::OutPoint] out_point
|
15
|
+
# @param [OpenAssets::Protocol::TransactionOutput] output
|
16
|
+
def initialize(out_point, output)
|
17
|
+
@out_point = out_point
|
18
|
+
@output = output
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Transaction
|
3
|
+
|
4
|
+
class TransactionBuilder
|
5
|
+
include OpenAssets::Util
|
6
|
+
|
7
|
+
# The minimum allowed output value.
|
8
|
+
attr_accessor :amount
|
9
|
+
|
10
|
+
def initialize(amount = 600)
|
11
|
+
@amount = amount
|
12
|
+
end
|
13
|
+
|
14
|
+
# issue asset.
|
15
|
+
# @param [TransferParameters] issue_spec The parameters of the issuance.
|
16
|
+
# @param [bytes] metadata The metadata to be embedded in the transaction.
|
17
|
+
# @param [Integer] fees The fees to include in the transaction.
|
18
|
+
# @return An unsigned transaction for issuing asset.
|
19
|
+
def issue_asset(issue_spec, metadata, fees)
|
20
|
+
inputs, total_amount =
|
21
|
+
TransactionBuilder.collect_uncolored_outputs(issue_spec.unspent_outputs, 2 * @amount + fees)
|
22
|
+
tx = Bitcoin::Protocol::Tx.new
|
23
|
+
inputs.each { |spendable|
|
24
|
+
script_sig = spendable.output.script.to_binary
|
25
|
+
tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(spendable.out_point.hash, spendable.out_point.index)
|
26
|
+
tx_in.script_sig = script_sig
|
27
|
+
tx.add_in(tx_in)
|
28
|
+
}
|
29
|
+
issue_address = oa_address_to_address(issue_spec.to_script)
|
30
|
+
from_address = oa_address_to_address(issue_spec.change_script)
|
31
|
+
validate_address([issue_address, from_address])
|
32
|
+
tx.add_out(create_colored_output(issue_address))
|
33
|
+
tx.add_out(create_marker_output([issue_spec.amount], metadata))
|
34
|
+
tx.add_out(create_uncolored_output(from_address, total_amount - @amount - fees))
|
35
|
+
tx
|
36
|
+
end
|
37
|
+
|
38
|
+
def transfer_assets
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def transfer_btc
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# collect uncolored outputs in unspent outputs(contains colored output).
|
47
|
+
# @param [Array[OpenAssets::Transaction::SpendableOutput]] unspent_outputs The Array of available outputs.
|
48
|
+
# @param [Integer] amount The amount to collect.
|
49
|
+
# @return [Array] inputs, total_amount
|
50
|
+
def self.collect_uncolored_outputs(unspent_outputs, amount)
|
51
|
+
total_amount = 0
|
52
|
+
results = []
|
53
|
+
unspent_outputs.each do |output|
|
54
|
+
if output.output.asset_id.nil?
|
55
|
+
results << output
|
56
|
+
total_amount += output.output.value
|
57
|
+
end
|
58
|
+
return results, total_amount if total_amount >= amount
|
59
|
+
end
|
60
|
+
raise InsufficientFundsError
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
# create colored output.
|
65
|
+
# @param [String] address The Bitcoin address.
|
66
|
+
# @return [Bitcoin::Protocol::TxOut] colored output
|
67
|
+
def create_colored_output(address)
|
68
|
+
hash160 = Bitcoin.hash160_from_address(address)
|
69
|
+
Bitcoin::Protocol::TxOut.new(@amount,
|
70
|
+
Bitcoin::Script.new(Bitcoin::Script.to_hash160_script(hash160)).to_payload)
|
71
|
+
end
|
72
|
+
|
73
|
+
# create marker output.
|
74
|
+
# @param [Array] asset_quantities asset_quantity array.
|
75
|
+
# @param [String] metadata
|
76
|
+
# @return [Bitcoin::Protocol::TxOut] the marker output.
|
77
|
+
def create_marker_output(asset_quantities, metadata)
|
78
|
+
script = OpenAssets::Protocol::MarkerOutput.new(asset_quantities, metadata).build_script
|
79
|
+
Bitcoin::Protocol::TxOut.new(0, script.to_payload)
|
80
|
+
end
|
81
|
+
|
82
|
+
# create an uncolored output.
|
83
|
+
# @param [String] address: The Bitcoin address.
|
84
|
+
# @param [Integer] value: The satoshi value of the output.
|
85
|
+
# @return [Bitcoin::Protocol::TxOut] an uncolored output.
|
86
|
+
def create_uncolored_output(address, value)
|
87
|
+
raise DustOutputError if value < @amount
|
88
|
+
hash160 = Bitcoin.hash160_from_address(address)
|
89
|
+
Bitcoin::Protocol::TxOut.new(value, Bitcoin::Script.new(Bitcoin::Script.to_hash160_script(hash160)).to_payload)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Transaction
|
3
|
+
|
4
|
+
# The value object of a bitcoin or asset transfer.
|
5
|
+
class TransferParameters
|
6
|
+
|
7
|
+
attr_accessor :unspent_outputs
|
8
|
+
attr_accessor :amount
|
9
|
+
attr_accessor :change_script
|
10
|
+
attr_accessor :to_script
|
11
|
+
|
12
|
+
# initialize
|
13
|
+
# @param [Array[OpenAssets::Transaction::SpendableOutput]] unspent_outputs Array of the unspent outputs available for the transaction.
|
14
|
+
# @param [String] to_script the output script to which to send the assets or bitcoins.
|
15
|
+
# @param [String] change_script the output script to which to send any remaining change.
|
16
|
+
# @param [Integer] amount The asset quantity or amount of the satoshi sent in the transaction.
|
17
|
+
def initialize(unspent_outputs, to_script, change_script, amount)
|
18
|
+
@unspent_outputs = unspent_outputs
|
19
|
+
@to_script = to_script
|
20
|
+
@change_script = change_script
|
21
|
+
@amount = amount
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Transaction
|
3
|
+
autoload :TransactionBuilder, 'openassets/transaction/transaction_builder'
|
4
|
+
autoload :TransferParameters, 'openassets/transaction/transfer_parameters'
|
5
|
+
autoload :SpendableOutput, 'openassets/transaction/spendable_output'
|
6
|
+
autoload :OutPoint, 'openassets/transaction/out_point'
|
7
|
+
autoload :TransactionBuildError, 'openassets/transaction/transaction_build_error'
|
8
|
+
autoload :InsufficientFundsError, 'openassets/transaction/insufficient_funds_error'
|
9
|
+
autoload :DustOutputError, 'openassets/transaction/dust_output_error'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
module Util
|
3
|
+
extend ::Bitcoin::Util
|
4
|
+
include ::Bitcoin::Util
|
5
|
+
|
6
|
+
# namespace of Open Asset
|
7
|
+
OA_NAMESPACE = 19
|
8
|
+
|
9
|
+
# version byte for Open Assets Address
|
10
|
+
OA_VERSION_BYTE = 23
|
11
|
+
|
12
|
+
# convert bitcoin address to open assets address
|
13
|
+
# @param [String] btc_address The Bitcoin address.
|
14
|
+
# @return [String] The Open Assets Address.
|
15
|
+
def address_to_oa_address(btc_address)
|
16
|
+
btc_hex = decode_base58(btc_address)
|
17
|
+
btc_hex = '0' +btc_hex if btc_hex.size==47
|
18
|
+
address = btc_hex[0..-9] # bitcoin address without checksum
|
19
|
+
named_addr = OA_NAMESPACE.to_s(16) + address
|
20
|
+
oa_checksum = checksum(named_addr)
|
21
|
+
encode_base58(named_addr + oa_checksum)
|
22
|
+
end
|
23
|
+
|
24
|
+
# convert open assets address to bitcoin address
|
25
|
+
# @param [String] oa_address The Open Assets Address.
|
26
|
+
# @return [String] The Bitcoin address.
|
27
|
+
def oa_address_to_address(oa_address)
|
28
|
+
decode_address = decode_base58(oa_address)
|
29
|
+
btc_addr = decode_address[2..-9]
|
30
|
+
btc_checksum = checksum(btc_addr)
|
31
|
+
encode_base58(btc_addr + btc_checksum)
|
32
|
+
end
|
33
|
+
|
34
|
+
# generate asset ID from public key.
|
35
|
+
def generate_asset_id(pub_key)
|
36
|
+
pubkey_hash_to_asset_id(hash160(pub_key))
|
37
|
+
end
|
38
|
+
|
39
|
+
def pubkey_hash_to_asset_id(hash)
|
40
|
+
# gen P2PKH script hash
|
41
|
+
# P2PKH script = OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
|
42
|
+
# (76=OP_DUP, a9=OP_HASH160, 14=Bytes to push, 88=OP_EQUALVERIFY, ac=OP_CHECKSIG)
|
43
|
+
script = hash160(["76", "a9", "14", hash, "88", "ac"].join)
|
44
|
+
script = OA_VERSION_BYTE.to_s(16) + script # add version byte to script hash
|
45
|
+
encode_base58(script + checksum(script)) # add checksum & encode
|
46
|
+
end
|
47
|
+
|
48
|
+
# LEB128 encode
|
49
|
+
def encode_leb128(value)
|
50
|
+
d7=->n{(n>>7)==0 ? [n] : d7[n>>7]+[127 & n]}
|
51
|
+
msb=->a{a0=a[0].to_s(16);[(a[0]< 16 ? "0"+a0 : a0)]+a[1..-1].map{|x|(x|128).to_s(16)}}
|
52
|
+
leb128=->n{msb[d7[n]].reverse.join}
|
53
|
+
leb128[value]
|
54
|
+
end
|
55
|
+
|
56
|
+
# LEB128 decode
|
57
|
+
def decode_leb128(value)
|
58
|
+
mbs = to_bytes(value).map{|x|(x.to_i(16)>=128 ? x : x+"|")}.join.split('|')
|
59
|
+
num=->a{(a.size==1 ? a[0] : (num[a[0..-2]]<<7)|a[-1])}
|
60
|
+
r7=->n{to_bytes(n).map{|x|(x.to_i(16))&127}}
|
61
|
+
mbs.map{|x|num[r7[x].reverse]}
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_bytes(string)
|
65
|
+
string.each_char.each_slice(2).map{|v|v.join}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Convert satoshi to coin.
|
69
|
+
# @param [Integer] satoshi The amount of satoshi unit.
|
70
|
+
# @return [String] The amount of coin.
|
71
|
+
def satoshi_to_coin(satoshi)
|
72
|
+
"%.8f" % (satoshi / 100000000.0)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get address from script.
|
76
|
+
# @param [Bitcoin::Script] script The output script.
|
77
|
+
# @return [String] The Bitcoin address. if the script dose not contains address, return nil.
|
78
|
+
def script_to_address(script)
|
79
|
+
return script.get_pubkey_address if script.is_pubkey?
|
80
|
+
return script.get_hash160_address if script.is_hash160?
|
81
|
+
return script.get_multisig_addresses if script.is_multisig?
|
82
|
+
return script.get_p2sh_address if script.is_p2sh?
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# validate bitcoin address
|
87
|
+
def validate_address(addresses)
|
88
|
+
addresses.each{|a|
|
89
|
+
raise ArgumentError, "#{a} is invalid bitcoin address. " unless valid_address?(a)
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/openassets.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bitcoin'
|
2
|
+
module OpenAssets
|
3
|
+
|
4
|
+
autoload :Protocol, 'openassets/protocol'
|
5
|
+
autoload :Transaction, 'openassets/transaction'
|
6
|
+
autoload :VERSION, 'openassets/version'
|
7
|
+
autoload :Util, 'openassets/util'
|
8
|
+
autoload :Api, 'openassets/api'
|
9
|
+
autoload :Provider, 'openassets/provider'
|
10
|
+
autoload :Error, 'openassets/error'
|
11
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'openassets/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "openassets-ruby"
|
8
|
+
spec.version = OpenAssets::VERSION
|
9
|
+
spec.authors = ["azuchi"]
|
10
|
+
spec.email = ["azuchi@haw.co.jp"]
|
11
|
+
|
12
|
+
spec.summary = %q{The implementation of the Open Assets Protocol for Ruby.}
|
13
|
+
spec.description = %q{The implementation of the Open Assets Protocol for Ruby.}
|
14
|
+
spec.homepage = "https://github.com/haw-itn/openassets-ruby"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
spec.add_runtime_dependency "bitcoin-ruby", "~> 0.0.7"
|
22
|
+
spec.add_runtime_dependency "ffi", "~>1.9.8"
|
23
|
+
spec.add_runtime_dependency "rest-client", "~>1.8.0"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openassets-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- azuchi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bitcoin-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.7
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.7
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ffi
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.9.8
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.9.8
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rest-client
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.8.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.8.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.9'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: The implementation of the Open Assets Protocol for Ruby.
|
98
|
+
email:
|
99
|
+
- azuchi@haw.co.jp
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".ruby-gemset"
|
107
|
+
- ".ruby-version"
|
108
|
+
- CODE_OF_CONDUCT.md
|
109
|
+
- Gemfile
|
110
|
+
- LICENSE
|
111
|
+
- LICENSE.txt
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- bin/console
|
115
|
+
- bin/setup
|
116
|
+
- lib/openassets.rb
|
117
|
+
- lib/openassets/api.rb
|
118
|
+
- lib/openassets/error.rb
|
119
|
+
- lib/openassets/protocol.rb
|
120
|
+
- lib/openassets/protocol/marker_output.rb
|
121
|
+
- lib/openassets/protocol/output_type.rb
|
122
|
+
- lib/openassets/protocol/transaction_output.rb
|
123
|
+
- lib/openassets/provider.rb
|
124
|
+
- lib/openassets/provider/api_error.rb
|
125
|
+
- lib/openassets/provider/bitcoin_core_provider.rb
|
126
|
+
- lib/openassets/provider/block_chain_provider_base.rb
|
127
|
+
- lib/openassets/transaction.rb
|
128
|
+
- lib/openassets/transaction/dust_output_error.rb
|
129
|
+
- lib/openassets/transaction/insufficient_funds_error.rb
|
130
|
+
- lib/openassets/transaction/out_point.rb
|
131
|
+
- lib/openassets/transaction/spendable_output.rb
|
132
|
+
- lib/openassets/transaction/transaction_build_error.rb
|
133
|
+
- lib/openassets/transaction/transaction_builder.rb
|
134
|
+
- lib/openassets/transaction/transfer_parameters.rb
|
135
|
+
- lib/openassets/util.rb
|
136
|
+
- lib/openassets/version.rb
|
137
|
+
- openassets-ruby.gemspec
|
138
|
+
homepage: https://github.com/haw-itn/openassets-ruby
|
139
|
+
licenses:
|
140
|
+
- MIT
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 2.4.6
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: The implementation of the Open Assets Protocol for Ruby.
|
162
|
+
test_files: []
|