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
@@ -3,7 +3,8 @@ module Straight
|
|
3
3
|
|
4
4
|
class Adapter
|
5
5
|
|
6
|
-
|
6
|
+
include Singleton
|
7
|
+
|
7
8
|
class FetchingFailed < Exception; end
|
8
9
|
class CurrencyNotSupported < Exception; end
|
9
10
|
|
@@ -39,6 +40,24 @@ module Straight
|
|
39
40
|
nil # this should be changed in descendant classes
|
40
41
|
end
|
41
42
|
|
43
|
+
# This method will get value we are interested in from hash and
|
44
|
+
# prevent failing with 'undefined method [] for Nil' if at some point hash doesn't have such key value pair
|
45
|
+
def get_rate_value_from_hash(rates_hash, *keys)
|
46
|
+
keys.inject(rates_hash) do |intermediate, key|
|
47
|
+
if intermediate.respond_to?(:[])
|
48
|
+
intermediate[key]
|
49
|
+
else
|
50
|
+
raise CurrencyNotSupported
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# We dont want to have false positive rate, because nil.to_f is 0.0
|
56
|
+
# This method checks that rate value is not nil
|
57
|
+
def rate_to_f(rate)
|
58
|
+
rate ? rate.to_f : raise(CurrencyNotSupported)
|
59
|
+
end
|
60
|
+
|
42
61
|
end
|
43
62
|
|
44
63
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Straight
|
2
|
+
module ExchangeRate
|
3
|
+
|
4
|
+
class AverageRateAdapter < Adapter
|
5
|
+
|
6
|
+
# Takes exchange rate adapters instances or classes as arguments
|
7
|
+
def self.instance(*adapters)
|
8
|
+
instance = super()
|
9
|
+
instance.instance_variable_set(:@adapters, adapters.map { |adapter| adapter.respond_to?(:instance) ? adapter.instance : adapter })
|
10
|
+
instance
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch_rates!
|
14
|
+
failed_fetches = 0
|
15
|
+
@adapters.each do |adapter|
|
16
|
+
begin
|
17
|
+
adapter.fetch_rates!
|
18
|
+
rescue Exception => e
|
19
|
+
failed_fetches += 1
|
20
|
+
raise e if failed_fetches == @adapters.size
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def rate_for(currency_code)
|
26
|
+
rates = []
|
27
|
+
@adapters.each do |adapter|
|
28
|
+
begin
|
29
|
+
rates << adapter.rate_for(currency_code)
|
30
|
+
rescue CurrencyNotSupported
|
31
|
+
rates << nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
unless rates.select(&:nil?).size == @adapters.size
|
36
|
+
rates.compact!
|
37
|
+
rates.inject {|sum, rate| sum + rate} / rates.size
|
38
|
+
else
|
39
|
+
raise CurrencyNotSupported
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_rate_value_from_hash(rates_hash, *keys)
|
44
|
+
raise "This method is not supposed to be used in #{self.class}."
|
45
|
+
end
|
46
|
+
|
47
|
+
def rate_to_f(rate)
|
48
|
+
raise "This method is not supposed to be used in #{self.class}."
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -7,8 +7,11 @@ module Straight
|
|
7
7
|
|
8
8
|
def rate_for(currency_code)
|
9
9
|
super
|
10
|
-
@rates.each do |
|
11
|
-
|
10
|
+
@rates.each do |rt|
|
11
|
+
if rt['code'] == currency_code
|
12
|
+
rate = get_rate_value_from_hash(rt, 'rate')
|
13
|
+
return rate_to_f(rate)
|
14
|
+
end
|
12
15
|
end
|
13
16
|
raise CurrencyNotSupported
|
14
17
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Straight
|
2
|
+
module ExchangeRate
|
3
|
+
|
4
|
+
class BtceAdapter < Adapter
|
5
|
+
|
6
|
+
FETCH_URL = 'https://btc-e.com/api/2/btc_usd/ticker'
|
7
|
+
|
8
|
+
def rate_for(currency_code)
|
9
|
+
super
|
10
|
+
raise CurrencyNotSupported if !FETCH_URL.include?("btc_#{currency_code.downcase}")
|
11
|
+
rate = get_rate_value_from_hash(@rates, 'ticker', 'last')
|
12
|
+
rate_to_f(rate)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -7,10 +7,8 @@ module Straight
|
|
7
7
|
|
8
8
|
def rate_for(currency_code)
|
9
9
|
super
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
raise CurrencyNotSupported
|
10
|
+
rate = get_rate_value_from_hash(@rates, "btc_to_#{currency_code.downcase}")
|
11
|
+
rate_to_f(rate)
|
14
12
|
end
|
15
13
|
|
16
14
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Straight
|
2
|
+
module ExchangeRate
|
3
|
+
|
4
|
+
class KrakenAdapter < Adapter
|
5
|
+
|
6
|
+
FETCH_URL = 'https://api.kraken.com/0/public/Ticker?pair=xbtusd'
|
7
|
+
|
8
|
+
def rate_for(currency_code)
|
9
|
+
super
|
10
|
+
rate = get_rate_value_from_hash(@rates, 'result', 'XXBTZ' + currency_code.upcase, 'c')
|
11
|
+
rate = rate.kind_of?(Array) ? rate.first : raise(CurrencyNotSupported)
|
12
|
+
rate_to_f(rate)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Straight
|
2
|
+
module ExchangeRate
|
3
|
+
|
4
|
+
class LocalbitcoinsAdapter < Adapter
|
5
|
+
|
6
|
+
FETCH_URL = 'https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/'
|
7
|
+
|
8
|
+
def rate_for(currency_code)
|
9
|
+
super
|
10
|
+
rate = get_rate_value_from_hash(@rates, currency_code.upcase, 'rates', 'last')
|
11
|
+
rate_to_f(rate)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Straight
|
2
|
+
module ExchangeRate
|
3
|
+
|
4
|
+
class OkcoinAdapter < Adapter
|
5
|
+
|
6
|
+
FETCH_URL = 'https://www.okcoin.com/api/ticker.do?ok=1'
|
7
|
+
|
8
|
+
def rate_for(currency_code)
|
9
|
+
super
|
10
|
+
raise CurrencyNotSupported if currency_code != 'USD'
|
11
|
+
rate = get_rate_value_from_hash(@rates, 'ticker', 'last')
|
12
|
+
rate_to_f(rate)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/straight/gateway.rb
CHANGED
@@ -43,9 +43,8 @@ module Straight
|
|
43
43
|
|
44
44
|
# Determines the algorithm for consequitive checks of the order status.
|
45
45
|
DEFAULT_STATUS_CHECK_SCHEDULE = -> (period, iteration_index) do
|
46
|
-
return false if period > 640
|
47
46
|
iteration_index += 1
|
48
|
-
if iteration_index
|
47
|
+
if iteration_index >= 20
|
49
48
|
period *= 2
|
50
49
|
iteration_index = 0
|
51
50
|
end
|
@@ -56,7 +55,7 @@ module Straight
|
|
56
55
|
# call super() somehwere inside those methods.
|
57
56
|
#
|
58
57
|
# In short, the idea is to let the class we're being prepended to do its magic
|
59
|
-
# after
|
58
|
+
# after our methods are finished.
|
60
59
|
module Prependable
|
61
60
|
end
|
62
61
|
|
@@ -85,7 +84,8 @@ module Straight
|
|
85
84
|
# the one a user of this class is going to properly increment) that is used to generate a
|
86
85
|
# an BIP32 bitcoin address deterministically.
|
87
86
|
def address_for_keychain_id(id)
|
88
|
-
|
87
|
+
# The 'm/0/n' notation is used by both Electrum and Mycelium
|
88
|
+
keychain.node_for_path("m/0/#{id.to_s}").to_address
|
89
89
|
end
|
90
90
|
|
91
91
|
def fetch_transaction(tid, address: nil)
|
@@ -101,7 +101,7 @@ module Straight
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def keychain
|
104
|
-
@keychain ||= MoneyTree::Node.from_serialized_address(
|
104
|
+
@keychain ||= MoneyTree::Node.from_serialized_address(pubkey)
|
105
105
|
end
|
106
106
|
|
107
107
|
# This is a callback method called from each order
|
@@ -130,6 +130,13 @@ module Straight
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
+
def current_exchange_rate(currency=self.default_currency)
|
134
|
+
currency = currency.to_s.upcase
|
135
|
+
try_adapters(@exchange_rate_adapters) do |a|
|
136
|
+
a.rate_for(currency)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
133
140
|
private
|
134
141
|
|
135
142
|
# Calls the block with each adapter until one of them does not fail.
|
@@ -163,12 +170,16 @@ module Straight
|
|
163
170
|
@default_currency = 'BTC'
|
164
171
|
@blockchain_adapters = [
|
165
172
|
Blockchain::BlockchainInfoAdapter.mainnet_adapter,
|
166
|
-
Blockchain::
|
173
|
+
Blockchain::MyceliumAdapter.mainnet_adapter
|
167
174
|
]
|
168
175
|
@exchange_rate_adapters = [
|
169
|
-
ExchangeRate::BitpayAdapter.
|
170
|
-
ExchangeRate::CoinbaseAdapter.
|
171
|
-
ExchangeRate::BitstampAdapter.
|
176
|
+
ExchangeRate::BitpayAdapter.instance,
|
177
|
+
ExchangeRate::CoinbaseAdapter.instance,
|
178
|
+
ExchangeRate::BitstampAdapter.instance,
|
179
|
+
ExchangeRate::BtceAdapter.instance,
|
180
|
+
ExchangeRate::KrakenAdapter.instance,
|
181
|
+
ExchangeRate::LocalbitcoinsAdapter.instance,
|
182
|
+
ExchangeRate::OkcoinAdapter.instance
|
172
183
|
]
|
173
184
|
@status_check_schedule = DEFAULT_STATUS_CHECK_SCHEDULE
|
174
185
|
end
|
data/lib/straight/order.rb
CHANGED
@@ -44,6 +44,8 @@ module Straight
|
|
44
44
|
expired: 5 # too much time passed since creating an order
|
45
45
|
}
|
46
46
|
|
47
|
+
attr_reader :old_status
|
48
|
+
|
47
49
|
class IncorrectAmount < Exception; end
|
48
50
|
|
49
51
|
# If you are defining methods in this module, it means you most likely want to
|
@@ -61,7 +63,18 @@ module Straight
|
|
61
63
|
# If as_sym is set to true, then each status is returned as Symbol, otherwise
|
62
64
|
# an equivalent Integer from STATUSES is returned.
|
63
65
|
def status(as_sym: false, reload: false)
|
64
|
-
|
66
|
+
|
67
|
+
if defined?(super)
|
68
|
+
begin
|
69
|
+
@status = super
|
70
|
+
# if no method with arguments found in the class
|
71
|
+
# we're prepending to, then let's use a standard getter
|
72
|
+
# with no argument.
|
73
|
+
rescue ArgumentError
|
74
|
+
@status = super()
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
65
78
|
# Prohibit status update if the order was paid in some way.
|
66
79
|
# This is just a caching workaround so we don't query
|
67
80
|
# the blockchain needlessly. The actual safety switch is in the setter.
|
@@ -105,11 +118,16 @@ module Straight
|
|
105
118
|
# The order in which these statements currently are prevents that error, because
|
106
119
|
# by the time a callback checks the status it's already set.
|
107
120
|
@status_changed = (@status != new_status)
|
121
|
+
@old_status = @status
|
108
122
|
@status = new_status
|
109
|
-
gateway.order_status_changed(self) if
|
123
|
+
gateway.order_status_changed(self) if status_changed?
|
110
124
|
super if defined?(super)
|
111
125
|
end
|
112
126
|
|
127
|
+
def status_changed?
|
128
|
+
@status_changed
|
129
|
+
end
|
130
|
+
|
113
131
|
end
|
114
132
|
|
115
133
|
module Includable
|
@@ -144,22 +162,32 @@ module Straight
|
|
144
162
|
# order.start_periodic_status_check
|
145
163
|
# end
|
146
164
|
#
|
147
|
-
|
148
|
-
|
165
|
+
# `duration` argument (value is in seconds) allows you to
|
166
|
+
# control in what time an order expires. In other words, we
|
167
|
+
# keep checking for new transactions until the time passes.
|
168
|
+
# Then we stop and set Order's status to STATUS[:expired]. See
|
169
|
+
# #check_status_on_schedule for the implementation details.
|
170
|
+
def start_periodic_status_check(duration: 600)
|
171
|
+
check_status_on_schedule(duration: duration)
|
149
172
|
end
|
150
173
|
|
151
174
|
# Recursion here! Keeps calling itself according to the schedule until
|
152
175
|
# either the status changes or the schedule tells it to stop.
|
153
|
-
def check_status_on_schedule(period: 10, iteration_index: 0)
|
176
|
+
def check_status_on_schedule(period: 10, iteration_index: 0, duration: 600, time_passed: 0)
|
154
177
|
self.status(reload: true)
|
155
|
-
|
156
|
-
if
|
157
|
-
|
158
|
-
|
159
|
-
period
|
160
|
-
|
161
|
-
|
162
|
-
|
178
|
+
time_passed += period
|
179
|
+
if duration >= time_passed # Stop checking if status is >= 2
|
180
|
+
if self.status < 2
|
181
|
+
schedule = gateway.status_check_schedule.call(period, iteration_index)
|
182
|
+
sleep period
|
183
|
+
check_status_on_schedule(
|
184
|
+
period: schedule[:period],
|
185
|
+
iteration_index: schedule[:iteration_index],
|
186
|
+
duration: duration,
|
187
|
+
time_passed: time_passed
|
188
|
+
)
|
189
|
+
end
|
190
|
+
elsif self.status < 2
|
163
191
|
self.status = STATUSES[:expired]
|
164
192
|
end
|
165
193
|
end
|
@@ -172,6 +200,11 @@ module Straight
|
|
172
200
|
{ status: status, amount: amount, address: address, tid: tid }
|
173
201
|
end
|
174
202
|
|
203
|
+
def amount_in_btc(as: :number)
|
204
|
+
a = Satoshi.new(amount, from_unit: :satoshi, to_unit: :btc)
|
205
|
+
as == :string ? a.to_unit(as: :string) : a.to_unit
|
206
|
+
end
|
207
|
+
|
175
208
|
end
|
176
209
|
|
177
210
|
end
|
@@ -1,14 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
RSpec.describe Straight::Blockchain::
|
3
|
+
RSpec.describe Straight::Blockchain::BiteasyAdapter do
|
4
4
|
|
5
|
-
subject(:adapter) { Straight::Blockchain::
|
6
|
-
|
7
|
-
it "fetches all transactions for the current address" do
|
8
|
-
address = "3B1QZ8FpAaHBgkSB5gFt76ag5AW9VeP8xp"
|
9
|
-
expect(adapter).to receive(:straighten_transaction).with(anything, address: address).at_least(:once)
|
10
|
-
expect(adapter.fetch_transactions_for(address)).not_to be_empty
|
11
|
-
end
|
5
|
+
subject(:adapter) { Straight::Blockchain::BiteasyAdapter.mainnet_adapter }
|
12
6
|
|
13
7
|
it "fetches the balance for a given address" do
|
14
8
|
address = "3B1QZ8FpAaHBgkSB5gFt76ag5AW9VeP8xp"
|
@@ -20,24 +14,35 @@ RSpec.describe Straight::Blockchain::HelloblockIoAdapter do
|
|
20
14
|
expect(adapter.fetch_transaction(tid)[:total_amount]).to eq(832947)
|
21
15
|
end
|
22
16
|
|
23
|
-
it "
|
24
|
-
|
25
|
-
expect(adapter.
|
17
|
+
it "calculates total_amount of a transaction for the given address only" do
|
18
|
+
t = { 'data' => {'outputs' => [{ 'value' => 1, 'to_address' => 'address1'}, { 'value' => 2, 'to_address' => 'address2'}] } }
|
19
|
+
expect(adapter.send(:straighten_transaction, t, address: 'address1')[:total_amount]).to eq(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "fetches all transactions for the current address" do
|
23
|
+
address = "3B1QZ8FpAaHBgkSB5gFt76ag5AW9VeP8xp"
|
24
|
+
expect(adapter).to receive(:straighten_transaction).with(anything, address: address).at_least(:once)
|
25
|
+
expect(adapter.fetch_transactions_for(address)).not_to be_empty
|
26
26
|
end
|
27
27
|
|
28
|
-
it "
|
28
|
+
it "calculates the number of confirmations for each transaction" do
|
29
29
|
tid = 'ae0d040f48d75fdc46d9035236a1782164857d6f0cca1f864640281115898560'
|
30
30
|
expect(adapter.fetch_transaction(tid)[:confirmations]).to be > 0
|
31
31
|
end
|
32
32
|
|
33
|
-
it "
|
34
|
-
|
35
|
-
expect(
|
33
|
+
it "gets a transaction id among other data" do
|
34
|
+
tid = 'ae0d040f48d75fdc46d9035236a1782164857d6f0cca1f864640281115898560'
|
35
|
+
expect(adapter.fetch_transaction(tid)[:tid]).to eq(tid)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "raises an exception when something goes wrong with fetching data" do
|
39
|
+
expect( -> { adapter.send(:api_request, "/a-404-request") }).to raise_error(Straight::Blockchain::Adapter::RequestError)
|
36
40
|
end
|
37
41
|
|
38
|
-
it "
|
39
|
-
|
40
|
-
|
42
|
+
it "uses the same Singleton instance" do
|
43
|
+
a = Straight::Blockchain::BiteasyAdapter.mainnet_adapter
|
44
|
+
b = Straight::Blockchain::BiteasyAdapter.mainnet_adapter
|
45
|
+
expect(a).to eq(b)
|
41
46
|
end
|
42
47
|
|
43
48
|
end
|
data/spec/lib/blockchain_adapters/{blockchain_info_spec.rb → blockchain_info_adapter_spec.rb}
RENAMED
@@ -31,7 +31,7 @@ RSpec.describe Straight::Blockchain::BlockchainInfoAdapter do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
it "caches blockchain.info latestblock requests" do
|
34
|
-
expect(adapter).to receive(:
|
34
|
+
expect(adapter).to receive(:api_request).once.and_return('{ "height": 1 }')
|
35
35
|
adapter.send(:calculate_confirmations, { "block_height" => 1 }, force_latest_block_reload: true)
|
36
36
|
adapter.send(:calculate_confirmations, { "block_height" => 1 })
|
37
37
|
adapter.send(:calculate_confirmations, { "block_height" => 1 })
|
@@ -40,8 +40,7 @@ RSpec.describe Straight::Blockchain::BlockchainInfoAdapter do
|
|
40
40
|
end
|
41
41
|
|
42
42
|
it "raises an exception when something goes wrong with fetching datd" do
|
43
|
-
|
44
|
-
expect( -> { adapter.http_request("http://blockchain.info/a-timed-out-request") }).to raise_error(Straight::Blockchain::Adapter::RequestError)
|
43
|
+
expect( -> { adapter.send(:api_request, "/a-404-request") }).to raise_error(Straight::Blockchain::Adapter::RequestError)
|
45
44
|
end
|
46
45
|
|
47
46
|
it "calculates total_amount of a transaction for the given address only" do
|
@@ -49,4 +48,10 @@ RSpec.describe Straight::Blockchain::BlockchainInfoAdapter do
|
|
49
48
|
expect(adapter.send(:straighten_transaction, t, address: 'address1')[:total_amount]).to eq(1)
|
50
49
|
end
|
51
50
|
|
51
|
+
it "uses the same Singleton instance" do
|
52
|
+
a = Straight::Blockchain::BlockchainInfoAdapter.mainnet_adapter
|
53
|
+
b = Straight::Blockchain::BlockchainInfoAdapter.mainnet_adapter
|
54
|
+
expect(a).to eq(b)
|
55
|
+
end
|
56
|
+
|
52
57
|
end
|