Skip to content

Commit 8110063

Browse files
committed
Initial Commit
1 parent 526c5f7 commit 8110063

File tree

3 files changed

+232
-0
lines changed

3 files changed

+232
-0
lines changed

lib/blockcypher.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
require 'blockcypher/api'
2+
require 'bitcoin'
3+
require 'json'
4+
require 'uri'
5+
require 'net/http'

lib/blockcypher/api.rb

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
2+
3+
module BlockCypher
4+
5+
V1 = 'v1'
6+
7+
BTC = 'btc'
8+
LTC = 'ltc'
9+
10+
MAIN_NET = 'main'
11+
TEST_NET = 'test'
12+
TEST_NET_3 = 'test3'
13+
14+
class Api
15+
16+
class Error < RuntimeError ; end
17+
18+
def initialize(version: V1, currency: BTC, network: MAIN_NET)
19+
@version = version
20+
@currency = currency
21+
@network = network
22+
end
23+
24+
##################
25+
# Blockchain API
26+
##################
27+
28+
def blockchain_transaction(transaction_hash)
29+
api_http_get('/txs/' + transaction_hash)
30+
end
31+
32+
def blockchain_block(block_index)
33+
api_http_get('/blocks/' + block_index)
34+
end
35+
36+
##################
37+
# Transaction API
38+
##################
39+
40+
def send_money(from_address, to_address, satoshi_amount, private_key)
41+
42+
unless to_address.kind_of? Array
43+
to_address = [to_address]
44+
end
45+
46+
tx_new = transaction_new([from_address], to_address, satoshi_amount)
47+
48+
transaction_sign_and_send(tx_new, private_key)
49+
end
50+
51+
def transaction_new(input_addreses, output_addresses, satoshi_amount)
52+
payload = {
53+
'inputs' => [
54+
{
55+
addresses: input_addreses
56+
}
57+
],
58+
'outputs' => [
59+
{
60+
addresses: output_addresses,
61+
value: satoshi_amount
62+
}
63+
]
64+
}
65+
api_http_post('/txs/new', json_payload: payload.to_json)
66+
end
67+
68+
def transaction_sign_and_send(new_tx, private_key)
69+
key = Bitcoin::Key.new(private_key, nil, compressed = true)
70+
public_key = key.pub
71+
signatures = []
72+
public_keys = []
73+
74+
new_tx['tosign'].each do |to_sign_hex|
75+
public_keys << public_key
76+
to_sign_binary = [to_sign_hex].pack("H*")
77+
sig_binary = key.sign(to_sign_binary)
78+
sig_hex = sig_binary.unpack("H*").first
79+
signatures << sig_hex
80+
end
81+
new_tx['signatures'] = signatures
82+
new_tx['pubkeys'] = public_keys
83+
84+
res = api_http_post('/txs/send', json_payload: new_tx.to_json)
85+
86+
res
87+
end
88+
89+
##################
90+
# Address APIs
91+
##################
92+
93+
def address_generate
94+
api_http_post('/addrs')
95+
end
96+
97+
def address_details(address)
98+
api_http_get('/addrs/' + address)
99+
end
100+
101+
def address_final_balance(address)
102+
details = address_details(address)
103+
details['final_balance']
104+
105+
end
106+
107+
##################
108+
# Events API
109+
##################
110+
111+
def event_webhook_subscribe(url, filter, token = nil)
112+
payload = {
113+
url: url,
114+
filter: filter,
115+
token: token
116+
}.to_json
117+
api_http_post('/hooks', json_payload: payload)
118+
end
119+
120+
private
121+
122+
def api_http_call(http_method, api_path, json_payload: nil)
123+
uri = endpoint_uri(api_path)
124+
125+
# Build the connection
126+
http = Net::HTTP.new(uri.host, uri.port)
127+
http.use_ssl = true
128+
129+
# Build the Request
130+
if http_method == :post
131+
request = Net::HTTP::Post.new(uri.request_uri)
132+
elsif http_method == :get
133+
request = Net::HTTP::Get.new(uri.request_uri)
134+
else
135+
raise 'Invalid HTTP method'
136+
end
137+
138+
unless json_payload.nil?
139+
request.content_type = 'application/json'
140+
request.body = json_payload
141+
end
142+
143+
response = http.request(request)
144+
145+
# Detect errors
146+
if response.code == '400'
147+
raise Error.new(uri.to_s + ' Response:' + response.body)
148+
end
149+
150+
# Process the response
151+
begin
152+
json_response = JSON.parse(response.body)
153+
return json_response
154+
rescue => e
155+
raise "Unable to parse JSON response #{e.inspect}, #{response.body}"
156+
end
157+
end
158+
159+
def api_http_get(api_path)
160+
api_http_call :get, api_path
161+
end
162+
163+
def api_http_post(api_path, json_payload: nil)
164+
api_http_call :post, api_path, json_payload: json_payload
165+
end
166+
167+
def endpoint_uri(api_path)
168+
if api_path[0] != '/'
169+
api_path += '/' + api_path
170+
end
171+
URI('https://api.blockcypher.com/' + @version + '/' + @currency + '/' + @network + api_path)
172+
end
173+
174+
end
175+
end

spec/blockcypher/api_spec.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require 'blockcypher'
2+
3+
module BlockCypher
4+
5+
describe Api do
6+
let(:api) { BlockCypher::Api.new(currency: BlockCypher::BTC, network: BlockCypher::TEST_NET_3, version: BlockCypher::V1) }
7+
8+
let(:address_1) { 'miB9s4fcYCEBxPQm8vw6UrsYc2iSiEW3Yn' }
9+
let(:address_1_private_key) { 'f2a73451a726e81aec76a2bfd5a4393a89822b30cc4cddb2b4317efb2266ad47' }
10+
11+
let(:address_2) { 'mnTBb2Fd13pKwNjFQz9LVoy2bqmDugLM5m' }
12+
let(:address_2_private_key) { '76a32c1e5b6f9e174719e7c1b555d6a55674fdc2fd99cfeee96a5de632775645' }
13+
14+
describe '#transaction_new' do
15+
16+
it 'should call the txs/new api' do
17+
input_addresses = [address_1]
18+
output_addresses = [address_2]
19+
satoshi_value = 20000
20+
res = api.transaction_new(input_addresses, output_addresses, satoshi_value)
21+
expect(res["tx"]["hash"]).to be_present
22+
end
23+
end
24+
25+
describe '#transaction_sign_and_send' do
26+
27+
it 'should call txs/send api' do
28+
29+
input_addresses = [address_2]
30+
output_addresses = [address_1]
31+
satoshi_value = 10000
32+
33+
new_tx = api.transaction_new(input_addresses, output_addresses, satoshi_value)
34+
res = api.transaction_sign_and_send(new_tx, address_2_private_key)
35+
expect(res["tx"]["hash"]).to be_present
36+
end
37+
38+
end
39+
40+
describe '#address_final_balance' do
41+
42+
it 'should get the balance of an address' do
43+
balance = api.address_final_balance(address_1)
44+
expect(balance).to be_kind_of Integer
45+
expect(balance).to be > 0
46+
end
47+
48+
end
49+
50+
end
51+
52+
end

0 commit comments

Comments
 (0)