straight 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -1
- data/README.md +20 -10
- data/VERSION +1 -1
- data/lib/straight.rb +10 -1
- data/lib/straight/blockchain_adapter.rb +2 -13
- data/lib/straight/blockchain_adapters/biteasy_adapter.rb +74 -0
- data/lib/straight/blockchain_adapters/blockchain_info_adapter.rb +37 -17
- data/lib/straight/blockchain_adapters/mycelium_adapter.rb +144 -0
- data/lib/straight/exchange_rate_adapter.rb +20 -1
- data/lib/straight/exchange_rate_adapters/average_rate_adapter.rb +54 -0
- data/lib/straight/exchange_rate_adapters/bitpay_adapter.rb +5 -2
- data/lib/straight/exchange_rate_adapters/bitstamp_adapter.rb +2 -1
- data/lib/straight/exchange_rate_adapters/btce_adapter.rb +18 -0
- data/lib/straight/exchange_rate_adapters/coinbase_adapter.rb +2 -4
- data/lib/straight/exchange_rate_adapters/kraken_adapter.rb +18 -0
- data/lib/straight/exchange_rate_adapters/localbitcoins_adapter.rb +17 -0
- data/lib/straight/exchange_rate_adapters/okcoin_adapter.rb +18 -0
- data/lib/straight/gateway.rb +20 -9
- data/lib/straight/order.rb +46 -13
- data/spec/lib/blockchain_adapters/{helloblock_io_spec.rb → biteasy_adapter_spec.rb} +23 -18
- data/spec/lib/blockchain_adapters/{blockchain_info_spec.rb → blockchain_info_adapter_spec.rb} +8 -3
- data/spec/lib/blockchain_adapters/mycelium_adapter_spec.rb +54 -0
- data/spec/lib/exchange_rate_adapter_spec.rb +6 -1
- data/spec/lib/exchange_rate_adapters/average_rate_adapter_spec.rb +43 -0
- data/spec/lib/exchange_rate_adapters/bitpay_adapter_spec.rb +14 -1
- data/spec/lib/exchange_rate_adapters/bitstamp_adapter_spec.rb +14 -1
- data/spec/lib/exchange_rate_adapters/btce_adapter_spec.rb +27 -0
- data/spec/lib/exchange_rate_adapters/coinbase_adapter_spec.rb +14 -1
- data/spec/lib/exchange_rate_adapters/kraken_adapter_spec.rb +27 -0
- data/spec/lib/exchange_rate_adapters/localbitcoins_adapter_spec.rb +27 -0
- data/spec/lib/exchange_rate_adapters/okcoin_adapter_spec.rb +27 -0
- data/spec/lib/gateway_spec.rb +23 -5
- data/spec/lib/order_spec.rb +18 -2
- data/straight.gemspec +95 -0
- metadata +33 -6
- data/lib/straight/blockchain_adapters/helloblock_io_adapter.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ce549f0d3b951b8ce6e5d6e35ba868497b977e7
|
4
|
+
data.tar.gz: 5c44763aa3b9d35441e0d1bdd4824dac85238320
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4632fafae05cd13ab8443a6817806bca0e67fa0f7eddc03027944287e6c57c8437bd914f18589db3b9e39a91ac2a9e796d68015485132ff2e68774fe70f4f86
|
7
|
+
data.tar.gz: 0724e768858d85d0b3f9290d6570f75944167554b9b5ed5177cbb97c1ae76f5569b68730e5a77cba88db1b499413e486381b0dfe35681f5c8aab8051cbc0982d
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -20,6 +20,9 @@ GEM
|
|
20
20
|
oauth2
|
21
21
|
hashie (3.3.1)
|
22
22
|
highline (1.6.21)
|
23
|
+
httparty (0.13.3)
|
24
|
+
json (~> 1.8)
|
25
|
+
multi_xml (>= 0.5.2)
|
23
26
|
jeweler (2.0.1)
|
24
27
|
builder
|
25
28
|
bundler (>= 1.0)
|
@@ -61,7 +64,7 @@ GEM
|
|
61
64
|
rspec-mocks (3.1.0)
|
62
65
|
rspec-support (~> 3.1.0)
|
63
66
|
rspec-support (3.1.0)
|
64
|
-
satoshi-unit (0.1.
|
67
|
+
satoshi-unit (0.1.7)
|
65
68
|
thread_safe (0.3.4)
|
66
69
|
|
67
70
|
PLATFORMS
|
@@ -70,6 +73,7 @@ PLATFORMS
|
|
70
73
|
DEPENDENCIES
|
71
74
|
bundler (~> 1.0)
|
72
75
|
github_api (= 0.11.3)
|
76
|
+
httparty
|
73
77
|
jeweler (~> 2.0.1)
|
74
78
|
money-tree
|
75
79
|
rspec
|
data/README.md
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
Straight
|
2
2
|
========
|
3
3
|
> Receive bitcoin payments directly into your wallet
|
4
|
+
|
4
5
|
> Website: http://straight.romansnitko.com
|
5
6
|
|
6
|
-
Straight is a built-in stateless gateway
|
7
|
-
your online store. Drop in this library,
|
8
|
-
|
9
|
-
with your database.
|
7
|
+
Straight is a built-in stateless gateway library written in Ruby.
|
8
|
+
It allows you to receive bitcoin payments for your online store. Drop in this library,
|
9
|
+
set your public key and start receiving payments. Your BIP32-compatible wallet will
|
10
|
+
see payments automatically without any need for integration with your database.
|
10
11
|
|
11
12
|
Straight cares about security and privacy. No private keys are stored on the server,
|
12
13
|
each order uses unique payment address. Straight notifies your application when payment is
|
@@ -16,7 +17,7 @@ IMPORTANT: this is a gem, not a server. It has no state and is intended to use w
|
|
16
17
|
an application, such as Ruby On Rails. Most likely, you want
|
17
18
|
[straight-server](https://github.com/snitko/straight-server), it is a server,
|
18
19
|
which holds the state of all orders for you and has a RESTful API you can use
|
19
|
-
with any application written in
|
20
|
+
with any application written in any language or platform.
|
20
21
|
|
21
22
|
Bitcoin donations are appreciated: 1D3PknG4Lw1gFuJ9SYenA7pboF9gtXtdcD
|
22
23
|
|
@@ -61,6 +62,7 @@ Usage
|
|
61
62
|
gateway.name = 'my gateway'
|
62
63
|
|
63
64
|
# Set the callback for orders' status changes
|
65
|
+
# (see lib/straight/order.rb for status attribute values and their meanings)
|
64
66
|
#
|
65
67
|
gateway.order_callbacks = [
|
66
68
|
lambda { |order| puts "Order status changed to #{order.status}" }
|
@@ -101,8 +103,8 @@ When this module is included, it doesn't actually *include* all the methods, som
|
|
101
103
|
It is important specifically for getters and setters and as a general rule only getters and setters are prepended.
|
102
104
|
|
103
105
|
If you don't want to bother yourself with modules, please use `Straight::Order` class and simply create new instances of it.
|
104
|
-
However, if you are contributing to the library, all new functionality should go to either Straight::OrderModule::Includable or
|
105
|
-
Straight::OrderModule::Prependable (most likely the former).
|
106
|
+
However, if you are contributing to the library, all new functionality should go to either `Straight::OrderModule::Includable` or
|
107
|
+
`Straight::OrderModule::Prependable` (most likely the former).
|
106
108
|
|
107
109
|
|
108
110
|
Important Considerations
|
@@ -115,14 +117,22 @@ all orders should be indexed sequentially, not randomly.
|
|
115
117
|
Why can't we just derive new addresses from order UUID, or assign them to orders? The reason is that your
|
116
118
|
wallet will have to integrate with your very own database and it may be enormously cumbersome to implement
|
117
119
|
in a generic way. Alternative would be to create a wallet within Straight and make it generate and keep the
|
118
|
-
private keys, but this would be highly insecure. Keys stored on popular hosting solutions would quickly
|
119
|
-
all sorts of attacks to get money from them.
|
120
|
+
private keys, but this would be highly insecure. Keys stored on popular hosting solutions would quickly
|
121
|
+
invite all sorts of attacks to get money from them.
|
122
|
+
|
123
|
+
|
124
|
+
A note about Mycelium blockchain adapter
|
125
|
+
----------------------------------------
|
126
|
+
If you wish to use Mycelium blockchain adapter you MUST install bitcoind on your server (you may run it in offline mode, no need to download the whole blockchain!) and have a `bitcoin-cli` in your PATH. This
|
127
|
+
requirement is due to the need to parse raw bitcoin transaction received from Mycelium WAPI.
|
128
|
+
By default, Mycelium is included as a second (fallback) adapter and will only be used in case
|
129
|
+
BlockchainInfo one fails. It will not raise an exception until it actually tries to parse the trasaction
|
130
|
+
and finds there is no `bitcoin-cli` in PATH.
|
120
131
|
|
121
132
|
Requirements
|
122
133
|
------------
|
123
134
|
Ruby 2.1 or later.
|
124
135
|
|
125
|
-
|
126
136
|
Credits
|
127
137
|
-------
|
128
138
|
Authors:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/straight.rb
CHANGED
@@ -4,15 +4,24 @@ require 'json'
|
|
4
4
|
require 'uri'
|
5
5
|
require 'open-uri'
|
6
6
|
require 'yaml'
|
7
|
+
require 'singleton'
|
8
|
+
require 'httparty'
|
7
9
|
|
8
10
|
require_relative 'straight/blockchain_adapter'
|
9
11
|
require_relative 'straight/blockchain_adapters/blockchain_info_adapter'
|
10
|
-
require_relative 'straight/blockchain_adapters/
|
12
|
+
require_relative 'straight/blockchain_adapters/biteasy_adapter'
|
13
|
+
require_relative 'straight/blockchain_adapters/mycelium_adapter'
|
11
14
|
|
12
15
|
require_relative 'straight/exchange_rate_adapter'
|
13
16
|
require_relative 'straight/exchange_rate_adapters/bitpay_adapter'
|
14
17
|
require_relative 'straight/exchange_rate_adapters/coinbase_adapter'
|
15
18
|
require_relative 'straight/exchange_rate_adapters/bitstamp_adapter'
|
19
|
+
require_relative 'straight/exchange_rate_adapters/localbitcoins_adapter'
|
20
|
+
require_relative 'straight/exchange_rate_adapters/okcoin_adapter'
|
21
|
+
require_relative 'straight/exchange_rate_adapters/btce_adapter'
|
22
|
+
require_relative 'straight/exchange_rate_adapters/kraken_adapter'
|
23
|
+
require_relative 'straight/exchange_rate_adapters/average_rate_adapter'
|
24
|
+
|
16
25
|
|
17
26
|
require_relative 'straight/order'
|
18
27
|
require_relative 'straight/gateway'
|
@@ -5,24 +5,13 @@ module Straight
|
|
5
5
|
# all blockchain adapters as well as supplying some useful methods.
|
6
6
|
class Adapter
|
7
7
|
|
8
|
+
include Singleton
|
9
|
+
|
8
10
|
# Raised when blockchain data cannot be retrived for any reason.
|
9
11
|
# We're not really intereste in the precise reason, although it is
|
10
12
|
# stored in the message.
|
11
13
|
class RequestError < Exception; end
|
12
14
|
|
13
|
-
# This method is a wrapper for creating an HTTP request
|
14
|
-
# to various services that ancestors of this class may use
|
15
|
-
# to retrieve blockchain data. Why do we need a wrapper?
|
16
|
-
# Because it respects timeouts.
|
17
|
-
def http_request(url)
|
18
|
-
uri = URI.parse(url)
|
19
|
-
begin
|
20
|
-
http = uri.read(read_timeout: 4)
|
21
|
-
rescue OpenURI::HTTPError => e
|
22
|
-
raise RequestError, YAML::dump(e)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
15
|
def fetch_transaction(tid)
|
27
16
|
raise "Please implement #fetch_transaction in #{self.to_s}"
|
28
17
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Straight
|
2
|
+
module Blockchain
|
3
|
+
|
4
|
+
class BiteasyAdapter < Adapter
|
5
|
+
|
6
|
+
def self.mainnet_adapter
|
7
|
+
instance = self.instance
|
8
|
+
instance._initialize("https://api.biteasy.com/blockchain/v1")
|
9
|
+
instance
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.testnet_adapter
|
13
|
+
raise "Not Supported Yet"
|
14
|
+
end
|
15
|
+
|
16
|
+
def _initialize(base_url)
|
17
|
+
@base_url = base_url
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the current balance of the address
|
21
|
+
def fetch_balance_for(address)
|
22
|
+
JSON.parse(api_request("/addresses/#{address}"))['data']['balance']
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns transaction info for the tid
|
26
|
+
def fetch_transaction(tid, address: nil)
|
27
|
+
straighten_transaction JSON.parse(api_request("/transactions/#{tid}"), address: address)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns all transactions for the address
|
31
|
+
def fetch_transactions_for(address)
|
32
|
+
transactions = JSON.parse(api_request("/transactions?address=#{address}"))['data']['transactions']
|
33
|
+
transactions.map { |t| straighten_transaction(t, address: address) }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def api_request(url)
|
39
|
+
begin
|
40
|
+
response = HTTParty.get("#{@base_url}/#{url}", timeout: 4, verify: false)
|
41
|
+
unless response.code == 200
|
42
|
+
raise RequestError, "Cannot access remote API, response code was #{response.code}"
|
43
|
+
end
|
44
|
+
response.body
|
45
|
+
rescue HTTParty::Error => e
|
46
|
+
raise RequestError, YAML::dump(e)
|
47
|
+
rescue JSON::ParserError => e
|
48
|
+
raise RequestError, YAML::dump(e)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Converts transaction info received from the source into the
|
53
|
+
# unified format expected by users of BlockchainAdapter instances.
|
54
|
+
def straighten_transaction(transaction, address: nil)
|
55
|
+
outs = []
|
56
|
+
total_amount = 0
|
57
|
+
transaction['data']['outputs'].each do |out|
|
58
|
+
total_amount += out['value'] if address.nil? || address == out['to_address']
|
59
|
+
outs << { amount: out['value'], receiving_address: out['to_address'] }
|
60
|
+
end
|
61
|
+
|
62
|
+
{
|
63
|
+
tid: transaction['data']['hash'],
|
64
|
+
total_amount: total_amount,
|
65
|
+
confirmations: transaction['data']['confirmations'],
|
66
|
+
outs: outs
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -4,36 +4,67 @@ module Straight
|
|
4
4
|
class BlockchainInfoAdapter < Adapter
|
5
5
|
|
6
6
|
def self.mainnet_adapter
|
7
|
-
self.
|
7
|
+
instance = self.instance
|
8
|
+
instance._initialize("https://blockchain.info")
|
9
|
+
instance
|
8
10
|
end
|
9
11
|
|
10
12
|
def self.testnet_adapter
|
11
13
|
raise "Not Supported Yet"
|
12
14
|
end
|
13
15
|
|
14
|
-
def
|
16
|
+
def _initialize(base_url)
|
15
17
|
@latest_block = { cache_timestamp: nil, block: nil }
|
16
18
|
@base_url = base_url
|
17
19
|
end
|
18
20
|
|
19
21
|
# Returns transaction info for the tid
|
20
22
|
def fetch_transaction(tid, address: nil)
|
21
|
-
straighten_transaction JSON.parse(
|
23
|
+
straighten_transaction JSON.parse(api_request("/rawtx/#{tid}"), address: address)
|
22
24
|
end
|
23
25
|
|
24
26
|
# Returns all transactions for the address
|
25
27
|
def fetch_transactions_for(address)
|
26
|
-
transactions = JSON.parse(
|
28
|
+
transactions = JSON.parse(api_request("/rawaddr/#{address}"))['txs']
|
27
29
|
transactions.map { |t| straighten_transaction(t, address: address) }
|
28
30
|
end
|
29
31
|
|
30
32
|
# Returns the current balance of the address
|
31
33
|
def fetch_balance_for(address)
|
32
|
-
JSON.parse(
|
34
|
+
JSON.parse(api_request("/rawaddr/#{address}"))['final_balance']
|
35
|
+
end
|
36
|
+
|
37
|
+
def latest_block(force_reload: false)
|
38
|
+
# If we checked Blockchain.info latest block data
|
39
|
+
# more than a minute ago, check again. Otherwise, use cached version.
|
40
|
+
if @latest_block[:cache_timestamp].nil? ||
|
41
|
+
@latest_block[:cache_timestamp] < (Time.now - 60) ||
|
42
|
+
force_reload
|
43
|
+
@latest_block = {
|
44
|
+
cache_timestamp: Time.now,
|
45
|
+
block: JSON.parse(api_request("/latestblock"))
|
46
|
+
}
|
47
|
+
else
|
48
|
+
@latest_block
|
49
|
+
end
|
33
50
|
end
|
34
51
|
|
35
52
|
private
|
36
53
|
|
54
|
+
def api_request(url)
|
55
|
+
begin
|
56
|
+
response = HTTParty.get("#{@base_url}/#{url}", timeout: 4, verify: false)
|
57
|
+
unless response.code == 200
|
58
|
+
raise RequestError, "Cannot access remote API, response code was #{response.code}"
|
59
|
+
end
|
60
|
+
response.body
|
61
|
+
rescue HTTParty::Error => e
|
62
|
+
raise RequestError, YAML::dump(e)
|
63
|
+
rescue JSON::ParserError => e
|
64
|
+
raise RequestError, YAML::dump(e)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
37
68
|
# Converts transaction info received from the source into the
|
38
69
|
# unified format expected by users of BlockchainAdapter instances.
|
39
70
|
def straighten_transaction(transaction, address: nil)
|
@@ -59,19 +90,8 @@ module Straight
|
|
59
90
|
# a certain address without making any new requests to the Blockchain API.
|
60
91
|
def calculate_confirmations(transaction, force_latest_block_reload: false)
|
61
92
|
|
62
|
-
# If we checked Blockchain.info latest block data
|
63
|
-
# more than a minute ago, check again. Otherwise, use cached version.
|
64
|
-
if @latest_block[:cache_timestamp].nil? ||
|
65
|
-
@latest_block[:cache_timestamp] < (Time.now - 60) ||
|
66
|
-
force_latest_block_reload
|
67
|
-
@latest_block = {
|
68
|
-
cache_timestamp: Time.now,
|
69
|
-
block: JSON.parse(http_request("#{@base_url}/latestblock"))
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
93
|
if transaction["block_height"]
|
74
|
-
|
94
|
+
latest_block(force_reload: force_latest_block_reload)[:block]["height"] - transaction["block_height"] + 1
|
75
95
|
else
|
76
96
|
0
|
77
97
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Straight
|
2
|
+
module Blockchain
|
3
|
+
|
4
|
+
class MyceliumAdapter < Adapter
|
5
|
+
|
6
|
+
class NoBitcoindInstalled < Exception
|
7
|
+
def message
|
8
|
+
"You need to install bitcoind on your server and have a `bitcoin-cli` executable in PATH.\n" +
|
9
|
+
"Note that you don't have to download the blockchain and you can run bitcoind in offline mode."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'base64'
|
14
|
+
|
15
|
+
def self.mainnet_adapter
|
16
|
+
instance = self.instance
|
17
|
+
instance._initialize("https://mws2.mycelium.com/wapi/wapi")
|
18
|
+
instance
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.testnet_adapter
|
22
|
+
instance = self.instance
|
23
|
+
instance._initialize("https://node3.mycelium.com/wapitestnet/wapi")
|
24
|
+
instance
|
25
|
+
end
|
26
|
+
|
27
|
+
def _initialize(base_url)
|
28
|
+
@latest_block = { cache_timestamp: nil, block: nil }
|
29
|
+
@base_url = base_url
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns transaction info for the tid
|
33
|
+
def fetch_transaction(tid, address: nil)
|
34
|
+
transaction = api_request('getTransactions', { txIds: [tid] })['transactions'].first
|
35
|
+
straighten_transaction transaction, address: address
|
36
|
+
end
|
37
|
+
|
38
|
+
# Supposed to returns all transactions for the address, but
|
39
|
+
# currently actually returns the first one, since we only need one.
|
40
|
+
def fetch_transactions_for(address)
|
41
|
+
tid = api_request('queryTransactionInventory', { addresses: [address], limit: 1 })["txIds"].first
|
42
|
+
tid ? [fetch_transaction(tid, address: address)] : []
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the current balance of the address
|
46
|
+
def fetch_balance_for(address)
|
47
|
+
unspent = 0
|
48
|
+
api_request('queryUnspentOutputs', { addresses: [address]})['unspent'].each do |out|
|
49
|
+
unspent += out['value']
|
50
|
+
end
|
51
|
+
unspent
|
52
|
+
end
|
53
|
+
|
54
|
+
# Here we are using Blockchain.info API at this point because I'm not sure how
|
55
|
+
# to get latest block from Mycelium WAPI
|
56
|
+
def latest_block(force_reload: false)
|
57
|
+
# If we checked Blockchain.info latest block data
|
58
|
+
# more than a minute ago, check again. Otherwise, use cached version.
|
59
|
+
if @latest_block[:cache_timestamp].nil? ||
|
60
|
+
@latest_block[:cache_timestamp] < (Time.now - 60) ||
|
61
|
+
force_reload
|
62
|
+
@latest_block = {
|
63
|
+
cache_timestamp: Time.now,
|
64
|
+
block: JSON.parse(HTTParty.get("https://blockchain.info/latestblock", timeout: 4, verify: false).body)
|
65
|
+
}
|
66
|
+
else
|
67
|
+
@latest_block
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def api_request(method, params={})
|
74
|
+
begin
|
75
|
+
body = JSON.parse(HTTParty.post(
|
76
|
+
"#{@base_url}/#{method}",
|
77
|
+
body: params.merge({version: 1}).to_json,
|
78
|
+
headers: { 'Content-Type' => 'application/json' },
|
79
|
+
timeout: 15,
|
80
|
+
verify: false
|
81
|
+
).body)["r"]
|
82
|
+
rescue HTTParty::Error => e
|
83
|
+
raise RequestError, YAML::dump(e)
|
84
|
+
rescue JSON::ParserError => e
|
85
|
+
raise RequestError, YAML::dump(e)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Converts transaction info received from the source into the
|
90
|
+
# unified format expected by users of BlockchainAdapter instances.
|
91
|
+
def straighten_transaction(transaction, address: nil)
|
92
|
+
|
93
|
+
# Get the block number this transaction was included into
|
94
|
+
block_height = transaction['height']
|
95
|
+
tid = transaction['txid']
|
96
|
+
|
97
|
+
# Converting from Base64 to hex
|
98
|
+
transaction = transaction['binary'].unpack("m0").first.unpack("H*").first
|
99
|
+
|
100
|
+
# Decoding with bitcoin-cli
|
101
|
+
begin
|
102
|
+
transaction = JSON.parse(`bitcoin-cli decoderawtransaction #{transaction}`)
|
103
|
+
rescue Errno::ENOENT => e
|
104
|
+
if e.message == 'No such file or directory - bitcoin-cli'
|
105
|
+
raise NoBitcoindInstalled
|
106
|
+
else
|
107
|
+
raise e
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
outs = []
|
112
|
+
total_amount = 0
|
113
|
+
transaction['vout'].each do |out|
|
114
|
+
out['value'] = out['value']*10**8
|
115
|
+
total_amount += out['value'] if address.nil? || address == out['address']
|
116
|
+
outs << { amount: out['value'], receiving_address: out['address'] }
|
117
|
+
end
|
118
|
+
|
119
|
+
{
|
120
|
+
tid: tid,
|
121
|
+
total_amount: total_amount,
|
122
|
+
confirmations: calculate_confirmations(block_height),
|
123
|
+
outs: outs
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# When we call #calculate_confirmations, it doesn't always make a new
|
128
|
+
# request to the blockchain API. Instead, it checks if cached_id matches the one in
|
129
|
+
# the hash. It's useful when we want to calculate confirmations for all transactions for
|
130
|
+
# a certain address without making any new requests to the Blockchain API.
|
131
|
+
def calculate_confirmations(block_height, force_latest_block_reload: false)
|
132
|
+
|
133
|
+
if block_height && block_height != -1
|
134
|
+
latest_block(force_reload: force_latest_block_reload)[:block]["height"] - block_height + 1
|
135
|
+
else
|
136
|
+
0
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|