counterparty_ruby 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+

|
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
|
+

|
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
|