bitcoin_op_return 0.0.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.
- 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: []
|