bitcoin_op_return 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/bitcoin_op_return.rb +252 -0
  3. 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: []