bitcoin_op_return 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/bitcoin_op_return.rb +252 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c0c7cdf9a7cfb03cbb4eafafd35585e3f92d1738
|
4
|
+
data.tar.gz: ed12beeb25a03a976bbdef0fe718f1d7d42d475c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 522d7927e0017e7ed33f44c8b8c68ad333bdb5be22c9b274f4adcbb2b86ae05529eb28d9dac551abe34e8421ac96a62f087c9a1c06fc806b1f359508bb56d910
|
7
|
+
data.tar.gz: 4e2d504780ea85d5f3fcef85fc791eeeeef6cbb92014b46dabe60fe038671f8e65912d64d97206d960c246a4ca740562365853b0400d93d7238fa1820cebc6c4
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require "shellwords"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
class String
|
5
|
+
def shift! n
|
6
|
+
n > 0 ? slice!(0..(n-1)) : ""
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module BitcoinOpReturn
|
11
|
+
BITCOIND_CMD = "/usr/local/bin/bitcoind"
|
12
|
+
TRANSACTION_FEE = 0.0001
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def create options
|
16
|
+
send_address = options[:address].to_s
|
17
|
+
send_amount = options[:amount].to_f
|
18
|
+
metadata = options[:metadata].to_s
|
19
|
+
testnet = !options[:testnet].nil?
|
20
|
+
|
21
|
+
# convert to hex where possible
|
22
|
+
metadata = [ metadata ].pack("H*") if metadata =~ /\A([0-9A-Fa-f]{2})*\z/
|
23
|
+
|
24
|
+
return { "error" => "invalid address" } unless bitcoind("validateaddress", testnet, send_address)["isvalid"]
|
25
|
+
return { "error" => "metadata too long, limit is 75, recommended is 40 bytes" } if metadata.length > 75
|
26
|
+
|
27
|
+
# get the unspent inputs
|
28
|
+
unspent_inputs = bitcoind("listunspent", testnet, 0)
|
29
|
+
return { "error" => "unable to retrieve unspent inputs" } unless unspent_inputs.kind_of? Array
|
30
|
+
|
31
|
+
unspent_inputs.each do |input|
|
32
|
+
input["priority"] = input["amount"] * input["confirmations"]
|
33
|
+
end
|
34
|
+
|
35
|
+
unspent_inputs.sort! do |a, b|
|
36
|
+
a = a["priority"]
|
37
|
+
b = b["priority"]
|
38
|
+
# a follows b => -1
|
39
|
+
# a == b => 0
|
40
|
+
# b follows a => 1
|
41
|
+
a == b ? 0 : (a < b ? 1 : -1)
|
42
|
+
end
|
43
|
+
|
44
|
+
inputs_spend = []
|
45
|
+
output_amount = send_amount + TRANSACTION_FEE
|
46
|
+
inputs_amount = 0
|
47
|
+
|
48
|
+
unspent_inputs.each do |input|
|
49
|
+
inputs_spend << input
|
50
|
+
inputs_amount += input["amount"]
|
51
|
+
break if inputs_amount >= output_amount
|
52
|
+
end
|
53
|
+
|
54
|
+
return { "error" => "insufficient funds to carry out transaction" } if inputs_amount < output_amount
|
55
|
+
|
56
|
+
|
57
|
+
outputs_hash = {}
|
58
|
+
outputs_hash[send_address] = send_amount
|
59
|
+
|
60
|
+
|
61
|
+
unless inputs_amount == output_amount # no change
|
62
|
+
change = inputs_amount - output_amount
|
63
|
+
outputs_hash[bitcoind("getrawchangeaddress", testnet)] = change
|
64
|
+
end
|
65
|
+
|
66
|
+
# pack then unpack
|
67
|
+
raw_txn = bitcoind("createrawtransaction", testnet, inputs_spend, outputs_hash)
|
68
|
+
|
69
|
+
unpacked_txn = unpack_raw_txn(raw_txn)
|
70
|
+
|
71
|
+
# append opreturn (6a represents op_return)
|
72
|
+
|
73
|
+
op_return_script = "6a" + "#{metadata.length.chr}#{metadata}".unpack("H*")[0]
|
74
|
+
|
75
|
+
unpacked_txn["vout"].push({
|
76
|
+
"value" => 0,
|
77
|
+
"scriptPubKey" => op_return_script
|
78
|
+
})
|
79
|
+
|
80
|
+
# $raw_txn=coinspark_pack_raw_txn($txn_unpacked);
|
81
|
+
raw_txn = pack_raw_txn(unpacked_txn)
|
82
|
+
|
83
|
+
sign_txn_response = bitcoind("signrawtransaction", testnet, raw_txn)
|
84
|
+
# txid = bitcoind("sendrawtransaction", testnet, signed_txn)
|
85
|
+
|
86
|
+
return { :error => "error signing transaction" } unless sign_txn_response["complete"]
|
87
|
+
|
88
|
+
txid = bitcoind("sendrawtransaction", testnet, sign_txn_response["hex"])
|
89
|
+
|
90
|
+
if txid.length != 64
|
91
|
+
{ :error => "could not send transaction" }
|
92
|
+
else
|
93
|
+
{ :txid => txid }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def bitcoind cmd, testnet, *args
|
99
|
+
command = "#{BITCOIND_CMD} #{testnet ? "-testnet" : ""}"
|
100
|
+
|
101
|
+
command += " #{Shellwords.escape(cmd)}"
|
102
|
+
|
103
|
+
args.each do |x|
|
104
|
+
begin
|
105
|
+
command += " #{Shellwords.escape(JSON.generate(x))}"
|
106
|
+
rescue
|
107
|
+
command += " #{Shellwords.escape(x)}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
raw_result = `#{command}`.strip.chomp
|
112
|
+
|
113
|
+
begin
|
114
|
+
JSON.parse(raw_result)
|
115
|
+
rescue
|
116
|
+
raw_result
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def parse_var_int binary
|
121
|
+
val = binary.shift!(1).unpack("C")[0]
|
122
|
+
|
123
|
+
if val == 0xFF # 64
|
124
|
+
unpack_uint64(binary.shift!(8))
|
125
|
+
elsif val == 0xFE # 32 bits
|
126
|
+
binary.shift!(4).unpack("V")[0]
|
127
|
+
elsif val == 0xFD # 16 bits
|
128
|
+
binary.shift!(2).unpack("v")[0]
|
129
|
+
else
|
130
|
+
val
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def pack_var_int n
|
135
|
+
if n < 0xFD # uint8_t
|
136
|
+
[ n ].pack("C")
|
137
|
+
elsif n <= 0xFFFF #0xfd followed by the uint16_t
|
138
|
+
"\xFD".b + [ n ].pack("v")
|
139
|
+
elsif n <= 0xFFFFFFFF #0xfe followed by the uint32_t
|
140
|
+
"\xFE".b + [ n ].pack("V")
|
141
|
+
else #0xff followed by the length as uint64_t
|
142
|
+
"\xFF".b + pack_uint64(n)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def unpack_uint64 str
|
147
|
+
# since it is lsb first ...
|
148
|
+
str[0..3].unpack("V*")[0] + str[4..7].unpack("V*")[0] * (2**32)
|
149
|
+
end
|
150
|
+
|
151
|
+
def pack_uint64 n
|
152
|
+
first = [ n % (2**32) ].pack("V") # small byte
|
153
|
+
second = [ (n / (2**32)).round ].pack("V") # big byte
|
154
|
+
first + second
|
155
|
+
end
|
156
|
+
|
157
|
+
def pack_raw_txn txn
|
158
|
+
binary = "".b
|
159
|
+
|
160
|
+
# pack version into 32 bit integer (little endian)
|
161
|
+
# $binary.=pack('V', $txn['version']);
|
162
|
+
binary += [ txn["version"] ].pack("V")
|
163
|
+
|
164
|
+
# $binary.=coinspark_pack_varint(count($txn['vin']));
|
165
|
+
# pack varint number of input
|
166
|
+
binary += pack_var_int(txn["vin"].length)
|
167
|
+
|
168
|
+
# pack the inputs
|
169
|
+
txn["vin"].each do |input|
|
170
|
+
binary += [ input["txid"] ].pack("H*").reverse
|
171
|
+
binary += [ input["vout"] ].pack("V")
|
172
|
+
# divide 2 because to positions in a hex string represents a byte
|
173
|
+
binary += pack_var_int(input["scriptSig"].length / 2)
|
174
|
+
binary += [ input["scriptSig"] ].pack("H*")
|
175
|
+
|
176
|
+
binary += [ input["sequence"] ].pack("V")
|
177
|
+
# input["sequence"].to_s(16).split("").each do |x|
|
178
|
+
# binary += x
|
179
|
+
# end
|
180
|
+
end
|
181
|
+
|
182
|
+
# pack varint number of outputs
|
183
|
+
binary += pack_var_int(txn["vout"].length)
|
184
|
+
|
185
|
+
# start packing the output
|
186
|
+
txn["vout"].each do |output|
|
187
|
+
binary += pack_uint64((output["value"] * 100000000).round)
|
188
|
+
binary += pack_var_int(output["scriptPubKey"].length / 2)
|
189
|
+
binary += [ output["scriptPubKey"] ].pack("H*")
|
190
|
+
end
|
191
|
+
|
192
|
+
# then append the lock time
|
193
|
+
binary += [ txn["locktime"] ].pack("V")
|
194
|
+
|
195
|
+
# convert the real reprsentation into a hex string
|
196
|
+
binary.unpack("H*")[0]
|
197
|
+
end
|
198
|
+
|
199
|
+
def unpack_raw_txn hex
|
200
|
+
# // see: https://en.bitcoin.it/wiki/Transactions
|
201
|
+
|
202
|
+
# convert the hex representation into a real string]
|
203
|
+
binary = [ hex ].pack("H*")
|
204
|
+
|
205
|
+
txn = {}
|
206
|
+
|
207
|
+
# get the version and initialize array
|
208
|
+
|
209
|
+
txn["version"] = binary.shift!(4).unpack("V")[0]
|
210
|
+
txn["vin"] = []
|
211
|
+
txn["vout"] = []
|
212
|
+
|
213
|
+
# parse the number of inputs
|
214
|
+
n = parse_var_int(binary)
|
215
|
+
|
216
|
+
# parse the inputs
|
217
|
+
n.times do
|
218
|
+
input = {}
|
219
|
+
input["txid"] = binary.shift!(32).reverse.unpack("H*")[0]
|
220
|
+
input["vout"] = binary.shift!(4).unpack("V")[0]
|
221
|
+
script_length = parse_var_int(binary)
|
222
|
+
|
223
|
+
input["scriptSig"] = binary.shift!(script_length).unpack("H*")[0]
|
224
|
+
input["sequence"] = binary.shift!(4).unpack("V")[0]
|
225
|
+
txn["vin"] << input
|
226
|
+
end
|
227
|
+
|
228
|
+
# parse the number of outputs
|
229
|
+
n = parse_var_int(binary)
|
230
|
+
|
231
|
+
# parse the outputs
|
232
|
+
n.times do
|
233
|
+
output = {}
|
234
|
+
output["value"] = binary.shift!(8).unpack("V")[0] / 100000000.0
|
235
|
+
# remember it is stored in satoshi inernally
|
236
|
+
|
237
|
+
script_length = parse_var_int(binary)
|
238
|
+
output["scriptPubKey"] = binary.shift!(script_length).unpack("H*")[0]
|
239
|
+
txn["vout"] << output
|
240
|
+
end
|
241
|
+
|
242
|
+
# locktime
|
243
|
+
txn["locktime"] = binary.shift!(4).unpack("V")[0]
|
244
|
+
|
245
|
+
# error handling
|
246
|
+
exit ("unexpected data in transaction") unless binary.length == 0
|
247
|
+
|
248
|
+
# finally, return
|
249
|
+
txn
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bitcoin_op_return
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- FY Quah
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-24 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A simple gem for you to send op_return into the bitcoin blockchain with
|
14
|
+
ruby
|
15
|
+
email: fuyong@fyquah.me
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/bitcoin_op_return.rb
|
21
|
+
homepage: https://github.com/fyquah95/bitcoin_op_return
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.2.2
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: Send OP Return transactions
|
45
|
+
test_files: []
|