counterparty_ruby 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +46 -0
- data/README.md +197 -0
- data/Rakefile +28 -0
- data/counterparty_ruby.gemspec +26 -0
- data/lib/counterparty/connection.rb +200 -0
- data/lib/counterparty/raw_tx.rb +130 -0
- data/lib/counterparty/resource.rb +179 -0
- data/lib/counterparty/resources.rb +713 -0
- data/lib/counterparty/version.rb +4 -0
- data/lib/counterparty_ruby.rb +70 -0
- data/spec/config.yml +3 -0
- data/spec/connection_spec.rb +14 -0
- data/spec/exceptions_spec.rb +38 -0
- data/spec/rawtx_decode_spec.rb +122 -0
- data/spec/resource_create_spec.rb +69 -0
- data/spec/resource_read_spec.rb +91 -0
- data/spec/spec_helper.rb +29 -0
- metadata +185 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
counterparty_ruby (0.9.0)
|
5
|
+
bitcoin-ruby
|
6
|
+
ffi
|
7
|
+
rest_client
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
bitcoin-ruby (0.0.6)
|
13
|
+
diff-lcs (1.2.5)
|
14
|
+
ffi (1.9.6)
|
15
|
+
json (1.8.1)
|
16
|
+
netrc (0.7.9)
|
17
|
+
rake (10.3.2)
|
18
|
+
rdoc (4.2.0)
|
19
|
+
json (~> 1.4)
|
20
|
+
rest_client (1.8.2)
|
21
|
+
netrc (~> 0.7.7)
|
22
|
+
rspec (3.1.0)
|
23
|
+
rspec-core (~> 3.1.0)
|
24
|
+
rspec-expectations (~> 3.1.0)
|
25
|
+
rspec-mocks (~> 3.1.0)
|
26
|
+
rspec-core (3.1.7)
|
27
|
+
rspec-support (~> 3.1.0)
|
28
|
+
rspec-expectations (3.1.2)
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
+
rspec-support (~> 3.1.0)
|
31
|
+
rspec-its (1.1.0)
|
32
|
+
rspec-core (>= 3.0.0)
|
33
|
+
rspec-expectations (>= 3.0.0)
|
34
|
+
rspec-mocks (3.1.3)
|
35
|
+
rspec-support (~> 3.1.0)
|
36
|
+
rspec-support (3.1.2)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
counterparty_ruby!
|
43
|
+
rake
|
44
|
+
rdoc
|
45
|
+
rspec
|
46
|
+
rspec-its
|
data/README.md
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
counterparty_ruby
|
2
|
+
=================
|
3
|
+
A ruby gem for communicating with a Counterparty (Bitcoin / XCP) API server.
|
4
|
+
|
5
|
+
What's up party people! [Chris DeRose](https://www.chrisderose.com) here,
|
6
|
+
community director of the [Counterparty Foundation](http://counterpartyfoundation.org/),
|
7
|
+
and today we're going to discuss.... Ruby! Or more specifically, how to start using
|
8
|
+
counterparty in your ruby and/or ruby on rails app.
|
9
|
+
|
10
|
+
This gem is designed to abstract communications with the counterpartyd api
|
11
|
+
server in an ActiveRecord-esque object model. But note that we also support
|
12
|
+
calling the api methods directly via the documented api calls in the offical docs.
|
13
|
+
|
14
|
+
Below you'll see some examples to get you started, but feel free to peruse the
|
15
|
+
specs for yet more examples. These examples all assume that you're running a
|
16
|
+
working counterpartyd on the same system that the code is running on, but it's
|
17
|
+
easy to specify another server connection if you'd like. (Just set the
|
18
|
+
Counterparty.connection to a connection object that references the correct API
|
19
|
+
server).
|
20
|
+
|
21
|
+
We hope you find this useful everyone, let us know if there are any issues, and
|
22
|
+
keep us updated on the apps you're building! We're working to make this
|
23
|
+
counterparty spool-up process as simple as possible, so bear with us while we're
|
24
|
+
setting everything up.
|
25
|
+
|
26
|
+
![Party On Wayne](http://data.whicdn.com/images/24796384/tumblr_m0ng6rBeWT1qhd0xso1_500_large.jpg)
|
27
|
+
|
28
|
+
## Examples
|
29
|
+
Documentation on the objects is available via:
|
30
|
+
* [counterparty_ruby's rubydoc](http://www.rubydoc.info/github/brighton36/counterparty_ruby/master),
|
31
|
+
* [The Counterparty official API guide](https://github.com/CounterpartyXCP/counterpartyd/blob/master/docs/API.rst#read-api-function-reference).
|
32
|
+
|
33
|
+
#### Find the first burn
|
34
|
+
Here we retrieve burns from the blockchain using ActiveRecord style method calls.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'counterparty_ruby'
|
38
|
+
|
39
|
+
Counterparty.production!
|
40
|
+
|
41
|
+
burns = Counterparty::Burn.find order_by: 'tx_hash', order_dir: 'asc',
|
42
|
+
start_block: 280537, end_block: 280539
|
43
|
+
|
44
|
+
puts "First burned via: %s" % burns.first.source
|
45
|
+
# => 1ADpYypUcnbezuuYpCyRCY7G4KD6a9YXiF
|
46
|
+
```
|
47
|
+
|
48
|
+
#### Find the first burn (Alternative API-like syntax)
|
49
|
+
This example achieves the same outcome as the above example, but uses a more
|
50
|
+
json-esque call syntax.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'counterparty_ruby'
|
54
|
+
|
55
|
+
# This connects to production mode on localhost:
|
56
|
+
production = Counterparty.connection.new 4000
|
57
|
+
|
58
|
+
# Note that we follow the api reference for calls here:
|
59
|
+
burns = production.get_burns order_by: 'tx_hash', order_dir: 'asc',
|
60
|
+
start_block: 280537, end_block: 280539
|
61
|
+
|
62
|
+
puts "First burned via: %s" % burns.first.source
|
63
|
+
# => 1ADpYypUcnbezuuYpCyRCY7G4KD6a9YXiF
|
64
|
+
```
|
65
|
+
|
66
|
+
#### Create an Issuance
|
67
|
+
Here we create an asset and persist that asset intothe blockchain using ActiveRecord style method calls.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
require 'counterparty_ruby'
|
71
|
+
|
72
|
+
# Note that we default to test mode. Too, this example requires that your
|
73
|
+
# private key for this address exists in the counterparty server's bitcoind
|
74
|
+
first_asset = Counterparty::Issuance.new(
|
75
|
+
source: 'msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2',
|
76
|
+
asset: 'MYFIRSTASSET',
|
77
|
+
description: "Its party time!",
|
78
|
+
divisible: false,
|
79
|
+
quantity: 100 )
|
80
|
+
|
81
|
+
transaction_id = first_asset.save!
|
82
|
+
|
83
|
+
puts "Transaction %s has been entered into the mempool" % transaction_id
|
84
|
+
```
|
85
|
+
|
86
|
+
#### Create an Issuance (Alternative API-like syntax)
|
87
|
+
This example achieves the same outcome as the above example, but uses a more
|
88
|
+
json-esque call syntax.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
require 'counterparty_ruby'
|
92
|
+
|
93
|
+
# Note that we default to test mode. Too, this example requires that your
|
94
|
+
# private key for this address exists in the counterparty server's bitcoind
|
95
|
+
transaction_id = Counterparty.connection.do_issuance(
|
96
|
+
source: 'msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2',
|
97
|
+
asset: 'MYFIRSTASSET',
|
98
|
+
description: "Its party time!",
|
99
|
+
divisible: false,
|
100
|
+
quantity: 100 )
|
101
|
+
|
102
|
+
puts "Transaction %s has been entered into the mempool" % transaction_id
|
103
|
+
```
|
104
|
+
|
105
|
+
#### Broadcast the outcome of an event
|
106
|
+
If you're the oracle, tasked with resolving a bet, here's how you would announce
|
107
|
+
an outcome to the network.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
require 'counterparty_ruby'
|
111
|
+
|
112
|
+
gold_down = Counterparty::Broadcast.new(
|
113
|
+
source: 'msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2',
|
114
|
+
fee_fraction: 0.05,
|
115
|
+
text: "Price of gold, 12AM UTC March1. 1=inc 2=dec/const",
|
116
|
+
timestamp: 1418926641,
|
117
|
+
value: 2 )
|
118
|
+
|
119
|
+
# Note that in this example, we're passing the private key that corresponds
|
120
|
+
# to the public key. This allows us to sign transactions without having to
|
121
|
+
# import the key onto the server's bitcoind. Note that this (currently) does
|
122
|
+
# pass the key to the counterpartyd server. This behavior may change in later
|
123
|
+
# versions.
|
124
|
+
tx_id = gold_down.save! 'cP7ufwcbZujaa1qkKthLbVZUaP88RS5r9awyXerJE5rAEMTRVmzc'
|
125
|
+
|
126
|
+
puts "Gold was broadcast down in transaction %s" % tx_id
|
127
|
+
```
|
128
|
+
|
129
|
+
#### Compile, Publish and Execute a Serpent Contract
|
130
|
+
This is still beta behavior, and only supported on testnet, but here's a quick
|
131
|
+
example of how Smart Contracts are published and executed. Note that we require
|
132
|
+
the serpent CLI executable is installed on the running system
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
require 'open3'
|
136
|
+
require 'counterparty_ruby'
|
137
|
+
|
138
|
+
class Serpent
|
139
|
+
# Be sure to use the version from : https://github.com/ethereum/pyethereum
|
140
|
+
SERPENT='/usr/local/bin/serpent'
|
141
|
+
|
142
|
+
def compile(contract)
|
143
|
+
serpent 'compile', '-s', :stdin_data => contract
|
144
|
+
end
|
145
|
+
|
146
|
+
def encode_datalist(data)
|
147
|
+
serpent 'encode_datalist', data
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def serpent(*args)
|
153
|
+
options = (args.last.kind_of? Hash) ? args.pop : {}
|
154
|
+
stdout, status = Open3.capture2( ([SERPENT]+args).join(' '), options)
|
155
|
+
|
156
|
+
raise StandardError, "Compile Failed: %s" % status.exitstatus unless status.success?
|
157
|
+
|
158
|
+
stdout.chomp
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
SOURCE_ADDRESS="msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2"
|
163
|
+
|
164
|
+
Counterparty.test!
|
165
|
+
|
166
|
+
serpent = Serpent.new
|
167
|
+
|
168
|
+
compiled_script = serpent.compile <<-eos
|
169
|
+
return(msg.data[0]*2)
|
170
|
+
eos
|
171
|
+
|
172
|
+
contract_id = Counterparty::Publish.new( source: SOURCE_ADDRESS,
|
173
|
+
code_hex: compiled_script, gasprice: 1, startgas: 1000000, endowment: 0,
|
174
|
+
allow_unconfirmed_inputs: true ).save!
|
175
|
+
|
176
|
+
datalist = serpent.encode_datalist '53'
|
177
|
+
|
178
|
+
execute_id = Counterparty::Execute.new( source: SOURCE_ADDRESS,
|
179
|
+
contract_id: contract_id, payload_hex: datalist, gasprice: 5,
|
180
|
+
startgas: 160000, value: 10, allow_unconfirmed_inputs: true ).save!
|
181
|
+
|
182
|
+
puts "Executed Transaction ID: %s" % execute_id
|
183
|
+
```
|
184
|
+
|
185
|
+
## Have questions?
|
186
|
+
The _best_ place to start is the [Counterparty API reference](https://github.com/CounterpartyXCP/counterpartyd/blob/master/docs/API.rst#read-api-function-reference).
|
187
|
+
You'll soon find that this gem is merely a wrapper around the official
|
188
|
+
counterpartyd json API.
|
189
|
+
|
190
|
+
But, if that doesn't help tweet [@derosetech](https://twitter.com/derosetech)
|
191
|
+
and/or check the [Counterparty Forums](https://forums.counterparty.io/) for more
|
192
|
+
help from the community.
|
193
|
+
|
194
|
+
We appreciate your patience if you're having problems, please bear in mind that
|
195
|
+
we're in active development mode. And thank-you for using Counterparty!
|
196
|
+
|
197
|
+
![Best Party Ever](http://www.quickmeme.com/img/c8/c8cc224c5b1e8b1baafeba4287d9534add53273bc79572a5fcc8ab8ab2cc19ab.jpg)
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# File: Rakefile
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/packagetask'
|
5
|
+
require 'rdoc/task'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
spec = Gem::Specification.load('counterparty_ruby.gemspec')
|
9
|
+
|
10
|
+
RDOC_FILES = FileList["README.md", "lib/*.rb", "lib/counterparty/*.rb"]
|
11
|
+
|
12
|
+
Rake::RDocTask.new do |rd|
|
13
|
+
rd.main = "README.rdoc"
|
14
|
+
rd.rdoc_dir = "doc/site/api"
|
15
|
+
rd.rdoc_files.include(RDOC_FILES)
|
16
|
+
end
|
17
|
+
|
18
|
+
Rake::RDocTask.new(:ri) do |rd|
|
19
|
+
rd.main = "README.md"
|
20
|
+
rd.rdoc_dir = "doc/ri"
|
21
|
+
rd.options << "--ri-system"
|
22
|
+
rd.rdoc_files.include(RDOC_FILES)
|
23
|
+
end
|
24
|
+
|
25
|
+
RSpec::Core::RakeTask.new('spec')
|
26
|
+
|
27
|
+
task :spec => :compile
|
28
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/counterparty/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Chris DeRose"]
|
6
|
+
gem.email = ["chris@chrisderose.com"]
|
7
|
+
gem.description = %q{This gem is designed to abstract communications with the counterpartyd api server in an ActiveRecord-esque object model}
|
8
|
+
gem.summary = %q{An ActiveRecord-esque abstraction of the Counterparty API}
|
9
|
+
gem.homepage = "https://github.com/brighton36/counterparty_ruby"
|
10
|
+
gem.files = `git ls-files`.split($\)
|
11
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
13
|
+
gem.name = "counterparty_ruby"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = Counterparty::VERSION
|
16
|
+
gem.required_ruby_version = '>= 1.9'
|
17
|
+
gem.license = 'LGPL'
|
18
|
+
|
19
|
+
['rest_client', 'bitcoin-ruby', 'ffi'].each do |dependency|
|
20
|
+
gem.add_runtime_dependency dependency
|
21
|
+
end
|
22
|
+
['rspec', 'rspec-its', 'rake', 'rdoc'].each do |dependency|
|
23
|
+
gem.add_development_dependency dependency
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module Counterparty
|
2
|
+
# This class connects to the Counterparty api. Mostly it's not intended for
|
3
|
+
# use by library consumers, but there are some helper methods in here for
|
4
|
+
# those that prefer the Connection.get_burns syntax instead of the
|
5
|
+
# Counterparty::Burn.find syntax
|
6
|
+
class Connection
|
7
|
+
# The default connection timeout, nil is "no timeout"
|
8
|
+
DEFAULT_TIMEOUT = nil
|
9
|
+
|
10
|
+
attr_accessor :host, :port, :username, :password
|
11
|
+
|
12
|
+
# A response timeout threshold By default, this initializes to -1,
|
13
|
+
# which means the library will wait indefinitely before timing out
|
14
|
+
attr_writer :timeout
|
15
|
+
|
16
|
+
def initialize(port=14000, username='rpc', password='1234', host='localhost')
|
17
|
+
@host,@port,@username,@password=host.to_s,port.to_i,username.to_s,password.to_s
|
18
|
+
@timeout = DEFAULT_TIMEOUT
|
19
|
+
end
|
20
|
+
|
21
|
+
# The url being connected to for the purpose of an api call
|
22
|
+
def api_url
|
23
|
+
'http://%s:%s@%s:%s/api/' % [@username,@password,@host,@port.to_s]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns a signed raw transaction, given a private key
|
27
|
+
def sign_tx(raw_tx, private_key)
|
28
|
+
request 'sign_tx', unsigned_tx_hex: raw_tx, privkey: private_key
|
29
|
+
end
|
30
|
+
|
31
|
+
# Broadcasts a signed transaction onto the bitcoin blockchain
|
32
|
+
def broadcast_tx(signed_tx)
|
33
|
+
request 'broadcast_tx', signed_tx_hex: signed_tx
|
34
|
+
end
|
35
|
+
|
36
|
+
# Issue a request to the counterpartyd server for the given method, with the
|
37
|
+
# given params.
|
38
|
+
def request(method, params)
|
39
|
+
client = RestClient::Resource.new api_url, :timeout => @timeout
|
40
|
+
response = JSON.parse client.post({ method: method,
|
41
|
+
params: params, jsonrpc: '2.0', id: '0' }.to_json,
|
42
|
+
user: @username, password: @password, accept: 'json',
|
43
|
+
content_type: 'json' )
|
44
|
+
|
45
|
+
raise JsonResponseError.new response if response.has_key? 'code'
|
46
|
+
raise ResponseError.new response['error'] if response.has_key? 'error'
|
47
|
+
|
48
|
+
response['result']
|
49
|
+
end
|
50
|
+
|
51
|
+
# This creates the legacy api-format request methods on the connection
|
52
|
+
# argument. It lets us be relatively introspective about how we do this so
|
53
|
+
# as to be future-proof here going forward
|
54
|
+
def self.resource_request(klass) # :nodoc:
|
55
|
+
define_method(klass.to_get_request){ |params| klass.find params }
|
56
|
+
define_method(klass.to_do_request){ |params| klass.create(params).save! }
|
57
|
+
define_method(klass.to_create_request){ |params| klass.create(params).to_raw_tx }
|
58
|
+
end
|
59
|
+
|
60
|
+
# :method: do_balance
|
61
|
+
# # Sends a do_balance call to the counterpartyd server, given the provided params
|
62
|
+
|
63
|
+
# :method: create_balance
|
64
|
+
# # Sends a create_balance call to the counterpartyd server, given the provided params
|
65
|
+
|
66
|
+
# :method: do_bet
|
67
|
+
# # Sends a do_bet call to the counterpartyd server, given the provided params
|
68
|
+
|
69
|
+
# :method: create_bet
|
70
|
+
# # Sends a create_bet call to the counterpartyd server, given the provided params
|
71
|
+
|
72
|
+
# :method: do_betmatch
|
73
|
+
# # Sends a do_betmatch call to the counterpartyd server, given the provided params
|
74
|
+
|
75
|
+
# :method: create_betmatch
|
76
|
+
# # Sends a create_betmatch call to the counterpartyd server, given the provided params
|
77
|
+
|
78
|
+
# :method: do_broadcast
|
79
|
+
# # Sends a do_broadcast call to the counterpartyd server, given the provided params
|
80
|
+
|
81
|
+
# :method: create_broadcast
|
82
|
+
# # Sends a create_broadcast call to the counterpartyd server, given the provided params
|
83
|
+
|
84
|
+
# :method: do_btcpays
|
85
|
+
# # Sends a do_btcpays call to the counterpartyd server, given the provided params
|
86
|
+
|
87
|
+
# :method: create_btcpay
|
88
|
+
# # Sends a create_btcpay call to the counterpartyd server, given the provided params
|
89
|
+
|
90
|
+
# :method: do_burn
|
91
|
+
# # Sends a do_burn call to the counterpartyd server, given the provided params
|
92
|
+
|
93
|
+
# :method: create_burn
|
94
|
+
# # Sends a create_burn call to the counterpartyd server, given the provided params
|
95
|
+
|
96
|
+
# :method: do_callback
|
97
|
+
# # Sends a do_callback call to the counterpartyd server, given the provided params
|
98
|
+
|
99
|
+
# :method: create_callback
|
100
|
+
# # Sends a create_callback call to the counterpartyd server, given the provided params
|
101
|
+
|
102
|
+
# :method: do_cancel
|
103
|
+
# # Sends a do_cancel call to the counterpartyd server, given the provided params
|
104
|
+
|
105
|
+
# :method: create_cancel
|
106
|
+
# # Sends a create_cancel call to the counterpartyd server, given the provided params
|
107
|
+
|
108
|
+
# :method: do_credit
|
109
|
+
# # Sends a do_credit call to the counterpartyd server, given the provided params
|
110
|
+
|
111
|
+
# :method: create_credit
|
112
|
+
# # Sends a create_credit call to the counterpartyd server, given the provided params
|
113
|
+
|
114
|
+
# :method: do_debit
|
115
|
+
# # Sends a do_debit call to the counterpartyd server, given the provided params
|
116
|
+
|
117
|
+
# :method: create_debit
|
118
|
+
# # Sends a create_debit call to the counterpartyd server, given the provided params
|
119
|
+
|
120
|
+
# :method: do_dividend
|
121
|
+
# # Sends a do_dividend call to the counterpartyd server, given the provided params
|
122
|
+
|
123
|
+
# :method: create_dividend
|
124
|
+
# # Sends a create_dividend call to the counterpartyd server, given the provided params
|
125
|
+
|
126
|
+
# :method: do_issuance
|
127
|
+
# # Sends a do_issuance call to the counterpartyd server, given the provided params
|
128
|
+
|
129
|
+
# :method: create_issuance
|
130
|
+
# # Sends a create_issuance call to the counterpartyd server, given the provided params
|
131
|
+
|
132
|
+
# :method: do_order
|
133
|
+
# # Sends a do_order call to the counterpartyd server, given the provided params
|
134
|
+
|
135
|
+
# :method: create_order
|
136
|
+
# # Sends a create_order call to the counterpartyd server, given the provided params
|
137
|
+
|
138
|
+
# :method: do_ordermatch
|
139
|
+
# # Sends a do_ordermatch call to the counterpartyd server, given the provided params
|
140
|
+
|
141
|
+
# :method: create_ordermatch
|
142
|
+
# # Sends a create_ordermatch call to the counterpartyd server, given the provided params
|
143
|
+
|
144
|
+
# :method: do_send
|
145
|
+
# # Sends a do_send call to the counterpartyd server, given the provided params
|
146
|
+
|
147
|
+
# :method: create_send
|
148
|
+
# # Sends a create_send call to the counterpartyd server, given the provided params
|
149
|
+
|
150
|
+
# :method: do_message
|
151
|
+
# # Sends a do_message call to the counterpartyd server, given the provided params
|
152
|
+
|
153
|
+
# :method: create_message
|
154
|
+
# # Sends a create_message call to the counterpartyd server, given the provided params
|
155
|
+
|
156
|
+
# :method: do_callback
|
157
|
+
# # Sends a do_callback call to the counterpartyd server, given the provided params
|
158
|
+
|
159
|
+
# :method: create_callback
|
160
|
+
# # Sends a create_callback call to the counterpartyd server, given the provided params
|
161
|
+
|
162
|
+
# :method: do_betexpiration
|
163
|
+
# # Sends a do_betexpiration call to the counterpartyd server, given the provided params
|
164
|
+
|
165
|
+
# :method: create_betexpiration
|
166
|
+
# # Sends a create_betexpiration call to the counterpartyd server, given the provided params
|
167
|
+
|
168
|
+
# :method: do_orderexpiration
|
169
|
+
# # Sends a do_orderexpiration call to the counterpartyd server, given the provided params
|
170
|
+
|
171
|
+
# :method: create_orderexpiration
|
172
|
+
# # Sends a create_orderexpiration call to the counterpartyd server, given the provided params
|
173
|
+
|
174
|
+
# :method: do_betmatchexpiration
|
175
|
+
# # Sends a do_betmatchexpiration call to the counterpartyd server, given the provided params
|
176
|
+
|
177
|
+
# :method: create_betmatchexpiration
|
178
|
+
# # Sends a create_betmatchexpiration call to the counterpartyd server, given the provided params
|
179
|
+
|
180
|
+
# :method: do_ordermatchexpiration
|
181
|
+
# # Sends a do_ordermatchexpiration call to the counterpartyd server, given the provided params
|
182
|
+
|
183
|
+
# :method: create_ordermatchexpiration
|
184
|
+
# # Sends a create_ordermatchexpiration call to the counterpartyd server, given the provided params
|
185
|
+
|
186
|
+
# Go ahead and setup the defined resources, and throw them into the native-style
|
187
|
+
# api methods:
|
188
|
+
Counterparty.constants.each do |c|
|
189
|
+
begin
|
190
|
+
klass = Counterparty.const_get(c)
|
191
|
+
if klass.respond_to?(:api_name) && klass != Counterparty::CounterResource
|
192
|
+
self.resource_request klass
|
193
|
+
end
|
194
|
+
rescue NameError
|
195
|
+
next
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# This class is mostly used to decode json transactions from raw transactions
|
2
|
+
# much of this implementation was inspired from:
|
3
|
+
# https://gist.github.com/shesek/5835695
|
4
|
+
class RawTx
|
5
|
+
# This is a map of offset to characters used for decoding base64 strings:
|
6
|
+
BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
7
|
+
|
8
|
+
MAX_32BIT_INTEGER=2**32-1
|
9
|
+
|
10
|
+
# Creates a raw transaction from a hexstring
|
11
|
+
def initialize(as_hexstring)
|
12
|
+
@hexstring = as_hexstring.downcase
|
13
|
+
raise ArgumentError, "Unsupported hex" unless /\A[0-9a-f]+\Z/.match @hexstring
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns this transaction in a standard json format
|
17
|
+
def to_hash(options = {})
|
18
|
+
bytes = @hexstring.scan(/../).collect(&:hex)
|
19
|
+
size = bytes.length
|
20
|
+
|
21
|
+
# Now we start shift elements off the byte stack:
|
22
|
+
version = shift_u32(bytes)
|
23
|
+
|
24
|
+
raise ArgumentError, "Unsupported Version/Tx" unless version == 0x01
|
25
|
+
|
26
|
+
# Parse the inputs:
|
27
|
+
ins = (0...shift_varint(bytes)).collect do
|
28
|
+
hash = bytes.slice!(0,32).reverse.collect{|n| '%02x' % n}.join
|
29
|
+
index,script,seq = shift_u32(bytes),shift_varchar(bytes),shift_u32(bytes)
|
30
|
+
|
31
|
+
# NOTE: We may want to base64 encode the hash, or support this via an
|
32
|
+
# option : self.class.bytes_to_base64_s(hash).reverse),
|
33
|
+
|
34
|
+
# NOTE: The :seq field isnt actually used right now, so some rawtx decoders
|
35
|
+
# return the varint (like decoder), and some return UINT_MAX (4294967295)
|
36
|
+
{ 'txid' => hash, 'vout' => index, 'sequence' => MAX_32BIT_INTEGER,
|
37
|
+
'scriptSig' => {'hex' => hex_stringify(script),
|
38
|
+
'asm' => disassemble_script(script) } }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Parse outputs:
|
42
|
+
outs = (0...shift_varint(bytes)).collect do |i|
|
43
|
+
value, script = shift_u64(bytes), shift_varchar(bytes)
|
44
|
+
|
45
|
+
{ 'value' => self.class.bytes_to_ui(value).to_f/1e8, 'n' => i,
|
46
|
+
'scriptPubKey' => {'hex' => hex_stringify(script),
|
47
|
+
'asm' => disassemble_script(script) } }
|
48
|
+
end
|
49
|
+
|
50
|
+
lock_time = shift_u32 bytes
|
51
|
+
|
52
|
+
{'vin' => ins, 'vout' => outs, 'lock_time' => lock_time, 'ver' => version,
|
53
|
+
'vin_sz' => ins.length, 'vout_sz' => outs.length, 'size' => size}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Convert an array of 4 bit numbers into an unsigned int,
|
57
|
+
# works for numbers up to 32-bit only
|
58
|
+
def self.nibbles_to_ui(nibbles)
|
59
|
+
raise ArgumentError if nibbles.length > 4
|
60
|
+
|
61
|
+
nibbles.each_with_index.inject(0){ |sum, (b,i)|
|
62
|
+
sum += (b.to_i & 0xff) << (4 * i) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Convert an array of 8 bit numbers into an unsigned int,
|
66
|
+
# Remember that for each input byte, the most significant nibble comes last.
|
67
|
+
def self.bytes_to_ui(bytes)
|
68
|
+
nibbles = bytes.collect{|b| [b & 0x0f, b >> 4]}.flatten
|
69
|
+
nibbles.each_with_index.inject(0){|sum,(b,i)| sum += b * 16**i}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convert an array of bytes to a base64 string.
|
73
|
+
def self.bytes_to_base64_s(input_bytes)
|
74
|
+
input_bytes.each_slice(3).collect.with_index{ |bytes,i|
|
75
|
+
# This is the base offset of this 3-byte slice in the sequence:
|
76
|
+
i_triplet = i*3
|
77
|
+
|
78
|
+
# Here we compose a triplet by or'ing the shifted bytes:
|
79
|
+
triplet = bytes.collect.with_index{|b,j| b << 8*(2-j) }.reduce(:|)
|
80
|
+
|
81
|
+
# And here we convert to chars, unless with equals as nil-padding:
|
82
|
+
0.upto(3).collect do |j|
|
83
|
+
(i_triplet * 8 + j * 6 <= input_bytes.length * 8) ?
|
84
|
+
BASE64_CHARS[((triplet >> 6 * (3 - j)) & 0x3F)] : '='
|
85
|
+
end
|
86
|
+
}.join
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def disassemble_script(bytes)
|
92
|
+
# We don't actually need to reference a hash argument to achieve disassembly:
|
93
|
+
btc_script = Bitcoin::Script.new String.new
|
94
|
+
chunks = btc_script.parse bytes.pack('C*')
|
95
|
+
btc_script.to_string chunks
|
96
|
+
end
|
97
|
+
|
98
|
+
def hex_stringify(nibbles)
|
99
|
+
nibbles.collect{|c| '%02x' % c}.join
|
100
|
+
end
|
101
|
+
|
102
|
+
# https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_string
|
103
|
+
def shift_varchar(bytes)
|
104
|
+
bytes.slice! 0, shift_varint(bytes)
|
105
|
+
end
|
106
|
+
|
107
|
+
# https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
|
108
|
+
def shift_varint(bytes)
|
109
|
+
n = shift_u8 bytes
|
110
|
+
case n
|
111
|
+
when 0xfd then shift_u16 bytes
|
112
|
+
when 0xfe then shift_u32 bytes
|
113
|
+
when 0xff then shift_u64 bytes
|
114
|
+
else n
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# These are numeric byte-shift short-cuts that make for more readable code
|
119
|
+
# above:
|
120
|
+
[1,2,4].each do |n|
|
121
|
+
define_method('shift_u%s' % [n*8]) do |bytes|
|
122
|
+
self.class.nibbles_to_ui bytes.slice!(0,n)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# 64 bit requests are an exception, and kept as 8-byte arrays:
|
127
|
+
def shift_u64(bytes)
|
128
|
+
bytes.slice!(0,8)
|
129
|
+
end
|
130
|
+
end
|