straight 0.2.3 → 1.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 +4 -4
- data/.travis.yml +5 -0
- data/Gemfile +7 -5
- data/Gemfile.lock +17 -7
- data/README.md +3 -0
- data/Rakefile +9 -0
- data/VERSION +1 -1
- data/lib/straight.rb +9 -1
- data/lib/straight/address_providers/base.rb +28 -0
- data/lib/straight/address_providers/bip32.rb +22 -0
- data/lib/straight/blockchain_adapter.rb +17 -1
- data/lib/straight/blockchain_adapters/biteasy_adapter.rb +14 -13
- data/lib/straight/blockchain_adapters/blockchain_info_adapter.rb +15 -14
- data/lib/straight/blockchain_adapters/insight_adapter.rb +76 -0
- data/lib/straight/blockchain_adapters/mycelium_adapter.rb +74 -49
- data/lib/straight/exchange_rate_adapter.rb +2 -2
- data/lib/straight/exchange_rate_adapters/average_rate_adapter.rb +1 -1
- data/lib/straight/exchange_rate_adapters/okcoin_adapter.rb +1 -1
- data/lib/straight/faraday_monkeypatch.rb +22 -0
- data/lib/straight/gateway.rb +71 -34
- data/lib/straight/order.rb +43 -32
- data/straight.gemspec +20 -27
- metadata +30 -26
- data/spec/lib/blockchain_adapters/biteasy_adapter_spec.rb +0 -48
- data/spec/lib/blockchain_adapters/blockchain_info_adapter_spec.rb +0 -57
- data/spec/lib/blockchain_adapters/mycelium_adapter_spec.rb +0 -58
- data/spec/lib/exchange_rate_adapter_spec.rb +0 -55
- data/spec/lib/exchange_rate_adapters/average_rate_adapter_spec.rb +0 -43
- data/spec/lib/exchange_rate_adapters/bitpay_adapter_spec.rb +0 -27
- data/spec/lib/exchange_rate_adapters/bitstamp_adapter_spec.rb +0 -27
- data/spec/lib/exchange_rate_adapters/btce_adapter_spec.rb +0 -27
- data/spec/lib/exchange_rate_adapters/coinbase_adapter_spec.rb +0 -27
- data/spec/lib/exchange_rate_adapters/kraken_adapter_spec.rb +0 -27
- data/spec/lib/exchange_rate_adapters/localbitcoins_adapter_spec.rb +0 -27
- data/spec/lib/exchange_rate_adapters/okcoin_adapter_spec.rb +0 -27
- data/spec/lib/gateway_spec.rb +0 -98
- data/spec/lib/order_spec.rb +0 -128
- data/spec/spec_helper.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6367d814a3c2ac159870bb0c8b6929f26c6c612
|
4
|
+
data.tar.gz: 525e7bce43d7e17a436103f36432fb637307b628
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5f817e6c86180289c7b6f3dbcad0d10e6d5f2e5de3ce7218c3f8d08fec9ac2851d7878ebd03b825bf0ddb88b096c9cfbfdbe31694c7a46be2458444bdcbdeac
|
7
|
+
data.tar.gz: f6f8afecb366765b9b80f2e471158bf38f614a640aeadddb155a1bdd129c24b0873d2f3ae936ce931458220ec6a6d74f822f805c69a4e276f4f9e9f82c95679d
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
source "
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
|
-
|
4
|
-
gem 'money-tree', "0.9.0"
|
3
|
+
gem 'btcruby', '~> 1.0'
|
5
4
|
|
6
5
|
# Used in exchange rate adapters
|
7
|
-
gem 'satoshi-unit'
|
8
|
-
gem 'httparty'
|
6
|
+
gem 'satoshi-unit', '~> 0.1'
|
7
|
+
gem 'httparty', '~> 0.13.5'
|
8
|
+
gem 'faraday'
|
9
9
|
|
10
10
|
group :development do
|
11
11
|
gem "bundler", "~> 1.0"
|
@@ -15,4 +15,6 @@ end
|
|
15
15
|
|
16
16
|
group :test do
|
17
17
|
gem 'rspec'
|
18
|
+
gem 'webmock'
|
19
|
+
gem 'vcr'
|
18
20
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
GEM
|
2
|
-
remote:
|
2
|
+
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
4
|
addressable (2.3.6)
|
5
|
+
btcruby (1.0.3)
|
6
|
+
ffi (~> 1.9, >= 1.9.3)
|
5
7
|
builder (3.2.2)
|
8
|
+
crack (0.4.2)
|
9
|
+
safe_yaml (~> 1.0.0)
|
6
10
|
descendants_tracker (0.0.4)
|
7
11
|
thread_safe (~> 0.3, >= 0.3.1)
|
8
12
|
diff-lcs (1.2.5)
|
@@ -20,7 +24,7 @@ GEM
|
|
20
24
|
oauth2
|
21
25
|
hashie (3.3.1)
|
22
26
|
highline (1.6.21)
|
23
|
-
httparty (0.13.
|
27
|
+
httparty (0.13.5)
|
24
28
|
json (~> 1.8)
|
25
29
|
multi_xml (>= 0.5.2)
|
26
30
|
jeweler (2.0.1)
|
@@ -35,8 +39,6 @@ GEM
|
|
35
39
|
json (1.8.1)
|
36
40
|
jwt (1.0.0)
|
37
41
|
mini_portile (0.6.0)
|
38
|
-
money-tree (0.9.0)
|
39
|
-
ffi
|
40
42
|
multi_json (1.10.1)
|
41
43
|
multi_xml (0.5.5)
|
42
44
|
multipart-post (2.0.0)
|
@@ -64,17 +66,25 @@ GEM
|
|
64
66
|
rspec-mocks (3.1.0)
|
65
67
|
rspec-support (~> 3.1.0)
|
66
68
|
rspec-support (3.1.0)
|
69
|
+
safe_yaml (1.0.4)
|
67
70
|
satoshi-unit (0.1.7)
|
68
71
|
thread_safe (0.3.4)
|
72
|
+
vcr (2.9.3)
|
73
|
+
webmock (1.21.0)
|
74
|
+
addressable (>= 2.3.6)
|
75
|
+
crack (>= 0.3.2)
|
69
76
|
|
70
77
|
PLATFORMS
|
71
78
|
ruby
|
72
79
|
|
73
80
|
DEPENDENCIES
|
81
|
+
btcruby (~> 1.0)
|
74
82
|
bundler (~> 1.0)
|
83
|
+
faraday
|
75
84
|
github_api (= 0.11.3)
|
76
|
-
httparty
|
85
|
+
httparty (~> 0.13.5)
|
77
86
|
jeweler (~> 2.0.1)
|
78
|
-
money-tree (= 0.9.0)
|
79
87
|
rspec
|
80
|
-
satoshi-unit
|
88
|
+
satoshi-unit (~> 0.1)
|
89
|
+
vcr
|
90
|
+
webmock
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -21,5 +21,14 @@ Jeweler::Tasks.new do |gem|
|
|
21
21
|
gem.description = %Q{An engine for the Straight payment gateway software. Requires no state to be saved (that is, no storage or DB). Its responsibilities only include processing data coming from an actual gateway.}
|
22
22
|
gem.email = "roman.snitko@gmail.com"
|
23
23
|
gem.authors = ["Roman Snitko"]
|
24
|
+
gem.files.exclude 'spec/**/*'
|
24
25
|
end
|
25
26
|
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec)
|
31
|
+
task default: :spec
|
32
|
+
rescue LoadError
|
33
|
+
# no rspec available
|
34
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/lib/straight.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'btcruby'
|
2
2
|
require 'satoshi-unit'
|
3
3
|
require 'json'
|
4
4
|
require 'uri'
|
@@ -6,11 +6,18 @@ require 'open-uri'
|
|
6
6
|
require 'yaml'
|
7
7
|
require 'singleton'
|
8
8
|
require 'httparty'
|
9
|
+
require 'faraday'
|
10
|
+
require_relative 'straight/faraday_monkeypatch'
|
11
|
+
|
12
|
+
module Straight
|
13
|
+
StraightError = Class.new(StandardError)
|
14
|
+
end
|
9
15
|
|
10
16
|
require_relative 'straight/blockchain_adapter'
|
11
17
|
require_relative 'straight/blockchain_adapters/blockchain_info_adapter'
|
12
18
|
require_relative 'straight/blockchain_adapters/biteasy_adapter'
|
13
19
|
require_relative 'straight/blockchain_adapters/mycelium_adapter'
|
20
|
+
require_relative 'straight/blockchain_adapters/insight_adapter'
|
14
21
|
|
15
22
|
require_relative 'straight/exchange_rate_adapter'
|
16
23
|
require_relative 'straight/exchange_rate_adapters/bitpay_adapter'
|
@@ -22,6 +29,7 @@ require_relative 'straight/exchange_rate_adapters/btce_adapter'
|
|
22
29
|
require_relative 'straight/exchange_rate_adapters/kraken_adapter'
|
23
30
|
require_relative 'straight/exchange_rate_adapters/average_rate_adapter'
|
24
31
|
|
32
|
+
require_relative 'straight/address_providers/bip32'
|
25
33
|
|
26
34
|
require_relative 'straight/order'
|
27
35
|
require_relative 'straight/gateway'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Straight
|
2
|
+
module AddressProvider
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_reader :gateway
|
6
|
+
|
7
|
+
def initialize(gateway)
|
8
|
+
@gateway = gateway
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [Hash] args see GatewayModule::Includable#new_order
|
12
|
+
# @return [String] bitcoin address
|
13
|
+
# Returns a Base58-encoded Bitcoin address to which the payment transaction
|
14
|
+
# is expected to arrive. keychain_id is an integer > 0 (hopefully not too large and hopefully
|
15
|
+
# the one a user of this class is going to properly increment) that is used to generate a
|
16
|
+
# an BIP32 bitcoin address deterministically.
|
17
|
+
def new_address(keychain_id:, **args)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
# If this method returns true, then address provider is expected to define
|
22
|
+
# #new_address_and_amount which returns ['address', Integer(amount in satoshi)]
|
23
|
+
def takes_fees?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Straight
|
4
|
+
module AddressProvider
|
5
|
+
class Bip32 < Base
|
6
|
+
def new_address(keychain_id:, **args)
|
7
|
+
path =
|
8
|
+
if gateway.address_derivation_scheme.to_s.empty?
|
9
|
+
# First check the depth. If the depth is 4 use '/i' notation (Mycelium iOS wallet)
|
10
|
+
if gateway.keychain.depth > 3
|
11
|
+
keychain_id.to_s
|
12
|
+
else # Otherwise, use 'm/0/n' - both Electrum and Mycelium on Android
|
13
|
+
"m/0/#{keychain_id.to_s}"
|
14
|
+
end
|
15
|
+
else
|
16
|
+
gateway.address_derivation_scheme.to_s.downcase.sub('n', keychain_id.to_s)
|
17
|
+
end
|
18
|
+
gateway.keychain.derived_key(path).address.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -10,7 +10,14 @@ module Straight
|
|
10
10
|
# Raised when blockchain data cannot be retrived for any reason.
|
11
11
|
# We're not really intereste in the precise reason, although it is
|
12
12
|
# stored in the message.
|
13
|
-
class RequestError <
|
13
|
+
class RequestError < StraightError; end
|
14
|
+
|
15
|
+
# Raised when an invalid address is used, for example a mainnet address
|
16
|
+
# is used on testnet and vice versa.
|
17
|
+
class BitcoinAddressInvalid < StraightError; end
|
18
|
+
|
19
|
+
# How much times try to connect to servers if ReadTimeout error appears
|
20
|
+
MAX_TRIES = 5
|
14
21
|
|
15
22
|
def fetch_transaction(tid)
|
16
23
|
raise "Please implement #fetch_transaction in #{self.to_s}"
|
@@ -34,5 +41,14 @@ module Straight
|
|
34
41
|
|
35
42
|
end
|
36
43
|
|
44
|
+
# Look for the adapter without namespace if not found it in a specific module
|
45
|
+
# @return nil
|
46
|
+
def self.const_missing(name)
|
47
|
+
Kernel.const_get(name)
|
48
|
+
rescue NameError
|
49
|
+
puts "WARNING: No blockchain adapter with the name #{name.to_s} was found!"
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
37
53
|
end
|
38
54
|
end
|
@@ -19,34 +19,35 @@ module Straight
|
|
19
19
|
|
20
20
|
# Returns the current balance of the address
|
21
21
|
def fetch_balance_for(address)
|
22
|
-
|
22
|
+
api_request("/addresses/#{address}")['data']['balance']
|
23
23
|
end
|
24
24
|
|
25
25
|
# Returns transaction info for the tid
|
26
26
|
def fetch_transaction(tid, address: nil)
|
27
|
-
straighten_transaction
|
27
|
+
straighten_transaction api_request("/transactions/#{tid}"), address: address
|
28
28
|
end
|
29
29
|
|
30
30
|
# Returns all transactions for the address
|
31
31
|
def fetch_transactions_for(address)
|
32
|
-
transactions =
|
32
|
+
transactions = api_request("/transactions?address=#{address}")['data']['transactions']
|
33
33
|
transactions.map { |t| straighten_transaction(t, address: address) }
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
38
|
def api_request(url)
|
39
|
-
|
40
|
-
|
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)
|
39
|
+
conn = Faraday.new("#{@base_url}/#{url}", ssl: { verify: false }) do |faraday|
|
40
|
+
faraday.adapter Faraday.default_adapter
|
49
41
|
end
|
42
|
+
result = conn.get
|
43
|
+
unless result.status == 200
|
44
|
+
raise RequestError, "Cannot access remote API, response code was #{result.code}"
|
45
|
+
end
|
46
|
+
JSON.parse(result.body)
|
47
|
+
rescue JSON::ParserError => e
|
48
|
+
raise RequestError, YAML::dump(e)
|
49
|
+
rescue => e
|
50
|
+
raise RequestError, YAML::dump(e)
|
50
51
|
end
|
51
52
|
|
52
53
|
# Converts transaction info received from the source into the
|
@@ -20,18 +20,18 @@ module Straight
|
|
20
20
|
|
21
21
|
# Returns transaction info for the tid
|
22
22
|
def fetch_transaction(tid, address: nil)
|
23
|
-
straighten_transaction
|
23
|
+
straighten_transaction(api_request("/rawtx/#{tid}"), address: address)
|
24
24
|
end
|
25
25
|
|
26
26
|
# Returns all transactions for the address
|
27
27
|
def fetch_transactions_for(address)
|
28
|
-
transactions =
|
28
|
+
transactions = api_request("/rawaddr/#{address}")['txs']
|
29
29
|
transactions.map { |t| straighten_transaction(t, address: address) }
|
30
30
|
end
|
31
31
|
|
32
32
|
# Returns the current balance of the address
|
33
33
|
def fetch_balance_for(address)
|
34
|
-
|
34
|
+
api_request("/rawaddr/#{address}")['final_balance']
|
35
35
|
end
|
36
36
|
|
37
37
|
def latest_block(force_reload: false)
|
@@ -42,7 +42,7 @@ module Straight
|
|
42
42
|
force_reload
|
43
43
|
@latest_block = {
|
44
44
|
cache_timestamp: Time.now,
|
45
|
-
block:
|
45
|
+
block: api_request("/latestblock")
|
46
46
|
}
|
47
47
|
else
|
48
48
|
@latest_block
|
@@ -52,17 +52,18 @@ module Straight
|
|
52
52
|
private
|
53
53
|
|
54
54
|
def api_request(url)
|
55
|
-
|
56
|
-
|
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)
|
55
|
+
conn = Faraday.new(url: "#{@base_url}/#{url}", ssl: { verify: false }) do |faraday|
|
56
|
+
faraday.adapter Faraday.default_adapter
|
65
57
|
end
|
58
|
+
result = conn.get
|
59
|
+
unless result.status == 200
|
60
|
+
raise RequestError, "Cannot access remote API, response code was #{result.code}"
|
61
|
+
end
|
62
|
+
JSON.parse(result.body)
|
63
|
+
rescue JSON::ParserError => e
|
64
|
+
raise RequestError, YAML::dump(e)
|
65
|
+
rescue => e
|
66
|
+
raise RequestError, YAML::dump(e)
|
66
67
|
end
|
67
68
|
|
68
69
|
# Converts transaction info received from the source into the
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Straight
|
2
|
+
module Blockchain
|
3
|
+
|
4
|
+
class InsightAdapter < Adapter
|
5
|
+
|
6
|
+
@@test_url = nil
|
7
|
+
|
8
|
+
def self.mainnet_adapter(main_url:, test_url: nil)
|
9
|
+
@@test_url = test_url
|
10
|
+
new(main_url)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.testnet_adapter
|
14
|
+
raise "Testnet not implemented" unless @@test_url
|
15
|
+
new(@@test_url)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(host_url)
|
19
|
+
@base_url = host_url
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_transaction(tid, address: nil)
|
23
|
+
res = api_request("/tx/", tid)
|
24
|
+
straighten_transaction(res, address: address)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_transactions_for(address)
|
28
|
+
res = api_request("/addr/", address)
|
29
|
+
return [] if res["transactions"].empty?
|
30
|
+
[fetch_transaction(res["transactions"].first, address: address)]
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch_balance_for(address)
|
34
|
+
res = api_request("/addr/", address)
|
35
|
+
res["balanceSat"].to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def api_request(place, val)
|
41
|
+
req_url = @base_url + place + val
|
42
|
+
conn = Faraday.new(url: req_url, ssl: { verify: false }) do |faraday|
|
43
|
+
faraday.adapter Faraday.default_adapter
|
44
|
+
end
|
45
|
+
result = conn.get do |req|
|
46
|
+
req.headers['Content-Type'] = 'application/json'
|
47
|
+
end
|
48
|
+
JSON.parse(result.body)
|
49
|
+
rescue JSON::ParserError => e
|
50
|
+
raise BitcoinAddressInvalid, message: "address in question: #{val}" if e.message.include?("Invalid address")
|
51
|
+
raise RequestError, YAML::dump(e)
|
52
|
+
rescue => e
|
53
|
+
raise RequestError, YAML::dump(e)
|
54
|
+
end
|
55
|
+
|
56
|
+
def straighten_transaction(transaction, address: nil)
|
57
|
+
total_amount = 0
|
58
|
+
tid = transaction["txid"]
|
59
|
+
transaction["vout"].each do |o|
|
60
|
+
total_amount += Satoshi.new(o["value"]) if address.nil? || address == o["scriptPubKey"]["addresses"].first
|
61
|
+
end
|
62
|
+
confirmations = transaction["confirmations"]
|
63
|
+
outs = transaction["vout"].map { |o| {amount: Satoshi.new(o["value"]).to_i, receiving_address: o["scriptPubKey"]["addresses"].first} }
|
64
|
+
|
65
|
+
{
|
66
|
+
tid: tid,
|
67
|
+
total_amount: total_amount,
|
68
|
+
confirmations: confirmations || 0,
|
69
|
+
outs: outs || []
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -3,30 +3,46 @@ module Straight
|
|
3
3
|
|
4
4
|
class MyceliumAdapter < Adapter
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
MAINNET_SERVERS = %w{
|
7
|
+
https://mws2.mycelium.com/wapi/wapi
|
8
|
+
https://mws6.mycelium.com/wapi/wapi
|
9
|
+
https://mws7.mycelium.com/wapi/wapi
|
10
|
+
}
|
11
|
+
TESTNET_SERVERS = %w{
|
12
|
+
https://node3.mycelium.com/wapitestnet/wapi
|
13
|
+
}
|
14
|
+
PINNED_CERTIFICATES = %w{
|
15
|
+
6afe0e9b6806fa4a49fc6818512014332953f30101dad7b91e76c14e073c3134
|
16
|
+
}.to_set
|
14
17
|
|
15
18
|
def self.mainnet_adapter
|
16
|
-
|
17
|
-
instance._initialize("https://mws2.mycelium.com/wapi/wapi")
|
18
|
-
instance
|
19
|
+
new(testnet: false)
|
19
20
|
end
|
20
|
-
|
21
|
+
|
21
22
|
def self.testnet_adapter
|
22
|
-
|
23
|
-
instance._initialize("https://node3.mycelium.com/wapitestnet/wapi")
|
24
|
-
instance
|
23
|
+
new(testnet: true)
|
25
24
|
end
|
26
|
-
|
27
|
-
def
|
25
|
+
|
26
|
+
def initialize(testnet: false)
|
28
27
|
@latest_block = { cache_timestamp: nil, block: nil }
|
29
|
-
@
|
28
|
+
@testnet = testnet
|
29
|
+
@api_servers = @testnet ? TESTNET_SERVERS : MAINNET_SERVERS
|
30
|
+
set_base_url
|
31
|
+
end
|
32
|
+
|
33
|
+
def testnet?
|
34
|
+
@testnet
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set url for API request.
|
38
|
+
# @param num [Integer] a number of server in array
|
39
|
+
def set_base_url(num = 0)
|
40
|
+
return nil if num >= @api_servers.size
|
41
|
+
@base_url = @api_servers[num]
|
42
|
+
end
|
43
|
+
|
44
|
+
def next_server
|
45
|
+
set_base_url(@api_servers.index(@base_url) + 1)
|
30
46
|
end
|
31
47
|
|
32
48
|
# Returns transaction info for the tid
|
@@ -38,8 +54,15 @@ module Straight
|
|
38
54
|
# Supposed to returns all transactions for the address, but
|
39
55
|
# currently actually returns the first one, since we only need one.
|
40
56
|
def fetch_transactions_for(address)
|
41
|
-
|
42
|
-
|
57
|
+
# API may return nil instead of an empty array if address turns out to be invalid
|
58
|
+
# (for example when trying to supply a testnet address instead of mainnet while using
|
59
|
+
# mainnet adapter.
|
60
|
+
if api_response = api_request('queryTransactionInventory', { addresses: [address], limit: 1 })
|
61
|
+
tid = api_response["txIds"].first
|
62
|
+
tid ? [fetch_transaction(tid, address: address)] : []
|
63
|
+
else
|
64
|
+
raise BitcoinAddressInvalid, message: "address in question: #{address}"
|
65
|
+
end
|
43
66
|
end
|
44
67
|
|
45
68
|
# Returns the current balance of the address
|
@@ -69,54 +92,56 @@ module Straight
|
|
69
92
|
private
|
70
93
|
|
71
94
|
def api_request(method, params={})
|
95
|
+
ssl_opts =
|
96
|
+
if testnet?
|
97
|
+
{verify: false}
|
98
|
+
else
|
99
|
+
{verify_callback: lambda { |preverify_ok, store_context|
|
100
|
+
end_cert = store_context.chain[0] # pinned invalid certificate
|
101
|
+
PINNED_CERTIFICATES.include?(OpenSSL::Digest::SHA256.hexdigest(end_cert.to_der)) || preverify_ok
|
102
|
+
}}
|
103
|
+
end
|
72
104
|
begin
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
rescue
|
83
|
-
raise
|
105
|
+
conn = Faraday.new(url: "#{@base_url}/#{method}", ssl: ssl_opts) do |faraday|
|
106
|
+
faraday.request :url_encoded # form-encode POST params
|
107
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
108
|
+
end
|
109
|
+
result = conn.post do |req|
|
110
|
+
req.body = params.merge({version: 1}).to_json
|
111
|
+
req.headers['Content-Type'] = 'application/json'
|
112
|
+
end
|
113
|
+
JSON.parse(result.body || '')['r']
|
114
|
+
rescue => e
|
115
|
+
next_server ? retry : raise(RequestError, YAML::dump(e))
|
84
116
|
end
|
85
117
|
end
|
86
118
|
|
87
119
|
# Converts transaction info received from the source into the
|
88
120
|
# unified format expected by users of BlockchainAdapter instances.
|
89
121
|
def straighten_transaction(transaction, address: nil)
|
90
|
-
|
91
122
|
# Get the block number this transaction was included into
|
92
123
|
block_height = transaction['height']
|
93
124
|
tid = transaction['txid']
|
94
125
|
|
95
|
-
# Converting from Base64 to
|
96
|
-
transaction = transaction['binary'].unpack(
|
126
|
+
# Converting from Base64 to binary
|
127
|
+
transaction = transaction['binary'].unpack('m0')[0]
|
97
128
|
|
98
|
-
# Decoding
|
99
|
-
|
100
|
-
transaction = JSON.parse(`bitcoin-cli decoderawtransaction #{transaction}`)
|
101
|
-
rescue Errno::ENOENT => e
|
102
|
-
if e.message == 'No such file or directory - bitcoin-cli'
|
103
|
-
raise NoBitcoindInstalled
|
104
|
-
else
|
105
|
-
raise e
|
106
|
-
end
|
107
|
-
end
|
129
|
+
# Decoding
|
130
|
+
transaction = BTC::Transaction.new(data: transaction)
|
108
131
|
|
109
132
|
outs = []
|
110
133
|
total_amount = 0
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
134
|
+
|
135
|
+
transaction.outputs.each do |out|
|
136
|
+
amount = out.value
|
137
|
+
receiving_address = out.script.standard_address
|
138
|
+
total_amount += amount if address.nil? || address == receiving_address.to_s
|
139
|
+
outs << {amount: amount, receiving_address: receiving_address}
|
115
140
|
end
|
116
141
|
|
117
142
|
{
|
118
143
|
tid: tid,
|
119
|
-
total_amount: total_amount,
|
144
|
+
total_amount: total_amount.to_i,
|
120
145
|
confirmations: calculate_confirmations(block_height),
|
121
146
|
outs: outs
|
122
147
|
}
|