diff --git a/app/controllers/admin/currencies_controller.rb b/app/controllers/admin/currencies_controller.rb
index 4236880f9c..b2b4be86d8 100644
--- a/app/controllers/admin/currencies_controller.rb
+++ b/app/controllers/admin/currencies_controller.rb
@@ -54,6 +54,7 @@ def permitted_currency_attributes
icon_url
quick_withdraw_limit
min_deposit_amount
+ min_collection_amount
withdraw_fee
deposit_fee
enabled
diff --git a/app/models/currency.rb b/app/models/currency.rb
index 099748ae94..441937777e 100644
--- a/app/models/currency.rb
+++ b/app/models/currency.rb
@@ -22,6 +22,7 @@ class Currency < ActiveRecord::Base
validates :quick_withdraw_limit,
:min_deposit_amount,
+ :min_collection_amount,
:withdraw_fee,
:deposit_fee,
numericality: { greater_than_or_equal_to: 0 }
@@ -147,25 +148,26 @@ def must_not_disable_all_markets
end
# == Schema Information
-# Schema version: 20181004114428
+# Schema version: 20181126101312
#
# Table name: currencies
#
-# id :string(10) not null, primary key
-# blockchain_key :string(32)
-# symbol :string(1) not null
-# type :string(30) default("coin"), not null
-# deposit_fee :decimal(32, 16) default(0.0), not null
-# quick_withdraw_limit :decimal(32, 16) default(0.0), not null
-# min_deposit_amount :decimal(32, 16) default(0.0), not null
-# withdraw_fee :decimal(32, 16) default(0.0), not null
-# options :string(1000) default({}), not null
-# enabled :boolean default(TRUE), not null
-# base_factor :integer default(1), not null
-# precision :integer default(8), not null
-# icon_url :string(255)
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :string(10) not null, primary key
+# blockchain_key :string(32)
+# symbol :string(1) not null
+# type :string(30) default("coin"), not null
+# deposit_fee :decimal(32, 16) default(0.0), not null
+# quick_withdraw_limit :decimal(32, 16) default(0.0), not null
+# min_deposit_amount :decimal(32, 16) default(0.0), not null
+# min_collection_amount :decimal(32, 16) default(0.0), not null
+# withdraw_fee :decimal(32, 16) default(0.0), not null
+# options :string(1000) default({}), not null
+# enabled :boolean default(TRUE), not null
+# base_factor :integer default(1), not null
+# precision :integer default(8), not null
+# icon_url :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
#
# Indexes
#
diff --git a/app/models/wallet.rb b/app/models/wallet.rb
index e5e40c8c5b..e50d3b3fb3 100644
--- a/app/models/wallet.rb
+++ b/app/models/wallet.rb
@@ -1,8 +1,17 @@
# encoding: UTF-8
# frozen_string_literal: true
+
class Wallet < ActiveRecord::Base
- KIND = %w[hot warm cold deposit fee].freeze
+ extend Enumerize
+
+ # We use this attribute values rules for wallet kinds:
+ # 1** - for deposit wallets.
+ # 2** - for fee wallets.
+ # 3** - for withdraw wallets (sorted by security hot < warm < cold).
+ ENUMERIZED_KINDS = { deposit: 100, fee: 200, hot: 310, warm: 320, cold: 330 }.freeze
+ enumerize :kind, in: ENUMERIZED_KINDS, scope: true
+
GATEWAYS = %w[bitcoind bitcoincashd litecoind geth dashd rippled bitgo].freeze
SETTING_ATTRIBUTES = %i[ uri
secret
@@ -22,7 +31,6 @@ class Wallet < ActiveRecord::Base
validates :address, presence: true
validates :status, inclusion: { in: %w[active disabled] }
- validates :kind, inclusion: { in: KIND }
validates :gateway, inclusion: { in: GATEWAYS }
validates :nsig, numericality: { greater_than_or_equal_to: 1, only_integer: true }
@@ -30,21 +38,51 @@ class Wallet < ActiveRecord::Base
validates :uri, url: { allow_blank: true }
scope :active, -> { where(status: :active) }
- scope :deposit, -> { where(kind: :deposit) }
- scope :withdraw, -> { where.not(kind: :deposit) }
+ scope :deposit, -> { where(kind: kinds(deposit: true, values: true)) }
+ scope :fee, -> { where(kind: kinds(fee: true, values: true)) }
+ scope :withdraw, -> { where(kind: kinds(withdraw: true, values: true)) }
+ scope :ordered, -> { order(kind: :asc) }
before_validation do
next unless blockchain_api&.supports_cash_addr_format? && address?
self.address = CashAddr::Converter.to_cash_address(address)
end
+ class << self
+ def kinds(options={})
+ ENUMERIZED_KINDS
+ .yield_self do |kinds|
+ case
+ when options.fetch(:deposit, false)
+ kinds.select { |_k, v| v / 100 == 1 }
+ when options.fetch(:fee, false)
+ kinds.select { |_k, v| v / 100 == 2 }
+ when options.fetch(:withdraw, false)
+ kinds.select { |_k, v| v / 100 == 3 }
+ else
+ kinds
+ end
+ end
+ .yield_self do |kinds|
+ case
+ when options.fetch(:keys, false)
+ kinds.keys
+ when options.fetch(:values, false)
+ kinds.values
+ else
+ kinds
+ end
+ end
+ end
+ end
+
def wallet_url
blockchain.explorer_address.gsub('#{address}', address) if blockchain
end
end
# == Schema Information
-# Schema version: 20180813105100
+# Schema version: 20181017114624
#
# Table name: wallets
#
@@ -53,7 +91,7 @@ def wallet_url
# currency_id :string(10)
# name :string(64)
# address :string(255) not null
-# kind :string(32) not null
+# kind :integer not null
# nsig :integer
# gateway :string(20) default(""), not null
# settings :string(1000) default({}), not null
@@ -63,3 +101,10 @@ def wallet_url
# created_at :datetime not null
# updated_at :datetime not null
#
+# Indexes
+#
+# index_wallets_on_currency_id (currency_id)
+# index_wallets_on_kind (kind)
+# index_wallets_on_kind_and_currency_id_and_status (kind,currency_id,status)
+# index_wallets_on_status (status)
+#
diff --git a/app/services/wallet_service.rb b/app/services/wallet_service.rb
index e1d9d47230..45f918d0d2 100644
--- a/app/services/wallet_service.rb
+++ b/app/services/wallet_service.rb
@@ -46,13 +46,48 @@ def create_address!
protected
- def destination_wallet(deposit)
- # TODO: Dynamicly check wallet balance and select destination wallet.
- # For keeping it simple we will collect all deposits to hot wallet.
+ def spread_deposit(deposit)
+ left_amount = deposit.amount
+ collection_spread = Hash.new(0)
+ currency = deposit.currency
+ destination_wallets(deposit).each do |wallet|
+ break if left_amount == 0
+ blockchain_client = BlockchainClient[Blockchain.find_by_key(wallet.blockchain_key).key]
+ wallet_balance = blockchain_client.load_balance!(wallet.address, deposit.currency)
+ amount_for_wallet = [wallet.max_balance - wallet_balance, left_amount].min
+ # If free amount for current wallet too small we will not able to collect it.
+ # So we try to collect it to next wallets.
+ next if amount_for_wallet < currency.min_collection_amount
+ left_amount -= amount_for_wallet
+ # If amount left is too small we will not able to collect it.
+ # So we collect everything to current wallet.
+ if left_amount < currency.min_collection_amount
+ amount_for_wallet += left_amount
+ left_amount = 0
+ end
+ collection_spread[wallet.address] = amount_for_wallet if amount_for_wallet > 0
+ rescue => e
+ # If have exception move to next wallet
+ report_exception(e)
+ end
+ # If deposit doesn't fit to any wallet collect it to last wallet.
+ # Last wallet is considered to be the most secure.
+ if left_amount > 0
+ collection_spread[destination_wallets(deposit).last.address] += left_amount
+ left_amount = 0
+ end
+ unless collection_spread.values.sum == deposit.amount
+ raise Error, "Deposit spread failed deposit.amount != collection_spread.values.sum"
+ end
+ collection_spread
+ end
+
+ def destination_wallets(deposit)
Wallet
.active
.withdraw
- .find_by(currency_id: deposit.currency_id, kind: :hot)
+ .ordered
+ .where(currency_id: deposit.currency_id)
end
end
end
diff --git a/app/services/wallet_service/bitcoind.rb b/app/services/wallet_service/bitcoind.rb
index c670c811bb..0ae002381e 100644
--- a/app/services/wallet_service/bitcoind.rb
+++ b/app/services/wallet_service/bitcoind.rb
@@ -9,18 +9,19 @@ def create_address(options = {})
end
def collect_deposit!(deposit, options={})
- destination_address = destination_wallet(deposit).address
pa = deposit.account.payment_address
- # this will automatically deduct fee from amount
+ # This will automatically deduct fee from amount so we can withdraw exact amount.
options = options.merge( subtract_fee: true )
-
- client.create_withdrawal!(
- { address: pa.address },
- { address: destination_address },
- deposit.amount,
- options
- )
+ spread_hash = spread_deposit(deposit)
+ spread_hash.map do |address, amount|
+ client.create_withdrawal!(
+ { address: pa.address },
+ { address: address},
+ amount,
+ options
+ )
+ end
end
def build_withdrawal!(withdraw, options = {})
diff --git a/app/services/wallet_service/geth.rb b/app/services/wallet_service/geth.rb
index 01a13701ac..4b5d1dfb9c 100644
--- a/app/services/wallet_service/geth.rb
+++ b/app/services/wallet_service/geth.rb
@@ -13,11 +13,11 @@ def create_address(options = {})
end
def collect_deposit!(deposit, options={})
- destination_address = destination_wallet(deposit).address
+ destination_wallets = destination_wallets(deposit)
if deposit.currency.code.eth?
- collect_eth_deposit!(deposit, destination_address, options)
+ collect_eth_deposit!(deposit, destination_wallets, options)
else
- collect_erc20_deposit!(deposit, destination_address, options)
+ collect_erc20_deposit!(deposit, destination_wallets, options)
end
end
@@ -30,12 +30,12 @@ def build_withdrawal!(withdraw)
end
def deposit_collection_fees(deposit, value=DEFAULT_ERC20_FEE_VALUE, options={})
- fees_wallet = erc20_fee_wallet
+ fee_wallet = erc20_fee_wallet
destination_address = deposit.account.payment_address.address
options = DEFAULT_ETH_FEE.merge options
client.create_eth_withdrawal!(
- { address: fees_wallet.address, secret: fees_wallet.secret },
+ { address: fee_wallet.address, secret: fee_wallet.secret },
{ address: destination_address },
value,
options
@@ -47,35 +47,38 @@ def deposit_collection_fees(deposit, value=DEFAULT_ERC20_FEE_VALUE, options={})
def erc20_fee_wallet
Wallet
.active
- .withdraw
.find_by(currency_id: :eth, kind: :fee)
end
- def collect_eth_deposit!(deposit, destination_address, options={})
+ def collect_eth_deposit!(deposit, destination_wallets, options={})
# Default values for Ethereum tx fees.
options = DEFAULT_ETH_FEE.merge options
-
- # We can't collect all funds we need to subtract gas fees.
- amount = deposit.amount_to_base_unit! - options[:gas_limit] * options[:gas_price]
pa = deposit.account.payment_address
- client.create_eth_withdrawal!(
- { address: pa.address, secret: pa.secret },
- { address: destination_address },
- amount,
- options
- )
+ spread_hash = spread_deposit(deposit)
+ spread_hash.map do |address, amount|
+ spread_amount = amount * deposit.currency.base_factor - options[:gas_limit] * options[:gas_price]
+ client.create_eth_withdrawal!(
+ { address: pa.address, secret: pa.secret },
+ { address: address},
+ spread_amount.to_i,
+ options
+ )
+ end
end
- def collect_erc20_deposit!(deposit, destination_address, options={})
+ def collect_erc20_deposit!(deposit, destination_wallets, options={})
pa = deposit.account.payment_address
- client.create_erc20_withdrawal!(
- { address: pa.address, secret: pa.secret },
- { address: destination_address },
- deposit.amount_to_base_unit!,
- options.merge(contract_address: deposit.currency.erc20_contract_address )
- )
-
+ spread_hash = spread_deposit(deposit)
+ spread_hash.map do |address, amount|
+ spread_amount = amount * deposit.currency.base_factor
+ client.create_erc20_withdrawal!(
+ { address: pa.address, secret: pa.secret },
+ { address: address},
+ spread_amount.to_i,
+ options.merge( contract_address: deposit.currency.erc20_contract_address )
+ )
+ end
end
def build_eth_withdrawal!(withdraw)
diff --git a/app/services/wallet_service/rippled.rb b/app/services/wallet_service/rippled.rb
index 6abd608305..4972d4b7d3 100644
--- a/app/services/wallet_service/rippled.rb
+++ b/app/services/wallet_service/rippled.rb
@@ -11,15 +11,16 @@ def create_address(options = {})
end
def collect_deposit!(deposit, options={})
- destination_address = destination_wallet(deposit).address
pa = deposit.account.payment_address
-
- client.create_withdrawal!(
- { address: pa.address, secret: pa.secret },
- { address: destination_address },
- deposit.amount,
- options
- )
+ spread_hash = spread_deposit(deposit)
+ spread_hash.map do |address, amount|
+ client.create_withdrawal!(
+ { address: pa.address, secret: pa.secret },
+ { address: address },
+ amount,
+ options
+ )
+ end
end
def build_withdrawal!(withdraw, options = {})
diff --git a/app/views/admin/currencies/show.html.erb b/app/views/admin/currencies/show.html.erb
index b731e362d8..4e50a06f1e 100644
--- a/app/views/admin/currencies/show.html.erb
+++ b/app/views/admin/currencies/show.html.erb
@@ -33,9 +33,12 @@
Deposit fee (fiat only)
<%= f.text_field :deposit_fee, class: 'form-control' %>
- Min Deposit amount
+ Min deposit amount
<%= f.text_field :min_deposit_amount, class: 'form-control' %>
+ Min collection amount
+ <%= f.text_field :min_collection_amount, class: 'form-control' %>
+
<% if @currency.coin? || @currency.new_record? %>
<% if @currency.new_record? || @currency.erc20_contract_address? %>
ERC20 contract address
diff --git a/app/views/admin/wallets/show.html.erb b/app/views/admin/wallets/show.html.erb
index 38ffa378a9..8345936032 100644
--- a/app/views/admin/wallets/show.html.erb
+++ b/app/views/admin/wallets/show.html.erb
@@ -25,7 +25,7 @@
<%= f.text_field :address, class: 'form-control mb-3' %>
- <%= f.select :kind, Wallet::KIND.map { |k| [k.capitalize, k] }, {selected: @wallet.kind}, {class: 'form-control mb-3'} %>
+ <%= f.select :kind, Wallet::kinds(keys: true).map { |k| [k.capitalize, k] }, {selected: @wallet.kind}, {class: 'form-control mb-3'} %>
diff --git a/config/templates/seed/currencies.yml.erb b/config/templates/seed/currencies.yml.erb
index 2f3b26f469..5e2700fc76 100644
--- a/config/templates/seed/currencies.yml.erb
+++ b/config/templates/seed/currencies.yml.erb
@@ -1,121 +1,129 @@
<% if ENV['CURRENCIES_CONFIG'] %>
<%= File.read(ENV['CURRENCIES_CONFIG']) %>
<% else %>
-- id: usd
- symbol: '$'
- type: fiat
- precision: 2
- base_factor: 1
- enabled: true
- quick_withdraw_limit: 1000
- min_deposit_amount: 0
- deposit_fee: 0
- withdraw_fee: 0
+- id: usd
+ symbol: '$'
+ type: fiat
+ precision: 2
+ base_factor: 1
+ enabled: true
+ quick_withdraw_limit: 1000
+ min_deposit_amount: 0
+ min_collection_amount: 0
+ deposit_fee: 0
+ withdraw_fee: 0
-- id: btc
- blockchain_key: btc-testnet
- symbol: '฿'
- type: coin
- precision: 8
- base_factor: 100_000_000
- enabled: true
- quick_withdraw_limit: 0.1
+- id: btc
+ blockchain_key: btc-testnet
+ symbol: '฿'
+ type: coin
+ precision: 8
+ base_factor: 100_000_000
+ enabled: true
+ quick_withdraw_limit: 0.1
# Deposits with less amount are skipped during blockchain synchronization.
# We advise to set value 10 times bigger than the network fee to prevent losses.
- min_deposit_amount: 0.0000356
- deposit_fee: 0
- withdraw_fee: 0
- options: {}
+ min_deposit_amount: 0.0000356
+ min_collection_amount: 0.0000356
+ deposit_fee: 0
+ withdraw_fee: 0
+ options: {}
-- id: xrp
- blockchain_key: xrp-testnet
- symbol: 'ꭆ'
- type: coin
- precision: 8
- base_factor: 1_000_000
- enabled: true
- quick_withdraw_limit: 1000
+- id: xrp
+ blockchain_key: xrp-testnet
+ symbol: 'ꭆ'
+ type: coin
+ precision: 8
+ base_factor: 1_000_000
+ enabled: true
+ quick_withdraw_limit: 1000
# Deposits with less amount are skipped during blockchain synchronization.
# We advise to set value 10 times bigger than the network fee to prevent losses.
- min_deposit_amount: 0.00012
- deposit_fee: 0
- withdraw_fee: 0
- options: {}
+ min_deposit_amount: 0.00012
+ min_collection_amount: 0.00012
+ deposit_fee: 0
+ withdraw_fee: 0
+ options: {}
-- id: bch
- blockchain_key: bch-testnet
- symbol: '฿'
- type: coin
- precision: 8
- base_factor: 100_000_000
- enabled: true
- quick_withdraw_limit: 1
+- id: bch
+ blockchain_key: bch-testnet
+ symbol: '฿'
+ type: coin
+ precision: 8
+ base_factor: 100_000_000
+ enabled: true
+ quick_withdraw_limit: 1
# Deposits with less amount are skipped during blockchain synchronization.
# We advise to set value 10 times bigger than the network fee to prevent losses.
- min_deposit_amount: 0.0000748
- deposit_fee: 0
- withdraw_fee: 0
- options: {}
+ min_deposit_amount: 0.0000748
+ min_collection_amount: 0.0000748
+ deposit_fee: 0
+ withdraw_fee: 0
+ options: {}
-- id: ltc
- blockchain_key: ltc-testnet
- symbol: 'Ł'
- type: coin
- precision: 8
- base_factor: 100_000_000
- enabled: true
- quick_withdraw_limit: 5
+- id: ltc
+ blockchain_key: ltc-testnet
+ symbol: 'Ł'
+ type: coin
+ precision: 8
+ base_factor: 100_000_000
+ enabled: true
+ quick_withdraw_limit: 5
# Deposits with less amount are skipped during blockchain synchronization.
# We advise to set value 10 times bigger than the network fee to prevent losses.
- min_deposit_amount: 0.0004488
- deposit_fee: 0
- withdraw_fee: 0
- options: {}
+ min_deposit_amount: 0.0004488
+ min_collection_amount: 0.0004488
+ deposit_fee: 0
+ withdraw_fee: 0
+ options: {}
-- id: dash
- blockchain_key: dash-testnet
- symbol: 'Đ'
- type: coin
- precision: 8
- base_factor: 100_000_000
- enabled: true
- quick_withdraw_limit: 2
+- id: dash
+ blockchain_key: dash-testnet
+ symbol: 'Đ'
+ type: coin
+ precision: 8
+ base_factor: 100_000_000
+ enabled: true
+ quick_withdraw_limit: 2
# Deposits with less amount are skipped during blockchain synchronization.
# We advise to set value 10 times bigger than the network fee to prevent losses.
- min_deposit_amount: 0.0000226
- deposit_fee: 0
- withdraw_fee: 0
- options: {}
+ min_deposit_amount: 0.0000226
+ min_collection_amount: 0.0000226
+ deposit_fee: 0
+ withdraw_fee: 0
+ options: {}
-- id: eth
- blockchain_key: eth-rinkeby
- symbol: 'Ξ'
- type: coin
- precision: 8
- base_factor: 1_000_000_000_000_000_000
- enabled: true
- quick_withdraw_limit: 2
+- id: eth
+ blockchain_key: eth-rinkeby
+ symbol: 'Ξ'
+ type: coin
+ precision: 8
+ base_factor: 1_000_000_000_000_000_000
+ enabled: true
+ quick_withdraw_limit: 2
# Deposits with less amount are skipped during blockchain synchronization.
# We advise to set value 10 times bigger than the network fee to prevent losses.
- min_deposit_amount: 0.00021
- deposit_fee: 0
- withdraw_fee: 0
- options: {}
+ min_deposit_amount: 0.00021
+ min_collection_amount: 0.00021
+ deposit_fee: 0
+ withdraw_fee: 0
+ options: {}
-- id: trst
- blockchain_key: eth-rinkeby
- symbol: 'Ξ'
- type: coin
- precision: 8
- base_factor: 1_000_000 # IMPORTANT: Don't forget to update this variable according
- enabled: true # to your ERC20-based currency requirements
- quick_withdraw_limit: 3000 # (usually can be found on the official website).
+- id: trst
+ blockchain_key: eth-rinkeby
+ symbol: 'Ξ'
+ type: coin
+ precision: 8
+ base_factor: 1_000_000 # IMPORTANT: Don't forget to update this variable according
+ enabled: true # to your ERC20-based currency requirements
+ quick_withdraw_limit: 3000 # (usually can be found on the official website).
# Deposits with less amount are skipped during blockchain synchronization.
# We advise to set value 10 times bigger than the network fee to prevent losses.
# NOTE: Network fee is paid in ETH but min_deposit_amount is in TRST.
- min_deposit_amount: 1.7
- deposit_fee: 0
- withdraw_fee: 0
+ min_deposit_amount: 0.00021
+ min_collection_amount: 0.00021
+ deposit_fee: 0
+ withdraw_fee: 0
options:
#
# ERC20 configuration.
diff --git a/db/migrate/20181017114624_enumerize_wallet_kind.rb b/db/migrate/20181017114624_enumerize_wallet_kind.rb
new file mode 100644
index 0000000000..d14ef62f10
--- /dev/null
+++ b/db/migrate/20181017114624_enumerize_wallet_kind.rb
@@ -0,0 +1,15 @@
+class EnumerizeWalletKind < ActiveRecord::Migration
+ def change
+ id_kind_hash = Wallet.pluck(:id, :kind).to_h
+
+ remove_column :wallets, :kind
+ add_column :wallets, :kind, :integer, limit: 4, null: false, after: :address
+
+ Wallet.find_each { |w| w.update!(kind: id_kind_hash[w.id]) }
+
+ add_index :wallets, :status
+ add_index :wallets, :kind
+ add_index :wallets, :currency_id
+ add_index :wallets, %i[kind currency_id status]
+ end
+end
diff --git a/db/migrate/20181126101312_add_min_collection_amount_to_currencies.rb b/db/migrate/20181126101312_add_min_collection_amount_to_currencies.rb
new file mode 100644
index 0000000000..c79408d4db
--- /dev/null
+++ b/db/migrate/20181126101312_add_min_collection_amount_to_currencies.rb
@@ -0,0 +1,5 @@
+class AddMinCollectionAmountToCurrencies < ActiveRecord::Migration
+ def change
+ add_column :currencies, :min_collection_amount, :decimal, precision: 32, scale: 16, default: 0.0, null: false, after: :min_deposit_amount
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 46be0fca8d..2022933c04 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20181004114428) do
+ActiveRecord::Schema.define(version: 20181126101312) do
create_table "accounts", force: :cascade do |t|
t.integer "member_id", limit: 4, null: false
@@ -57,20 +57,21 @@
add_index "blockchains", ["status"], name: "index_blockchains_on_status", using: :btree
create_table "currencies", force: :cascade do |t|
- t.string "blockchain_key", limit: 32
- t.string "symbol", limit: 1, null: false
- t.string "type", limit: 30, default: "coin", null: false
- t.decimal "deposit_fee", precision: 32, scale: 16, default: 0.0, null: false
- t.decimal "quick_withdraw_limit", precision: 32, scale: 16, default: 0.0, null: false
- t.decimal "min_deposit_amount", precision: 32, scale: 16, default: 0.0, null: false
- t.decimal "withdraw_fee", precision: 32, scale: 16, default: 0.0, null: false
- t.string "options", limit: 1000, default: "{}", null: false
- t.boolean "enabled", default: true, null: false
- t.integer "base_factor", limit: 8, default: 1, null: false
- t.integer "precision", limit: 1, default: 8, null: false
- t.string "icon_url", limit: 255
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.string "blockchain_key", limit: 32
+ t.string "symbol", limit: 1, null: false
+ t.string "type", limit: 30, default: "coin", null: false
+ t.decimal "deposit_fee", precision: 32, scale: 16, default: 0.0, null: false
+ t.decimal "quick_withdraw_limit", precision: 32, scale: 16, default: 0.0, null: false
+ t.decimal "min_deposit_amount", precision: 32, scale: 16, default: 0.0, null: false
+ t.decimal "min_collection_amount", precision: 32, scale: 16, default: 0.0, null: false
+ t.decimal "withdraw_fee", precision: 32, scale: 16, default: 0.0, null: false
+ t.string "options", limit: 1000, default: "{}", null: false
+ t.boolean "enabled", default: true, null: false
+ t.integer "base_factor", limit: 8, default: 1, null: false
+ t.integer "precision", limit: 1, default: 8, null: false
+ t.string "icon_url", limit: 255
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
add_index "currencies", ["enabled"], name: "index_currencies_on_enabled", using: :btree
@@ -197,7 +198,7 @@
t.string "currency_id", limit: 10
t.string "name", limit: 64
t.string "address", limit: 255, null: false
- t.string "kind", limit: 32, null: false
+ t.integer "kind", limit: 4, null: false
t.integer "nsig", limit: 4
t.string "gateway", limit: 20, default: "", null: false
t.string "settings", limit: 1000, default: "{}", null: false
@@ -208,6 +209,11 @@
t.datetime "updated_at", null: false
end
+ add_index "wallets", ["currency_id"], name: "index_wallets_on_currency_id", using: :btree
+ add_index "wallets", ["kind", "currency_id", "status"], name: "index_wallets_on_kind_and_currency_id_and_status", using: :btree
+ add_index "wallets", ["kind"], name: "index_wallets_on_kind", using: :btree
+ add_index "wallets", ["status"], name: "index_wallets_on_status", using: :btree
+
create_table "withdraws", force: :cascade do |t|
t.integer "account_id", limit: 4, null: false
t.integer "member_id", limit: 4, null: false
diff --git a/lib/blockchain_client/bitcoin.rb b/lib/blockchain_client/bitcoin.rb
index 6b0957c440..d0700e03eb 100644
--- a/lib/blockchain_client/bitcoin.rb
+++ b/lib/blockchain_client/bitcoin.rb
@@ -13,6 +13,12 @@ def endpoint
@json_rpc_endpoint
end
+ def load_balance!(address, currency)
+ json_rpc(:listunspent, [1, 10_000_000, [address]])
+ .fetch('result')
+ .sum { |vout| vout['amount'] }
+ end
+
def load_deposit!(txid)
json_rpc(:gettransaction, [normalize_txid(txid)]).fetch('result').yield_self { |tx| build_standalone_deposit(tx) }
end
diff --git a/lib/blockchain_client/ethereum.rb b/lib/blockchain_client/ethereum.rb
index 0a18f34a23..9ec2a6dc44 100644
--- a/lib/blockchain_client/ethereum.rb
+++ b/lib/blockchain_client/ethereum.rb
@@ -22,6 +22,27 @@ def get_block(height)
json_rpc(:eth_getBlockByNumber, ["0x#{current_block.to_s(16)}", true]).fetch('result')
end
+ def load_balance!(address, currency)
+ if currency.code.eth?
+ json_rpc(:eth_getBalance, [normalize_address(address), 'latest'])
+ .fetch('result')
+ .hex
+ .to_d
+ .yield_self { |amount| convert_from_base_unit(amount, currency) }
+ else
+ load_balance_of_address(address, currency)
+ end
+ end
+
+ def load_balance_of_address(address, currency)
+ data = abi_encode('balanceOf(address)', normalize_address(address))
+ json_rpc(:eth_call, [{ to: contract_address(currency), data: data }, 'latest'])
+ .fetch('result')
+ .hex
+ .to_d
+ .yield_self { |amount| convert_from_base_unit(amount, currency) }
+ end
+
def to_address(tx)
if tx.has_key?('logs')
get_erc20_addresses(tx)
@@ -75,6 +96,10 @@ def case_sensitive?
false
end
+ def convert_from_base_unit(value, currency)
+ value.to_d / currency.base_factor
+ end
+
protected
def connection
@@ -172,7 +197,7 @@ def build_erc20_transaction(tx, current_block_json, address, currency)
entries: entries.compact }
end
- def contract_address
+ def contract_address(currency)
normalize_address(currency.erc20_contract_address)
end
end
diff --git a/lib/blockchain_client/ripple.rb b/lib/blockchain_client/ripple.rb
index 6e3674a70a..9d63073817 100644
--- a/lib/blockchain_client/ripple.rb
+++ b/lib/blockchain_client/ripple.rb
@@ -21,6 +21,18 @@ def from_address(tx)
normalize_address(tx['Account'])
end
+ def load_balance!(address, currency)
+ json_rpc(:account_info, [account: normalize_address(address), ledger_index: 'validated', strict: true])
+ .fetch('result')
+ .fetch('account_data')
+ .fetch('Balance')
+ .to_d
+ .yield_self { |amount| convert_from_base_unit(amount, currency) }
+ rescue => e
+ report_exception_to_screen(e)
+ 0.0
+ end
+
def build_transaction(tx:, currency:)
{
id: normalize_txid(tx.fetch('hash')),
diff --git a/lib/wallet_client/geth.rb b/lib/wallet_client/geth.rb
index 8fa3684e46..e0897a09f3 100644
--- a/lib/wallet_client/geth.rb
+++ b/lib/wallet_client/geth.rb
@@ -82,6 +82,13 @@ def normalize_txid(txid)
txid.downcase
end
+ def load_balance!(address)
+ json_rpc(:eth_getBalance, [normalize_address(address), 'latest']).fetch('result').hex.to_d
+ rescue => e
+ report_exception_to_screen(e)
+ 0.0
+ end
+
protected
def abi_encode(method, *args)
diff --git a/lib/wallet_client/rippled.rb b/lib/wallet_client/rippled.rb
index 8ab7fbcb2b..59584b0cea 100644
--- a/lib/wallet_client/rippled.rb
+++ b/lib/wallet_client/rippled.rb
@@ -112,7 +112,19 @@ def calculate_current_fee
end
end
- protected
+ def load_balance!(address)
+ json_rpc(:account_info, [account: normalize_address(address), ledger_index: 'validated', strict: true])
+ .fetch('result')
+ .fetch('account_data')
+ .fetch('Balance')
+ .to_d
+ .yield_self { |amount| convert_from_base_unit(amount) }
+ rescue => e
+ report_exception_to_screen(e)
+ 0.0
+ end
+
+ protected
def connection
Faraday.new(@json_rpc_endpoint).tap do |connection|
diff --git a/spec/factories/blockchains.rb b/spec/factories/blockchains.rb
index 132260c28f..c15fbe3309 100644
--- a/spec/factories/blockchains.rb
+++ b/spec/factories/blockchains.rb
@@ -7,7 +7,7 @@
key { 'xrp-testnet' }
name { 'Ripple Testnet' }
client { 'ripple' }
- server { 'https://s.altnet.rippletest.net:51234' }
+ server { 'http://127.0.0.1:5005' }
height { 40280751 }
min_confirmations { 1 }
explorer_address { '' }
@@ -46,7 +46,7 @@
server { 'http://127.0.0.1:18332' }
height { 1350000 }
min_confirmations { 1 }
- explorer_address { ' https://blockchain.info/address/#{address}' }
+ explorer_address { 'https://blockchain.info/address/#{address}' }
explorer_transaction { 'https://blockchain.info/tx/#{txid}' }
status { 'active' }
end
diff --git a/spec/factories/deposits.rb b/spec/factories/deposits.rb
index b14b84e104..52bec42295 100644
--- a/spec/factories/deposits.rb
+++ b/spec/factories/deposits.rb
@@ -6,15 +6,74 @@
member { create(:member, :level_3) }
amount { Kernel.rand(100..10_000).to_d }
- factory :deposit_btc, class: 'Deposits::Coin' do
+ factory :deposit_btc, class: Deposits::Coin do
currency { Currency.find(:btc) }
address { Faker::Bitcoin.address }
txid { Faker::Lorem.characters(64) }
txout { 0 }
end
- factory :deposit_usd, class: 'Deposits::Fiat' do
+ factory :deposit_usd, class: Deposits::Fiat do
currency { Currency.find(:usd) }
end
+
+ trait :deposit_btc do
+ type { Deposits::Coin }
+ currency { Currency.find(:btc) }
+ address { Faker::Bitcoin.address }
+ txid { Faker::Lorem.characters(64) }
+ txout { 0 }
+ end
+
+ trait :deposit_bch do
+ type { Deposits::Coin }
+ currency { Currency.find(:bch) }
+ address { Faker::Bitcoin.address }
+ txid { Faker::Lorem.characters(64) }
+ txout { 0 }
+ end
+
+ trait :deposit_dash do
+ type { Deposits::Coin }
+ currency { Currency.find(:dash) }
+ address { Faker::Bitcoin.address }
+ txid { Faker::Lorem.characters(64) }
+ txout { 0 }
+ end
+
+ trait :deposit_ltc do
+ type { Deposits::Coin }
+ currency { Currency.find(:ltc) }
+ address { Faker::Bitcoin.address }
+ txid { Faker::Lorem.characters(64) }
+ txout { 0 }
+ end
+
+ trait :deposit_eth do
+ type { Deposits::Coin }
+ currency { Currency.find(:eth) }
+ member { create(:member, :level_3, :barong) }
+ address { Faker::Bitcoin.address }
+ txid { Faker::Lorem.characters(64) }
+ txout { 0 }
+ end
+
+ trait :deposit_trst do
+ type { Deposits::Coin }
+ currency { Currency.find(:trst) }
+ member { create(:member, :level_3, :barong) }
+ address { Faker::Bitcoin.address }
+ txid { Faker::Lorem.characters(64) }
+ txout { 0 }
+ end
+
+ trait :deposit_xrp do
+ type { Deposits::Coin }
+ currency { Currency.find(:xrp) }
+ member { create(:member, :level_3, :barong) }
+ address { Faker::Bitcoin.address }
+ txid { Faker::Lorem.characters(64) }
+ txout { 0 }
+ end
end
end
diff --git a/spec/factories/wallet.rb b/spec/factories/wallet.rb
index 19fe6accdb..8211a999c8 100644
--- a/spec/factories/wallet.rb
+++ b/spec/factories/wallet.rb
@@ -3,11 +3,26 @@
FactoryBot.define do
factory :wallet do
+
+ trait :eth_deposit do
+ currency_id { 'eth' }
+ blockchain_key { 'eth-rinkeby' }
+ name { 'Ethereum Deposit Wallet' }
+ address { '0x828058628DF254Ebf252e0b1b5393D1DED91E369' }
+ kind { 'deposit' }
+ max_balance { 0.0 }
+ nsig { 2 }
+ status { 'active' }
+ gateway { 'geth' }
+ uri { 'http://127.0.0.1:8545' }
+ secret { 'changeme' }
+ end
+
trait :eth_hot do
currency_id { 'eth' }
blockchain_key { 'eth-rinkeby' }
name { 'Ethereum Hot Wallet' }
- address { '249048804499541338815845805798634312140346616732' }
+ address { '0xb6a61c43DAe37c0890936D720DC42b5CBda990F9' }
kind { 'hot' }
max_balance { 100.0 }
nsig { 2 }
@@ -17,12 +32,13 @@
secret { 'changeme' }
end
- trait 'eth_warm' do
+ trait :eth_warm do
currency_id { 'eth' }
blockchain_key { 'eth-rinkeby' }
name { 'Ethereum Warm Wallet' }
address { '0x2b9fBC10EbAeEc28a8Fc10069C0BC29E45eBEB9C' }
kind { 'warm' }
+ max_balance { 1000.0 }
nsig { 2 }
status { 'active' }
gateway { 'geth' }
@@ -30,16 +46,59 @@
secret { 'changeme' }
end
- trait :btc_hot do
- currency_id { 'btc' }
- blockchain_key { 'btc-testnet' }
- name { 'Bitcoin Hot Wallet' }
+ trait :eth_cold do
+ currency_id { 'eth' }
+ blockchain_key { 'eth-rinkeby' }
+ name { 'Ethereum Cold Wallet' }
address { '0x2b9fBC10EbAeEc28a8Fc10069C0BC29E45eBEB9C' }
+ kind { 'cold' }
+ max_balance { 1000.0 }
+ nsig { 2 }
+ status { 'active' }
+ gateway { 'geth' }
+ uri { 'http://127.0.0.1:8545' }
+ secret { 'changeme' }
+ end
+
+ trait :eth_fee do
+ currency_id { 'eth' }
+ blockchain_key { 'eth-rinkeby' }
+ name { 'Ethereum Fee Wallet' }
+ address { '0x45a31b15a2ab8a8477375b36b6f5a0c63733dce8' }
+ kind { 'fee' }
+ max_balance { 1000.0 }
+ nsig { 2 }
+ status { 'active' }
+ gateway { 'geth' }
+ uri { 'http://127.0.0.1:8545' }
+ secret { 'changeme' }
+ end
+
+ trait :trst_deposit do
+ currency_id { 'trst' }
+ blockchain_key { 'eth-rinkeby' }
+ name { 'Trust Coin Deposit Wallet' }
+ address { '0x828058628DF254Ebf252e0b1b5393D1DED91E369' }
+ kind { 'deposit' }
+ max_balance { 0.0 }
+ nsig { 2 }
+ status { 'active' }
+ gateway { 'geth' }
+ uri { 'http://127.0.0.1:8545' }
+ secret { 'changeme' }
+ end
+
+ trait :trst_hot do
+ currency_id { 'trst' }
+ blockchain_key { 'eth-rinkeby' }
+ name { 'Trust Coin Hot Wallet' }
+ address { '0xb6a61c43DAe37c0890936D720DC42b5CBda990F9' }
kind { 'hot' }
+ max_balance { 100.0 }
nsig { 2 }
status { 'active' }
- gateway { 'bitcoind' }
- uri { 'http://127.0.0.1:18332' }
+ gateway { 'geth' }
+ uri { 'http://127.0.0.1:8545' }
secret { 'changeme' }
end
@@ -47,8 +106,9 @@
currency_id { 'btc' }
blockchain_key { 'btc-testnet' }
name { 'Bitcoin Deposit Wallet' }
- address { '0x2b9fBC10EbAeEc28a8Fc10069C0BC29E45eBEB9C' }
+ address { '3DX3Ak4751ckkoTFbYSY9FEQ6B7mJ4furT' }
kind { 'deposit' }
+ max_balance { 0.0 }
nsig { 2 }
status { 'active' }
gateway { 'bitcoind' }
@@ -56,17 +116,128 @@
secret { 'changeme' }
end
+ trait :btc_hot do
+ currency_id { 'btc' }
+ blockchain_key { 'btc-testnet' }
+ name { 'Bitcoin Hot Wallet' }
+ address { '3NwYr8JxjHG2MBkgdBiHCxStSWDzyjS5U8' }
+ kind { 'hot' }
+ max_balance { 500.0 }
+ nsig { 2 }
+ status { 'active' }
+ gateway { 'bitcoind' }
+ uri { 'http://127.0.0.1:18332' }
+ secret { 'changeme' }
+ end
+
+ trait :xrp_deposit do
+ currency_id { 'xrp' }
+ blockchain_key { 'xrp-testnet' }
+ name { 'Ripple Deposit Wallet' }
+ address { 'rN3J1yMz2PCGievtS2XTEgkrmdHiJgzb5Y?dt=917590223' }
+ kind { 'deposit' }
+ max_balance { 0.0 }
+ nsig { 2 }
+ status { 'active' }
+ gateway { 'rippled' }
+ uri { 'http://127.0.0.1:5005' }
+ secret { 'changeme' }
+ end
+
trait :xrp_hot do
currency_id { 'xrp' }
blockchain_key { 'xrp-testnet' }
name { 'Ripple Hot Wallet' }
address { 'r4kpJtnx4goLYXoRdi7mbkRpZ9Xpx2RyPN' }
kind { 'hot' }
+ max_balance { 100.0 }
nsig { 2 }
status { 'active' }
gateway { 'rippled' }
uri { 'http://127.0.0.1:5005' }
secret { 'changeme' }
end
+
+ trait :bch_deposit do
+ currency_id { 'bch' }
+ blockchain_key { 'bch-testnet' }
+ name { 'Bitcoincash Deposit Wallet' }
+ address { 'mqF8Bsv2rHThg4cVDgwYcnEYNDWKi4spD7' }
+ kind { 'deposit' }
+ max_balance { 0.0 }
+ nsig { 1 }
+ status { 'active' }
+ gateway { 'bitcoincashd' }
+ uri { 'http://127.0.0.1:18332' }
+ secret { 'changeme' }
+ end
+
+ trait :bch_hot do
+ currency_id { 'bch' }
+ blockchain_key { 'bch-testnet' }
+ name { 'Bitcoincash Hot Wallet' }
+ address { 'n2stP7w1DpSh7N1PzJh7eGjgCk3eTF3DMC' }
+ kind { 'hot' }
+ max_balance { 100.0 }
+ nsig { 1 }
+ status { 'active' }
+ gateway { 'bitcoincashd' }
+ uri { 'http://127.0.0.1:18332' }
+ secret { 'changeme' }
+ end
+
+ trait :dash_deposit do
+ currency_id { 'dash' }
+ blockchain_key { 'dash-testnet' }
+ name { 'Dash Deposit Wallet' }
+ address { 'yVcZM6oUjfwrREm2CDb9G8BMHwwm5o5UsL' }
+ kind { 'deposit' }
+ max_balance { 0.0 }
+ nsig { 1 }
+ status { 'active' }
+ gateway { 'dashd' }
+ uri { 'http://127.0.0.1:19998' }
+ secret { 'changeme' }
+ end
+
+ trait :dash_hot do
+ currency_id { 'dash' }
+ blockchain_key { 'dash-testnet' }
+ name { 'Dash Hot Wallet' }
+ address { 'yborj44WhothaX6vwoMhRMjkq1xELhAWQp' }
+ kind { 'hot' }
+ max_balance { 100.0 }
+ nsig { 1 }
+ status { 'active' }
+ gateway { 'dashd' }
+ uri { 'http://127.0.0.1:19998' }
+ secret { 'changeme' }
+ end
+
+ trait :ltc_deposit do
+ currency_id { 'ltc' }
+ blockchain_key { 'ltc-testnet' }
+ name { 'Litecoin Deposit Wallet' }
+ address { 'QcM2zjgbaXbH26utxnNFge24A1BnDgSgcU' }
+ kind { 'deposit' }
+ max_balance { 0.0 }
+ nsig { 1 }
+ status { 'active' }
+ gateway { 'litecoind' }
+ uri { 'http://127.0.0.1:17732' }
+ end
+
+ trait :ltc_hot do
+ currency_id { 'ltc' }
+ blockchain_key { 'ltc-testnet' }
+ name { 'Litecoin Hot Wallet' }
+ address { 'Qc2BM7gp8mKgJPPxLAadLAHteNQwhFwwuf' }
+ kind { 'hot' }
+ max_balance { 100.0 }
+ nsig { 1 }
+ status { 'active' }
+ gateway { 'litecoind' }
+ uri { 'http://127.0.0.1:17732' }
+ end
end
end
diff --git a/spec/models/wallet_spec.rb b/spec/models/wallet_spec.rb
index cf94d205d0..926f5ef35f 100644
--- a/spec/models/wallet_spec.rb
+++ b/spec/models/wallet_spec.rb
@@ -4,7 +4,7 @@
describe Wallet do
context 'validations' do
- subject { build(:wallet, 'eth_warm') }
+ subject { build(:wallet, :eth_cold) }
it 'checks valid record' do
expect(subject).to be_valid
diff --git a/spec/resources/ripple-data/sign-transaction.json b/spec/resources/ripple-data/sign-transaction.json
new file mode 100644
index 0000000000..318428bebf
--- /dev/null
+++ b/spec/resources/ripple-data/sign-transaction.json
@@ -0,0 +1,22 @@
+{
+ "result": {
+ "status": "success",
+ "tx_blob": "1200002280000000240000016861D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402200E5C2DD81FDF0BE9AB2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F858081144B4E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754",
+ "tx_json": {
+ "Account": "rN3J1yMz2PCGievtS2XTEgkrmdHiJgzb5Y",
+ "Amount": {
+ "currency": "XRP",
+ "issuer": "rN3J1yMz2PCGievtS2XTEgkrmdHiJgzb5Y",
+ "value": "10000000"
+ },
+ "Destination": "r4kpJtnx4goLYXoRdi7mbkRpZ9Xpx2RyPN",
+ "Fee": "10000",
+ "Flags": 2147483648,
+ "Sequence": 360,
+ "SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
+ "TransactionType": "Payment",
+ "TxnSignature": "304402200E5C2DD81FDF0BE9AB2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F8580",
+ "hash": "4D5D90890F8D49519E4151938601EF3D0B30B16CD6A519D9C99102C9FA77F7E0"
+ }
+ }
+}
diff --git a/spec/resources/ripple-data/submit-transaction.json b/spec/resources/ripple-data/submit-transaction.json
new file mode 100644
index 0000000000..ceac2e1822
--- /dev/null
+++ b/spec/resources/ripple-data/submit-transaction.json
@@ -0,0 +1,25 @@
+{
+ "result": {
+ "engine_result": "tesSUCCESS",
+ "engine_result_code": 0,
+ "engine_result_message": "The transaction was applied. Only final in a validated ledger.",
+ "status": "success",
+ "tx_blob": "1200002280000000240000016961D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB74473045022100A7CCD11455E47547FF617D5BFC15D120D9053DFD0536B044F10CA3631CD609E502203B61DEE4AC027C5743A1B56AF568D1E2B8E79BB9E9E14744AC87F38375C3C2F181144B4E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754",
+ "tx_json": {
+ "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
+ "Amount": {
+ "currency": "USD",
+ "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
+ "value": "1"
+ },
+ "Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
+ "Fee": "10000",
+ "Flags": 2147483648,
+ "Sequence": 361,
+ "SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
+ "TransactionType": "Payment",
+ "TxnSignature": "3045022100A7CCD11455E47547FF617D5BFC15D120D9053DFD0536B044F10CA3631CD609E502203B61DEE4AC027C5743A1B56AF568D1E2B8E79BB9E9E14744AC87F38375C3C2F1",
+ "hash": "5B31A7518DC304D5327B4887CD1F7DC2C38D5F684170097020C7C9758B973847"
+ }
+ }
+}
diff --git a/spec/services/blockchain_service/bitcoin_spec.rb b/spec/services/blockchain_service/bitcoin_spec.rb
index cfaf9796cd..035af8a3e7 100644
--- a/spec/services/blockchain_service/bitcoin_spec.rb
+++ b/spec/services/blockchain_service/bitcoin_spec.rb
@@ -69,6 +69,8 @@ def request_block_body(block_hash)
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
+
block_data.each_with_index do |blk, index|
# stub get_block_hash
stub_request(:post, client.endpoint)
@@ -224,6 +226,8 @@ def request_block_body(block_hash)
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
+
block_data.each_with_index do |blk, index|
# stub get_block_hash
stub_request(:post, client.endpoint)
diff --git a/spec/services/blockchain_service/bitcoincash_spec.rb b/spec/services/blockchain_service/bitcoincash_spec.rb
index b7557b98a5..4dfc4c4eed 100644
--- a/spec/services/blockchain_service/bitcoincash_spec.rb
+++ b/spec/services/blockchain_service/bitcoincash_spec.rb
@@ -83,6 +83,8 @@ def request_raw_transaction_body(txid)
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
+
block_data.each_with_index do |blk, index|
# stub get_block_hash
stub_request(:post, client.endpoint)
@@ -166,6 +168,8 @@ def request_raw_transaction_body(txid)
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
+
block_data.each_with_index do |blk, index|
# stub get_block_hash
stub_request(:post, client.endpoint)
diff --git a/spec/services/blockchain_service/dash_spec.rb b/spec/services/blockchain_service/dash_spec.rb
index e168c393ce..8ce9629389 100644
--- a/spec/services/blockchain_service/dash_spec.rb
+++ b/spec/services/blockchain_service/dash_spec.rb
@@ -77,6 +77,7 @@ def request_raw_transaction_body(txid)
before do
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
block_data.each_with_index do |blk, index|
# stub get_block_hash
@@ -160,6 +161,7 @@ def request_raw_transaction_body(txid)
before do
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
block_data.each_with_index do |blk, index|
# stub get_block_hash
diff --git a/spec/services/blockchain_service/ethereum_spec.rb b/spec/services/blockchain_service/ethereum_spec.rb
index 9da84e9289..0995c29d40 100644
--- a/spec/services/blockchain_service/ethereum_spec.rb
+++ b/spec/services/blockchain_service/ethereum_spec.rb
@@ -74,6 +74,8 @@ def request_body(block_number, index)
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
client.class.any_instance.stubs(:rpc_call_id).returns(1)
+ Deposits::Coin.where(currency: currency).delete_all
+
block_data.each_with_index do |blk, index|
stub_request(:post, client.endpoint)
.with(body: request_body(blk['result']['number'],index))
@@ -151,6 +153,8 @@ def request_body(block_number, index)
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
client.class.any_instance.stubs(:rpc_call_id).returns(1)
+ Deposits::Coin.where(currency: currency).delete_all
+
block_data.each_with_index do |blk, index|
stub_request(:post, client.endpoint)
.with(body: request_body(blk['result']['number'], index))
diff --git a/spec/services/blockchain_service/litecoin_spec.rb b/spec/services/blockchain_service/litecoin_spec.rb
index f814d0f819..30f8dc347b 100644
--- a/spec/services/blockchain_service/litecoin_spec.rb
+++ b/spec/services/blockchain_service/litecoin_spec.rb
@@ -63,6 +63,7 @@ def request_block_body(block_hash)
before do
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
block_data.each_with_index do |blk, index|
# stub get_block_hash
@@ -143,6 +144,7 @@ def request_block_body(block_hash)
before do
# Mock requests and methods.
client.class.any_instance.stubs(:latest_block_number).returns(latest_block)
+ Deposits::Coin.where(currency: currency).delete_all
block_data.each_with_index do |blk, index|
# stub get_block_hash
diff --git a/spec/services/blockchain_service/ripple_spec.rb b/spec/services/blockchain_service/ripple_spec.rb
index 0fbfb86af5..0b867a69c3 100644
--- a/spec/services/blockchain_service/ripple_spec.rb
+++ b/spec/services/blockchain_service/ripple_spec.rb
@@ -116,6 +116,7 @@ def request_body(ledger_index, index)
end
before do
+ Deposits::Coin.where(currency: currency).delete_all
client.class.any_instance.stubs(:latest_block_number).returns(latest_block_number)
stub_request(:post, client.endpoint)
.with(body: request_body(start_ledger_index, 0))
diff --git a/spec/services/wallet_service/bitcoincashd_spec.rb b/spec/services/wallet_service/bitcoincashd_spec.rb
new file mode 100644
index 0000000000..5cc04e7eac
--- /dev/null
+++ b/spec/services/wallet_service/bitcoincashd_spec.rb
@@ -0,0 +1,101 @@
+# encoding: UTF-8
+# frozen_string_literal: true
+
+describe WalletService::Bitcoincashd do
+
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ describe 'WalletService::Bitcoincashd' do
+
+ let(:deposit) { create(:deposit, :deposit_bch, amount: 10) }
+ let(:withdraw) { create(:bch_withdraw) }
+ let(:deposit_wallet) { Wallet.find_by(gateway: :bitcoincashd, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(gateway: :bitcoincashd, kind: :hot) }
+
+ context '#create_address' do
+ subject { WalletService[deposit_wallet].create_address }
+
+ let(:new_address) { 'bchtest:pzsze7ety982w764sh9nq2ztknz0988w9cp7sv0zpj' }
+
+ let :getnewaddress_request do
+ { jsonrpc: '1.0',
+ method: 'getnewaddress',
+ params: []
+ }.to_json
+ end
+
+ let :getnewaddress_response do
+ { result: new_address }.to_json
+ end
+
+ before do
+ stub_request(:post, deposit_wallet.uri).with(body: getnewaddress_request).to_return(body: getnewaddress_response)
+ end
+
+ it { is_expected.to eq(address: new_address) }
+ end
+
+ context '#collect_deposit!' do
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+
+ let :listunspent_request do
+ {
+ jsonrpc: '1.0',
+ method: 'listunspent',
+ params: [1, 10000000, ['bchtest:qr49q8zvd3w6yeteak3hsap36s0ywpaj9g022qu4aw']],
+ }.to_json
+ end
+
+ let :listunspent_response do
+ { result: '0' }.to_json
+ end
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [hot_wallet.address, deposit.amount, '', '', true]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: txid }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: listunspent_request).to_return(body: listunspent_response)
+ stub_request(:post, deposit_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq([txid]) }
+ end
+
+ context '#build_withdrawal!' do
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [withdraw.rid, withdraw.amount, '', '', false]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: txid }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq(txid) }
+ end
+ end
+end
diff --git a/spec/services/wallet_service/bitcoind_spec.rb b/spec/services/wallet_service/bitcoind_spec.rb
new file mode 100644
index 0000000000..7113408ac3
--- /dev/null
+++ b/spec/services/wallet_service/bitcoind_spec.rb
@@ -0,0 +1,99 @@
+# encoding: UTF-8
+# frozen_string_literal: true
+
+describe WalletService::Bitcoind do
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ describe 'WalletService::Bitcoind' do
+
+ let(:deposit) { create(:deposit, :deposit_btc, amount: 10) }
+ let(:withdraw) { create(:btc_withdraw) }
+ let(:deposit_wallet) { Wallet.find_by(gateway: :bitcoind, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(gateway: :bitcoind, kind: :hot) }
+
+ context '#create_address' do
+ subject { WalletService[deposit_wallet].create_address }
+
+ let(:newaddress) { '2N7r9zKXkypzqtXfWkKfs3uZqKbJUhdK6JE' }
+ let :getnewaddress_request do
+ { jsonrpc: '1.0',
+ method: 'getnewaddress',
+ params: []
+ }.to_json
+ end
+
+ let :getnewaddress_response do
+ { result: newaddress }.to_json
+ end
+
+ before do
+ stub_request(:post, deposit_wallet.uri).with(body: getnewaddress_request).to_return(body: getnewaddress_response)
+ end
+
+ it { is_expected.to eq(address: newaddress) }
+ end
+
+ context '#collect_deposit!' do
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+
+ let :listunspent_response do
+ { result: '0' }.to_json
+ end
+
+ let :listunspent_request do
+ {
+ jsonrpc: '1.0',
+ method: 'listunspent',
+ params: [1, 10000000, ['3NwYr8JxjHG2MBkgdBiHCxStSWDzyjS5U8']],
+ }.to_json
+ end
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [hot_wallet.address, deposit.amount, '', '', true]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: txid }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: listunspent_request).to_return(body: listunspent_response)
+ stub_request(:post, deposit_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq([txid]) }
+ end
+
+ context '#build_withdrawal!' do
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [withdraw.rid, withdraw.amount, '', '', false]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: txid }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq(txid) }
+ end
+ end
+end
diff --git a/spec/services/wallet_service/bitgo_spec.rb b/spec/services/wallet_service/bitgo_spec.rb
new file mode 100644
index 0000000000..3a3cd5158a
--- /dev/null
+++ b/spec/services/wallet_service/bitgo_spec.rb
@@ -0,0 +1,123 @@
+# encoding: UTF-8
+# frozen_string_literal: true
+
+describe WalletService::Bitgo do
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ let(:deposit) { create(:deposit_btc) }
+ let(:deposit_wallet) { Wallet.find_by(currency: :btc, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(currency: :btc, kind: :hot) }
+ let(:wallet_client) { WalletClient[deposit_wallet] }
+ let(:withdraw) { create(:btc_withdraw) }
+
+ before do
+ [deposit_wallet, hot_wallet].each do |wallet|
+ wallet.update! \
+ gateway: 'bitgo',
+ bitgo_test_net: true,
+ bitgo_wallet_id: '5a7d9f52ba1923b107b80baabe0c3574',
+ address: '2MtmgqDM5Gb91dAo1cUHpx9fdh1xgD7L1Xb',
+ bitgo_wallet_passphrase: 'secret',
+ bitgo_rest_api_root: 'http://127.0.0.1:3080/api/v2',
+ bitgo_rest_api_access_token: 'v2x0b53e612518e5ea625eb3c24175438b37f56bc1f82e9c9ba3b038c91b0c72e67'
+ end
+ end
+
+ def request_headers(wallet)
+ { Accept: 'application/json',
+ Authorization: 'Bearer ' + wallet.bitgo_rest_api_access_token }
+ end
+
+ def response_headers
+ { 'Content-Type' => 'application/json' }
+ end
+
+ describe '#create_address' do
+ subject { WalletService[deposit_wallet].create_address(options) }
+
+ before do
+ stub_request(request_method, deposit_wallet.bitgo_rest_api_root + request_path)
+ .with(body: request_body, headers: request_headers(deposit_wallet))
+ .to_return(status: 200, body: response_body, headers: response_headers)
+ end
+
+ let(:request_body) { {} }
+ let(:response_body) { '{"id":"5acb44423a713ade07b42b0140f91a96","address":"2MySruptM4SgZF49KSc3x5KyxAW61ghyvtc"}' }
+
+ context 'when BitGo address ID is provided' do
+ let(:options) { {} }
+ let(:request_method) { :post }
+ let(:request_path) { '/tbtc/wallet/' + deposit_wallet.bitgo_wallet_id + '/address' }
+
+ it { is_expected.to eq(address: '2MySruptM4SgZF49KSc3x5KyxAW61ghyvtc', bitgo_address_id: '5acb44423a713ade07b42b0140f91a96') }
+ end
+
+ context 'when BitGo address ID is provided' do
+ let(:request_path) { '/tbtc/wallet/' + deposit_wallet.bitgo_wallet_id + '/address/5acb44423a713ade07b42b0140f91a96' }
+ let(:request_method) { :get }
+ let(:options) { { address_id: '5acb44423a713ade07b42b0140f91a96' } }
+
+ it { is_expected.to eq(address: '2MySruptM4SgZF49KSc3x5KyxAW61ghyvtc') }
+ end
+ end
+
+ describe '#collect_deposit!' do
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ let(:options) { {} }
+ let(:request_method) { :post }
+ let(:request_path) { '/tbtc/wallet/' + deposit_wallet.bitgo_wallet_id + '/tx/build' }
+ let(:request_body) {{recipients:[{address: hot_wallet.address, amount: "#{wallet_client.convert_to_base_unit!(deposit.amount)}" }]} }
+ let(:response_body) {'{"feeInfo": {"fee": 3037}}'}
+
+ let(:set_tx_request_path) { '/tbtc/wallet/' + deposit_wallet.bitgo_wallet_id + '/sendcoins' }
+ let(:set_tx_request_body) do
+ { address: hot_wallet.address,
+ amount: "#{(wallet_client.convert_to_base_unit!(deposit.amount)-3037).to_i}",
+ walletPassphrase: deposit_wallet.bitgo_wallet_passphrase
+ }
+ end
+ let(:set_tx_response_body) {'{"txid": "dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3" }'}
+
+ before do
+ # stub build_raw_transaction request
+ stub_request(request_method, deposit_wallet.bitgo_rest_api_root + request_path)
+ .with(body: request_body, headers: request_headers(deposit_wallet))
+ .to_return(status: 200, body: response_body, headers: response_headers)
+
+ # stub create_withdrawal request
+ stub_request(request_method, deposit_wallet.bitgo_rest_api_root + set_tx_request_path)
+ .with(body: set_tx_request_body, headers: request_headers(deposit_wallet))
+ .to_return(status: 200, body: set_tx_response_body, headers: response_headers)
+ end
+
+ it { is_expected.to eq('dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3') }
+ end
+
+ describe '#build_withdrawal!' do
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw) }
+
+ let(:options) { {} }
+ let(:request_method) { :post }
+ let(:request_path) { '/tbtc/wallet/' + hot_wallet.bitgo_wallet_id + '/sendcoins' }
+ let(:request_body) do
+ { address: withdraw.rid,
+ amount: "#{(wallet_client.convert_to_base_unit!(withdraw.amount)).to_i}",
+ walletPassphrase: hot_wallet.bitgo_wallet_passphrase
+ }
+ end
+ let(:response_body) {'{"txid": "dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3" }'}
+
+ before do
+ stub_request(request_method, hot_wallet.bitgo_rest_api_root + request_path)
+ .with(body: request_body, headers: request_headers(hot_wallet))
+ .to_return(status: 200, body: response_body, headers: response_headers)
+ end
+
+ it { is_expected.to eq('dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3') }
+ end
+end
diff --git a/spec/services/wallet_service/dashd_spec.rb b/spec/services/wallet_service/dashd_spec.rb
new file mode 100644
index 0000000000..0784429350
--- /dev/null
+++ b/spec/services/wallet_service/dashd_spec.rb
@@ -0,0 +1,100 @@
+# encoding: UTF-8
+# frozen_string_literal: true
+
+describe WalletService::Dashd do
+
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ describe 'WalletService::Dashd' do
+
+ let(:deposit) { create(:deposit, :deposit_dash, amount: 10) }
+ let(:withdraw) { create(:dash_withdraw) }
+ let(:deposit_wallet) { Wallet.find_by(gateway: :dashd, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(gateway: :dashd, kind: :hot) }
+
+ context '#create_address' do
+ subject { WalletService[deposit_wallet].create_address }
+
+ let(:new_adrress) { 'yborj44WhothaX6vwoMhRMjkq1xELhAWQp' }
+
+ let :getnewaddress_request do
+ { jsonrpc: '1.0',
+ method: 'getnewaddress',
+ params: []
+ }.to_json
+ end
+
+ let :getnewaddress_response do
+ { result: new_adrress }.to_json
+ end
+
+ before do
+ stub_request(:post, deposit_wallet.uri).with(body: getnewaddress_request).to_return(body: getnewaddress_response)
+ end
+
+ it { is_expected.to eq(address: new_adrress) }
+ end
+
+ context '#collect_deposit!' do
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+ let :listunspent_request do
+ {
+ jsonrpc: '1.0',
+ method: 'listunspent',
+ params: [1, 10000000, ['yborj44WhothaX6vwoMhRMjkq1xELhAWQp']],
+ }.to_json
+ end
+
+ let :listunspent_response do
+ { result: '0' }.to_json
+ end
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [hot_wallet.address, deposit.amount, '', '', true]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: txid }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: listunspent_request).to_return(body: listunspent_response)
+ stub_request(:post, deposit_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq([txid]) }
+ end
+
+ context '#build_withdrawal!' do
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [withdraw.rid, withdraw.amount, '', '', false]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: txid }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq(txid) }
+ end
+ end
+end
diff --git a/spec/services/wallet_service/geth_spec.rb b/spec/services/wallet_service/geth_spec.rb
new file mode 100644
index 0000000000..a63b10e5dc
--- /dev/null
+++ b/spec/services/wallet_service/geth_spec.rb
@@ -0,0 +1,310 @@
+# encoding: UTF-8
+# frozen_string_literal: true
+
+describe WalletService::Geth do
+
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ let(:deposit_wallet) { Wallet.find_by(currency: :eth, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(currency: :eth, kind: :hot) }
+ let(:warm_wallet) { Wallet.find_by(currency: :eth, kind: :warm) }
+ let(:fee_wallet) { Wallet.find_by(currency: 'eth', kind: 'fee') }
+ let(:eth_options) { { gas_limit: 21_000, gas_price: 1_000_000_000 } }
+
+ describe '#create_address' do
+ subject { WalletService[deposit_wallet].create_address }
+
+ let :personal_newAccount_request do
+ { jsonrpc: '2.0',
+ id: 1,
+ method: 'personal_newAccount',
+ params: %w[ pass@word ]
+ }.to_json
+ end
+
+ let :personal_newAccount_response do
+ { jsonrpc: '2.0',
+ id: 1,
+ result: '0x42eb768f2244c8811c63729a21a3569731535f06'
+ }.to_json
+ end
+
+ before do
+ Passgen.stubs(:generate).returns('pass@word')
+ stub_request(:post, deposit_wallet.uri ).with(body: personal_newAccount_request).to_return(body: personal_newAccount_response)
+ end
+
+ it { is_expected.to eq(address: '0x42eb768f2244c8811c63729a21a3569731535f06', secret: 'pass@word') }
+ end
+
+ describe '#collect_deposit!' do
+
+ let(:deposit) { create(:deposit, :deposit_eth, amount: 10) }
+ let(:eth_payment_address) { deposit.account.payment_address }
+ let(:issuer) { { address: eth_payment_address.address.downcase, secret: eth_payment_address.secret } }
+
+ let!(:payment_address) do
+ create(:eth_payment_address, {account: deposit.account, address: '0xe3cb6897d83691a8eb8458140a1941ce1d6e6daa'})
+ end
+
+ context 'Collect eth deposit to hot wallet' do
+
+ let(:deposit_wallet_address) { deposit_wallet.address.downcase }
+ let(:hot_wallet_address) { hot_wallet.address.downcase }
+ let(:txid) { '0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b' }
+
+ let :eth_getBalance_request do
+ { jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ hot_wallet_address, 'latest' ],
+ }.to_json
+ end
+
+ let :eth_getBalance_response do
+ { result: '0' }.to_json
+ end
+
+ let :eth_sendTransaction_request do
+ { jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_sendTransaction',
+ params:
+ [
+ {
+ from: issuer[:address],
+ to: hot_wallet_address,
+ value: '0x' + (deposit.amount_to_base_unit! - eth_options[:gas_limit] * eth_options[:gas_price]).to_s(16),
+ gas: '0x' + eth_options[:gas_limit].to_s(16),
+ gasPrice: '0x' + eth_options[:gas_price].to_s(16)
+ }
+ ]
+ }.to_json
+ end
+
+ let :eth_sendTransaction_response do
+ { jsonrpc: '2.0',
+ id: 2,
+ result: txid
+ }.to_json
+ end
+
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: eth_getBalance_request).to_return(body: eth_getBalance_response)
+ WalletClient[deposit_wallet].class.any_instance.expects(:permit_transaction)
+ stub_request(:post, deposit_wallet.uri).with(body: eth_sendTransaction_request).to_return(body: eth_sendTransaction_response)
+ end
+
+ it do
+ #Transaction to Hot wallet with all deposit amount
+ is_expected.to eq([txid])
+ end
+ end
+
+ context 'Collect TRST deposit to hot wallet' do
+ let(:deposit) { create(:deposit, :deposit_trst, amount: 10) }
+ let(:trst_payment_address) { deposit.account.payment_address }
+
+ let(:deposit_wallet) { Wallet.find_by(currency: :trst, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(currency: :trst, kind: :hot) }
+
+ let(:issuer) { { address: trst_payment_address.address, secret: trst_payment_address.secret } }
+ let(:recipient) { { address: hot_wallet.address } }
+
+ let!(:payment_address) do
+ create(:trst_payment_address, {account: deposit.account, address: '0xe3cb6897d83691a8eb8458140a1941ce1d6e6daa'})
+ end
+
+ let(:txid) { '0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b' }
+
+ let :eth_call_request do
+ {
+ "jsonrpc":"2.0",
+ "id":1,
+ "method":"eth_call",
+ "params":
+ [
+ {
+ "to":"0x87099add3bcc0821b5b151307c147215f839a110",
+ "data":"0x70a08231000000000000000000000000b6a61c43dae37c0890936d720dc42b5cbda990f9"
+ },
+ "latest"
+ ]
+ }.to_json
+ end
+
+ let :eth_call_response do
+ { result: '0' }.to_json
+ end
+
+ let :eth_sendTransaction_request do
+ { jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_sendTransaction',
+ params:
+ [
+ {
+ from: issuer[:address],
+ to: '0x87099add3bcc0821b5b151307c147215f839a110',
+ data: '0xa9059cbb000000000000000000000000b6a61c43dae37c0890936d720dc42b5cbda990f90000000000000000000000000000000000000000000000000000000000989680'
+ }
+ ]
+ }.to_json
+ end
+
+ let :eth_sendTransaction_response do
+ { jsonrpc: '2.0',
+ id: 1,
+ result: txid
+ }.to_json
+ end
+
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: eth_call_request).to_return(body: eth_call_response)
+ WalletClient[deposit_wallet].class.any_instance.expects(:permit_transaction)
+ stub_request(:post, deposit_wallet.uri).with(body: eth_sendTransaction_request).to_return(body: eth_sendTransaction_response)
+ end
+
+ it do
+ is_expected.to eq([txid])
+ end
+ end
+ end
+
+ describe 'create_withdrawal!' do
+ let(:issuer) { { address: hot_wallet.address.downcase, secret: hot_wallet.secret } }
+ let(:recipient) { { address: withdraw.rid.downcase } }
+
+ context 'ETH Withdrawal' do
+ let(:withdraw) { create(:eth_withdraw, rid: '0x85h43d8a49eeb85d32cf465507dd71d507100c1') }
+
+ let(:txid) { '0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b' }
+
+ let :eth_sendTransaction_request do
+ { jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_sendTransaction',
+ params:
+ [
+ {
+ from: issuer[:address],
+ to: recipient[:address],
+ value: '0x8a6e51a672858000'
+ }
+ ]
+ }.to_json
+ end
+
+ let :eth_sendTransaction_response do
+ { jsonrpc: '2.0',
+ id: 1,
+ result: txid
+ }.to_json
+ end
+
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw)}
+
+ before do
+ WalletClient[hot_wallet].class.any_instance.expects(:permit_transaction)
+ stub_request(:post, 'http://127.0.0.1:8545/').with(body: eth_sendTransaction_request).to_return(body: eth_sendTransaction_response)
+ end
+
+ it { is_expected.to eq(txid) }
+ end
+
+ context 'TRST Withdrawal' do
+ let(:withdraw) { create(:trst_withdraw, rid: '0x85h43d8a49eeb85d32cf465507dd71d507100c1') }
+ let(:hot_wallet) { Wallet.find_by(currency: :trst, kind: :hot) }
+ let(:txid) { '0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b' }
+
+ let :eth_sendTransaction_request do
+ { jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_sendTransaction',
+ params:
+ [
+ {
+ from: issuer[:address].downcase,
+ to: '0x87099add3bcc0821b5b151307c147215f839a110',
+ data: '0xa9059cbb000000000000000000000000085h43d8a49eeb85d32cf465507dd71d507100c100000000000000000000000000000000000000000000000000000000009834d8'
+
+ }
+ ]
+ }.to_json
+ end
+
+ let :eth_sendTransaction_response do
+ { jsonrpc: '2.0',
+ id: 1,
+ result: txid
+ }.to_json
+ end
+
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw)}
+
+ before do
+ WalletClient[hot_wallet].class.any_instance.expects(:permit_transaction)
+ stub_request(:post, 'http://127.0.0.1:8545/').with(body: eth_sendTransaction_request).to_return(body: eth_sendTransaction_response)
+ end
+
+ it { is_expected.to eq(txid) }
+ end
+ end
+
+ describe 'deposit_collection_fees!' do
+ let(:deposit) { create(:deposit, :deposit_trst, amount: 10) }
+ let(:trst_payment_address) { deposit.account.payment_address }
+
+ let(:issuer) { { address: fee_wallet.address.downcase, secret: fee_wallet.secret } }
+ let(:recipient) { { address: trst_payment_address.address.downcase } }
+
+ let!(:payment_address) do
+ create(:trst_payment_address, {account: deposit.account, address: '0xe3cb6897d83691a8eb8458140a1941ce1d6e6daa'})
+ end
+
+ let(:txid) { '0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b' }
+
+ let :eth_sendTransaction_request do
+ { jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_sendTransaction',
+ params:
+ [
+ {
+ from: issuer[:address],
+ to: recipient[:address],
+ value: '0x5af3107a4000',
+ gas: '0x' + eth_options[:gas_limit].to_s(16),
+ gasPrice: '0x' + eth_options[:gas_price].to_s(16)
+ }
+ ]
+ }.to_json
+ end
+
+ let :eth_sendTransaction_response do
+ { jsonrpc: '2.0',
+ id: 1,
+ result: txid
+ }.to_json
+ end
+
+ subject { WalletService[deposit_wallet].deposit_collection_fees(deposit) }
+
+ before do
+ WalletClient[deposit_wallet].class.any_instance.expects(:permit_transaction)
+ stub_request(:post, deposit_wallet.uri).with(body: eth_sendTransaction_request).to_return(body: eth_sendTransaction_response)
+ end
+
+ it do
+ is_expected.to eq(txid)
+ end
+ end
+end
diff --git a/spec/services/wallet_service/litecoind_spec.rb b/spec/services/wallet_service/litecoind_spec.rb
new file mode 100644
index 0000000000..af1473ed59
--- /dev/null
+++ b/spec/services/wallet_service/litecoind_spec.rb
@@ -0,0 +1,101 @@
+# encoding: UTF-8
+# frozen_string_literal: true
+
+describe WalletService::Litecoind do
+
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ describe 'WalletService::Litecoind' do
+
+ let(:deposit) { create(:deposit, :deposit_ltc, amount: 10) }
+ let(:withdraw) { create(:ltc_withdraw) }
+ let(:deposit_wallet) { Wallet.find_by(gateway: :litecoind, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(gateway: :litecoind, kind: :hot) }
+
+ context '#create_address' do
+ subject { WalletService[deposit_wallet].create_address }
+
+ let(:new_adrress) { 'QcM2zjgbaXbH26utxnNFge24A1BnDgSgcU' }
+
+ let :getnewaddress_request do
+ { jsonrpc: '1.0',
+ method: 'getnewaddress',
+ params: []
+ }.to_json
+ end
+
+ let :getnewaddress_response do
+ { result: new_adrress }.to_json
+ end
+
+ before do
+ stub_request(:post, deposit_wallet.uri).with(body: getnewaddress_request).to_return(body: getnewaddress_response)
+ end
+
+ it { is_expected.to eq(address: new_adrress) }
+ end
+
+ context '#collect_deposit!' do
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+
+ let :listunspent_request do
+ {
+ jsonrpc: '1.0',
+ method: 'listunspent',
+ params: [1, 10000000, ['Qc2BM7gp8mKgJPPxLAadLAHteNQwhFwwuf']],
+ }.to_json
+ end
+
+ let :listunspent_response do
+ { result: '0' }.to_json
+ end
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [hot_wallet.address, deposit.amount, '', '', true]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: txid }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: listunspent_request).to_return(body: listunspent_response)
+ stub_request(:post, deposit_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq([txid]) }
+ end
+
+ context '#build_withdrawal!' do
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw) }
+
+ let(:txid) { 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }
+
+ let :sendtoaddress_request do
+ { jsonrpc: '1.0',
+ method: 'sendtoaddress',
+ params: [withdraw.rid, withdraw.amount, '', '', false]
+ }.to_json
+ end
+
+ let :sendtoaddress_response do
+ { result: 'dcedf50780f251c99e748362c1a035f2916efb9bb44fe5c5c3e857ea74ca06b3' }.to_json
+ end
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: sendtoaddress_request).to_return(body: sendtoaddress_response)
+ end
+
+ it { is_expected.to eq(txid) }
+ end
+ end
+end
diff --git a/spec/services/wallet_service/rippled_spec.rb b/spec/services/wallet_service/rippled_spec.rb
index 803b56a474..d94cbc01da 100644
--- a/spec/services/wallet_service/rippled_spec.rb
+++ b/spec/services/wallet_service/rippled_spec.rb
@@ -1,19 +1,185 @@
+# encoding: UTF-8
# frozen_string_literal: true
-describe 'WalletService::Ripple' do
- describe '#create_address!' do
- let(:service) { WalletService[wallet] }
- let(:wallet) { Wallet.find_by_blockchain_key('xrp-testnet') }
- let(:create_address) { service.create_address }
+describe WalletService::Rippled do
- it 'create valid address with destination_tag' do
- address = create_address[:address]
- expect(normalize_address(address)).to eq wallet.address
- expect(destination_tag_from(address).to_i).to be > 0
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ describe 'WalletService::Rippled' do
+
+ let(:sign_data) do
+ Rails.root.join('spec', 'resources', 'ripple-data', 'sign-transaction.json')
+ .yield_self {|file_path| File.open(file_path)}
+ .yield_self {|file| JSON.load(file)}
+ .to_json
+ end
+
+ let(:submit_data) do
+ Rails.root.join('spec', 'resources', 'ripple-data', 'submit-transaction.json')
+ .yield_self {|file_path| File.open(file_path)}
+ .yield_self {|file| JSON.load(file)}
+ .to_json
+ end
+
+ let(:deposit) {create(:deposit, :deposit_xrp, address: 'rN3J1yMz2PCGievtS2XTEgkrmdHiJgzb5Y', amount: 10)}
+ let(:withdraw) {create(:xrp_withdraw)}
+ let(:deposit_wallet) { Wallet.find_by(gateway: :rippled, kind: :deposit)}
+ let(:hot_wallet) { Wallet.find_by(gateway: :rippled, kind: :hot)}
+
+ context '#create_address!' do
+ let(:service) {WalletService[wallet]}
+ let(:wallet) {Wallet.find_by(gateway: :rippled, kind: :deposit)}
+ let(:create_address) {service.create_address}
+
+ it 'create valid address with destination_tag' do
+ address = create_address[:address]
+ expect(normalize_address(address)).to eq wallet.address
+ expect(destination_tag_from(address).to_i).to be > 0
+ end
+ end
+
+ context '#collect_deposit' do
+
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ let!(:payment_address) do
+ create(:xrp_payment_address, {account: deposit.account, address: 'rN3J1yMz2PCGievtS2XTEgkrmdHiJgzb5Y?dt=917590223', secret: 'changeme'})
+ end
+
+ let :account_info_response do
+ { result: '0' }.to_json
+ end
+
+ let :account_info_request do
+ {
+ jsonrpc: '1.0',
+ id: 1,
+ method: 'account_info',
+ params:
+ [
+ {
+ account: hot_wallet.address,
+ ledger_index: 'validated',
+ strict: true
+ }
+ ]
+ }.to_json
+ end
+
+ let :sign_request do
+ {jsonrpc: '1.0',
+ id: 1,
+ method: 'sign',
+ params:
+ [
+ secret: 'changeme',
+ tx_json:
+ {
+ Account: deposit.address,
+ Amount: '9990000',
+ Fee: 10000,
+ Destination: hot_wallet.address,
+ DestinationTag: 0,
+ TransactionType: 'Payment',
+ LastLedgerSequence: 31234504
+ }
+ ]
+ }.to_json
+ end
+
+ let :submit_request do
+ {
+ jsonrpc: '1.0',
+ id: 2,
+ method: 'submit',
+ params:
+ [
+ tx_blob: '1200002280000000240000016861D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5'\
+ 'EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402200E5C2DD81FDF0BE9AB'\
+ '2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F858081144B4'\
+ 'E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754'
+ ]
+ }.to_json
+ end
+
+ subject { WalletService[deposit_wallet].collect_deposit!(deposit) }
+
+ before do
+ stub_request(:post, hot_wallet.uri).with(body: account_info_request).to_return(body: account_info_response)
+ WalletClient[hot_wallet].class.any_instance.expects(:calculate_current_fee).returns(10000)
+ WalletClient[hot_wallet].class.any_instance.expects(:latest_block_number).returns(31234500)
+ stub_request(:post, deposit_wallet.uri).with(body: sign_request).to_return(body: sign_data)
+ stub_request(:post, deposit_wallet.uri).with(body: submit_request).to_return(body: submit_data)
+ end
+
+ it do
+ is_expected.to eq(["5B31A7518DC304D5327B4887CD1F7DC2C38D5F684170097020C7C9758B973847"])
+ end
+ end
+
+ context '#build_withdrawal!' do
+
+ let(:withdraw) { create(:xrp_withdraw, rid: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn') }
+
+ let :sign_request do
+ { jsonrpc: '1.0',
+ id: 1,
+ method: 'sign',
+ params:
+ [
+ secret: 'changeme',
+ tx_json:
+ {
+ Account: hot_wallet.address,
+ Amount: '9975000',
+ Fee: 10000,
+ Destination: withdraw.rid,
+ DestinationTag: 0,
+ TransactionType: 'Payment',
+ LastLedgerSequence: 31234504
+ }
+ ]
+ }.to_json
+ end
+
+ let :submit_request do
+ {
+ jsonrpc: '1.0',
+ id: 2,
+ method: 'submit',
+ params:
+ [
+ tx_blob: '1200002280000000240000016861D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5'\
+ 'EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402200E5C2DD81FDF0BE9AB'\
+ '2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F858081144B4'\
+ 'E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754'
+ ]
+ }.to_json
+ end
+
+ subject { WalletService[hot_wallet].build_withdrawal!(withdraw) }
+
+ before do
+ WalletClient[hot_wallet].class.any_instance.expects(:calculate_current_fee).returns(10000)
+ WalletClient[hot_wallet].class.any_instance.expects(:latest_block_number).returns(31234500)
+ # Request with method 'sign' to return a signed binary representation of the transaction.
+ stub_request(:post, deposit_wallet.uri).with(body: sign_request).to_return(body: sign_data)
+ # Request with method 'submit' method to apply a transaction and send it to the network to be confirmed and included in future ledgers
+ stub_request(:post, deposit_wallet.uri).with(body: submit_request).to_return(body: submit_data)
+ end
+
+ it do
+ is_expected.to eq('5B31A7518DC304D5327B4887CD1F7DC2C38D5F684170097020C7C9758B973847')
+ end
end
end
end
+
def normalize_address(address)
address.gsub(/\?dt=\d*\Z/, '')
end
diff --git a/spec/services/wallet_service_spec.rb b/spec/services/wallet_service_spec.rb
new file mode 100644
index 0000000000..ee5df7519c
--- /dev/null
+++ b/spec/services/wallet_service_spec.rb
@@ -0,0 +1,230 @@
+# encoding: UTF-8
+# frozen_string_literal: true
+
+describe WalletService do
+
+ around do |example|
+ WebMock.disable_net_connect!
+ example.run
+ WebMock.allow_net_connect!
+ end
+
+ let(:deposit_wallet) { Wallet.find_by(currency: :eth, kind: :deposit) }
+ let(:hot_wallet) { Wallet.find_by(currency: :eth, kind: :hot) }
+ let(:warm_wallet) { Wallet.find_by(currency: :eth, kind: :warm) }
+
+ describe 'spread deposit' do
+
+ context 'Deposit divided in two wallets (hot and warm)' do
+
+ let(:deposit) { create(:deposit, :deposit_eth, amount: 100) }
+
+ let :hot_wallet_eth_getBalance_response do
+ { result: '2b5e3af16b1880000' }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_response do
+ { result: '0' }.to_json
+ end
+
+ let :hot_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ hot_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ warm_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :spread_hash do
+ {
+ "0xb6a61c43DAe37c0890936D720DC42b5CBda990F9"=>0.5e2,
+ "0x2b9fBC10EbAeEc28a8Fc10069C0BC29E45eBEB9C"=>0.5e2
+ }
+ end
+
+ subject { WalletService[deposit_wallet].send(:spread_deposit, deposit) }
+
+ before do
+ # Hot wallet balance = 50 eth
+ stub_request(:post, hot_wallet.uri).with(body: hot_wallet_eth_getBalance_request).to_return(body: hot_wallet_eth_getBalance_response)
+ # Warm wallet balance = 0 eth
+ stub_request(:post, hot_wallet.uri).with(body: warm_wallet_eth_getBalance_request).to_return(body: warm_wallet_eth_getBalance_response)
+ end
+ it do
+ # Deposit amount 100 eth
+ # Collect 50 eth to Hot wallet and 50 eth to Warm wallet
+ is_expected.to eq(spread_hash)
+ end
+ end
+
+ context 'Deposit divided in two wallets and collect all remaining to last wallet(warm)' do
+
+ let(:deposit) { create(:deposit, :deposit_eth, amount: 200) }
+
+ let :hot_wallet_eth_getBalance_response do
+ { result: '2b5e3af16b1880000' }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_response do
+ { result: '0' }.to_json
+ end
+
+ let :hot_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ hot_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ warm_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :spread_hash do
+ {
+ "0xb6a61c43DAe37c0890936D720DC42b5CBda990F9"=>0.5e2,
+ "0x2b9fBC10EbAeEc28a8Fc10069C0BC29E45eBEB9C"=>1.5e2
+ }
+ end
+
+ subject { WalletService[deposit_wallet].send(:spread_deposit, deposit) }
+
+ before do
+ warm_wallet.update!(max_balance: 100)
+ # Hot wallet balance = 50 eth
+ stub_request(:post, hot_wallet.uri).with(body: hot_wallet_eth_getBalance_request).to_return(body: hot_wallet_eth_getBalance_response)
+ # Warm wallet balance = 0 eth
+ stub_request(:post, hot_wallet.uri).with(body: warm_wallet_eth_getBalance_request).to_return(body: warm_wallet_eth_getBalance_response)
+ end
+ it do
+ # Deposit amount 200 eth
+ # Collect 50 eth to Hot wallet and 150 eth to Warm wallet(last wallet)
+ is_expected.to eq(spread_hash)
+ end
+ end
+
+ context 'Deposit doesn\'t fit in any wallet' do
+ let(:deposit) { create(:deposit, :deposit_eth, amount: 200) }
+
+ let :hot_wallet_eth_getBalance_response do
+ { result: '56bc75e2d63100000' }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_response do
+ { result: '821ab0d4414980000' }.to_json
+ end
+
+ let :hot_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ hot_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ warm_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :spread_hash do
+ {
+ "0x2b9fBC10EbAeEc28a8Fc10069C0BC29E45eBEB9C"=>2e2
+ }
+ end
+
+ subject { WalletService[deposit_wallet].send(:spread_deposit, deposit) }
+
+ before do
+ hot_wallet.update!(max_balance: 100)
+ warm_wallet.update!(max_balance: 150)
+ # Hot wallet balance = 100 eth
+ stub_request(:post, hot_wallet.uri).with(body: hot_wallet_eth_getBalance_request).to_return(body: hot_wallet_eth_getBalance_response)
+ # Warm wallet balance = 150 eth
+ stub_request(:post, hot_wallet.uri).with(body: warm_wallet_eth_getBalance_request).to_return(body: warm_wallet_eth_getBalance_response)
+ end
+
+ it do
+ # Deposit amount 200 eth
+ # Collect all deposit to last wallet
+ is_expected.to eq(spread_hash)
+ end
+ end
+
+ context 'Intermediate amount is less than min collection amount in hot wallet' do
+ let(:deposit) { create(:deposit, :deposit_eth, amount: 100) }
+
+ let :hot_wallet_eth_getBalance_response do
+ { result: '35ab028ac154b80000' }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_response do
+ { result: '0' }.to_json
+ end
+
+ let :hot_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ hot_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :warm_wallet_eth_getBalance_request do
+ {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'eth_getBalance',
+ params: [ warm_wallet[:address].downcase, 'latest' ],
+ }.to_json
+ end
+
+ let :spread_hash do
+ {
+ "0x2b9fBC10EbAeEc28a8Fc10069C0BC29E45eBEB9C"=>0.1e3
+ }
+ end
+
+ subject { WalletService[deposit_wallet].send(:spread_deposit, deposit) }
+
+ before do
+ hot_wallet.update!(max_balance: 100)
+ warm_wallet.update!(max_balance: 200)
+ deposit.currency.update!(min_deposit_amount: 2)
+ # Hot wallet balance = 99 eth
+ stub_request(:post, hot_wallet.uri).with(body: hot_wallet_eth_getBalance_request).to_return(body: hot_wallet_eth_getBalance_response)
+ # Warm wallet balance = 0 eth
+ stub_request(:post, hot_wallet.uri).with(body: warm_wallet_eth_getBalance_request).to_return(body: warm_wallet_eth_getBalance_response)
+ end
+
+ it do
+ # Deposit amount 100 eth
+ # Collect all deposit to warm wallet
+ is_expected.to eq(spread_hash)
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 18c59f8eb1..5ce1daa3d2 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -90,8 +90,9 @@
I18n.locale = :en
%w[ eth-rinkeby btc-testnet dash-testnet ltc-testnet bch-testnet xrp-testnet ].each { |blockchain| FactoryBot.create(:blockchain, blockchain) }
%i[ usd btc dash eth xrp trst bch eur ltc ].each { |ccy| FactoryBot.create(:currency, ccy) }
- %i[ eth_hot btc_hot btc_deposit xrp_hot].each { |ccy| FactoryBot.create(:wallet, ccy) }
- %i[ btcusd dashbtc btceth btcxrp].each { |market| FactoryBot.create(:market, market) }
+ %i[ eth_deposit eth_hot eth_fee trst_deposit trst_hot btc_hot btc_deposit bch_deposit bch_hot dash_deposit dash_hot ltc_deposit ltc_hot xrp_deposit xrp_hot eth_warm ]
+ .each { |ccy| FactoryBot.create(:wallet, ccy) }
+ %i[ btcusd dashbtc btceth btcxrp ].each { |market| FactoryBot.create(:market, market) }
end
config.append_after :each do