All Files
(92.43%
covered at
4.04
hits/line)
6 files in total.
185 relevant lines.
171 lines covered and
14 lines missed
-
# frozen_string_literal: true
-
-
1
require "peatio/eos/version"
-
1
require "active_support/core_ext/object/blank"
-
1
require "active_support/core_ext/enumerable"
-
1
require "peatio"
-
-
1
module Peatio
-
1
module Eos
-
1
require "bigdecimal"
-
1
require "bigdecimal/util"
-
-
1
require "peatio/eos/blockchain"
-
1
require "peatio/eos/client"
-
1
require "peatio/eos/wallet"
-
1
require "peatio/eos/transaction_serializer"
-
-
1
require "peatio/eos/hooks"
-
1
require "peatio/eos/version"
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Peatio
-
1
module Eos
-
1
class Blockchain < Peatio::Blockchain::Abstract
-
1
DEFAULT_FEATURES = {case_sensitive: true, cash_addr_format: false}.freeze
-
-
1
TOKEN_STANDARD = "eosio.token"
-
-
1
class MissingTokenNameError < Peatio::Blockchain::Error; end
-
1
class UndefinedCurrencyError < Peatio::Blockchain::Error; end
-
-
1
def initialize(custom_features={})
-
15
@features = DEFAULT_FEATURES.merge(custom_features).slice(*SUPPORTED_FEATURES)
-
15
@settings = {}
-
end
-
-
1
def configure(settings={})
-
# Clean client state during configure.
-
10
@client = nil
-
-
10
supported_settings = settings.slice(*SUPPORTED_SETTINGS)
-
10
supported_settings[:currencies]&.each do |c|
-
11
raise MissingTokenNameError, c[:id] if c.dig(:options, :eos_token_name).blank?
-
end
-
10
@settings.merge!(supported_settings)
-
end
-
-
1
def fetch_block!(block_number)
-
4
client.json_rpc("/v1/chain/get_block", "block_num_or_id" => block_number)
-
.fetch("transactions").each_with_object([]) do |tx, txs_array|
-
18
txs = build_transaction(tx).map do |ntx|
-
10
Peatio::Transaction.new(ntx.merge(block_number: block_number))
-
end
-
18
txs_array.append(*txs)
-
4
end.yield_self {|txs_array| Peatio::Block.new(block_number, txs_array) }
-
rescue Peatio::Eos::Client::Error => e
-
raise Peatio::Blockchain::ClientError, e
-
end
-
-
1
def load_balance_of_address!(address, currency_id)
-
2
currency = settings_fetch(:currencies).find {|c| c[:id] == currency_id }
-
1
raise UndefinedCurrencyError unless currency
-
-
1
balance = client.json_rpc("/v1/chain/get_currency_balance",
-
"account" => address, "code" => TOKEN_STANDARD)
-
1
.find {|b| b.split[1] == currency.dig(:options, :eos_token_name) }
-
-
# EOS return array with balances for all eosio.token currencies
-
1
balance.blank? ? 0 : normalize_balance(balance, currency)
-
rescue Peatio::Eos::Client::Error => e
-
raise Peatio::Wallet::ClientError, e
-
end
-
-
1
def latest_block_number
-
1
client.json_rpc("/v1/chain/get_info").fetch("head_block_num")
-
rescue Peatio::Eos::Client::Error => e
-
raise Peatio::Blockchain::ClientError, e
-
end
-
-
1
private
-
-
1
def build_transaction(tx)
-
21
return [] if tx["trx"]["id"].blank? # check for deferred transaction
-
-
17
tx.dig("trx", "transaction", "actions")
-
.each_with_object([]).with_index do |(entry, formatted_txs), i|
-
20
next unless entry["name"] == "transfer" && !entry["data"]["to"].empty?
-
-
16
amount, token_name = entry["data"]["quantity"]&.split
-
16
next if token_name.nil? || amount.to_d < 0
-
-
34
currencies = settings_fetch(:currencies).select {|c| c.dig(:options, :eos_token_name) == token_name }
-
16
status = tx["status"] == "executed" ? "success" : "failed"
-
16
address = "#{entry['data']['to']}?memo=#{get_memo(entry['data']['memo'])}"
-
-
# Build transaction for each currency belonging to blockchain.
-
-
16
currencies.pluck(:id).each do |currency_id|
-
15
formatted_txs << {hash: tx["trx"]["id"],
-
txout: i,
-
to_address: address,
-
amount: amount.to_d,
-
status: status,
-
currency_id: currency_id}
-
end
-
end
-
end
-
-
1
def normalize_balance(balance, currency)
-
1
balance.chomp(currency.dig(:options, :eos_token_name)).to_d
-
end
-
-
1
def get_memo(memo)
-
16
memo.match(/\bID[A-Z0-9]{10}\z/) ? memo : ""
-
end
-
-
1
def client
-
6
@client ||= Peatio::Eos::Client.new(settings_fetch(:server))
-
end
-
-
1
def settings_fetch(key)
-
23
@settings.fetch(key) { raise Peatio::Blockchain::MissingSettingError, key.to_s }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "memoist"
-
1
require "faraday"
-
1
require "better-faraday"
-
-
1
module Peatio
-
1
module Eos
-
1
class Client
-
1
Error = Class.new(StandardError)
-
1
class ConnectionError < Error; end
-
-
1
class ResponseError < StandardError
-
1
def initialize(code, msg)
-
super "#{msg} (#{code})"
-
end
-
end
-
-
1
extend Memoist
-
-
1
def initialize(endpoint, idle_timeout: 5)
-
8
@rpc_endpoint = URI.parse(endpoint)
-
8
@idle_timeout = idle_timeout
-
end
-
-
1
def json_rpc(path, params=nil, port=nil)
-
# We need to communicate with keosd to sign transaction,
-
# keosd is running on non default eos port which is 8900
-
# and passed to json_rpc function when we sign transaction
-
12
rpc = URI.parse(@rpc_endpoint.to_s)
-
12
rpc.port = (port.present? ? port : @rpc_endpoint.port)
-
12
response = connection(rpc).post do |req|
-
12
req.url path
-
-
# To communicate with keosd to sign transaction we need to pass Host param with keosd port
-
12
req.headers["Host"] = "0.0.0.0:#{port}" if port.present?
-
12
req.headers["Content-Type"] = "application/json"
-
12
req.body = params.to_json if params.present?
-
12
req.options.timeout = 120
-
end
-
12
response.assert_success!
-
12
response = JSON.parse(response.body)
-
12
return response if response.is_a?(Array) # get balance call return an array
-
-
20
response["error"].tap {|error| raise ResponseError.new(error["code"], error["message"]) if error }
-
10
response
-
rescue Faraday::Error => e
-
raise ConnectionError, e
-
rescue StandardError => e
-
raise Error, e
-
end
-
-
1
private
-
-
1
def connection(rpc)
-
12
Faraday.new(rpc) do |f|
-
12
f.adapter :net_http_persistent, pool_size: 5, idle_timeout: @idle_timeout
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Peatio
-
1
module Eos
-
1
module Hooks
-
1
BLOCKCHAIN_VERSION_REQUIREMENT = "~> 1.0.0"
-
1
WALLET_VERSION_REQUIREMENT = "~> 1.0.0"
-
-
1
class << self
-
1
def check_compatibility
-
1
unless Gem::Requirement.new(BLOCKCHAIN_VERSION_REQUIREMENT)
-
.satisfied_by?(Gem::Version.new(Peatio::Blockchain::VERSION))
-
[
-
"Eos blockchain version requiremnt was not suttisfied by Peatio::Blockchain.",
-
"Eos blockchain requires #{BLOCKCHAIN_VERSION_REQUIREMENT}.",
-
"Peatio::Blockchain version is #{Peatio::Blockchain::VERSION}"
-
].join('\n').tap {|s| Kernel.abort s }
-
end
-
-
1
unless Gem::Requirement.new(WALLET_VERSION_REQUIREMENT)
-
.satisfied_by?(Gem::Version.new(Peatio::Wallet::VERSION))
-
[
-
"Eos wallet version requiremnt was not suttisfied by Peatio::Wallet.",
-
"Eos wallet requires #{WALLET_VERSION_REQUIREMENT}.",
-
"Peatio::Wallet version is #{Peatio::Wallet::VERSION}"
-
].join('\n').tap {|s| Kernel.abort s }
-
end
-
end
-
-
1
def register
-
1
Peatio::Blockchain.registry[:eos] = Eos::Blockchain.new
-
1
Peatio::Wallet.registry[:eos] = Eos::Wallet.new
-
end
-
end
-
-
1
if defined?(Rails::Railtie)
-
require "peatio/eos/railtie"
-
else
-
1
check_compatibility
-
1
register
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Peatio
-
1
module Eos
-
1
class TransactionSerializer
-
1
class << self
-
1
def to_pack_json(address: "default", to_address: "default", amount: "default")
-
{
-
1
"code" => "eosio.token",
-
"action" => "transfer",
-
"args" => {
-
"from" => address,
-
"to" => to_address,
-
"quantity" => amount,
-
"memo" => "transfer from peatio"
-
}
-
}
-
end
-
end
-
-
1
class << self
-
1
def to_sign_json(ref_block_num: 2, block_prefix: 1, expiration: "default",
-
address: "default", packed_data: "default", secret: "default", chain_id: "default")
-
[
-
{
-
1
"ref_block_num" => ref_block_num,
-
"ref_block_prefix" => block_prefix,
-
"max_cpu_usage_ms" => 0,
-
"max_net_usage_words" => 0,
-
"expiration" => expiration,
-
"region" => "0",
-
"actions" => [{
-
"account" => "eosio.token",
-
"name" => "transfer",
-
"authorization" => [{
-
"actor" => address,
-
"permission" => "active",
-
}],
-
"data" => packed_data,
-
}],
-
:signatures => []
-
},
-
[secret],
-
chain_id
-
]
-
end
-
end
-
-
1
class << self
-
1
def to_push_json(address: "default", packed_data: "default", expiration: "default",
-
block_num: 2, block_prefix: 1, signature: "default")
-
{
-
1
"compression" => "none",
-
"transaction" => {
-
"actions" => [{
-
"account" => "eosio.token",
-
"name" => "transfer",
-
"authorization" => [{
-
"actor" => address,
-
"permission" => "active"
-
}],
-
"data" => packed_data,
-
}],
-
"expiration" => expiration,
-
"max_cpu_usage_ms" => 0,
-
"max_net_usage_words" => 0,
-
"delay_sec" => 0,
-
"ref_block_num" => block_num,
-
"ref_block_prefix" => block_prefix,
-
"context_free_actions" => [],
-
"context_free_data" => [],
-
"signatures" => signature,
-
"transaction_extensions" => []
-
},
-
"signatures" => signature
-
}
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Peatio
-
1
module Eos
-
1
class Wallet < Peatio::Wallet::Abstract
-
1
PRECISION = 4
-
-
1
ADDRESS_LENGTH = 12
-
-
1
TOKEN_STANDARD = "eosio.token"
-
-
1
class MissingTokenNameError < Peatio::Blockchain::Error; end
-
-
1
def initialize(settings={})
-
8
@settings = settings
-
end
-
-
1
def configure(settings={})
-
10
@settings.merge!(settings.slice(*SUPPORTED_SETTINGS))
-
-
10
@wallet = @settings.fetch(:wallet) do
-
1
raise Peatio::Wallet::MissingSettingError, :wallet
-
end.slice(:uri, :address, :secret)
-
-
9
@currency = @settings.fetch(:currency) do
-
1
raise Peatio::Wallet::MissingSettingError, :wallet
-
end.slice(:id, :base_factor, :options)
-
8
raise MissingTokenNameError if @currency.dig(:options, :eos_token_name).blank?
-
end
-
-
1
def create_address!(options={})
-
# For EOS and all others eosio.token deposits we use one EOS account which is defined like deposit wallet address in peatio.
-
# In Peatio EOS plugin we will define owner of deposit by user unige identifier (UID)
-
1
name = "#{@wallet.fetch(:address)}?memo=#{options.fetch(:uid)}"
-
1
{address: name, secret: @wallet.fetch(:secret)}
-
rescue Peatio::Eos::Client::Error => e
-
raise Peatio::Wallet::ClientError, e
-
end
-
-
1
def create_transaction!(transaction, _options={})
-
1
tx = transaction
-
1
amount = normalize_amount(tx.amount)
-
1
address = normalize_address(@wallet.fetch(:address))
-
# Pack main transaction info into hash
-
1
packed_data = client.json_rpc("/v1/chain/abi_json_to_bin", Peatio::Eos::TransactionSerializer.to_pack_json(address: address,
-
to_address: tx.to_address, amount: amount)).fetch("binargs")
-
1
info = client.json_rpc("/v1/chain/get_info")
-
# Get block info
-
1
block = client.json_rpc("/v1/chain/get_block", "block_num_or_id" => info.fetch("last_irreversible_block_num"))
-
1
ref_block_num = info.fetch("last_irreversible_block_num") & 0xFFFF
-
# Get transaction expiration
-
1
expiration = normalize_expiration(block.fetch("timestamp"))
-
# Sign transaction before push
-
1
signed = client.json_rpc("/v1/wallet/sign_transaction", Peatio::Eos::TransactionSerializer.to_sign_json(ref_block_num: ref_block_num,
-
block_prefix: block.fetch("ref_block_prefix"), expiration: expiration, address: address,
-
packed_data: packed_data, secret: @wallet.fetch(:secret), chain_id: info.fetch("chain_id")), 8900)
-
1
txid = client.json_rpc("/v1/chain/push_transaction", Peatio::Eos::TransactionSerializer.to_push_json(address: address,
-
packed_data: packed_data, expiration: signed.fetch("expiration"), block_num: signed.fetch("ref_block_num"),
-
block_prefix: signed.fetch("ref_block_prefix"), signature: signed.fetch("signatures"))).fetch("transaction_id")
-
1
tx.hash = txid
-
1
tx
-
rescue Peatio::Eos::Client::Error => e
-
raise Peatio::Wallet::ClientError, e
-
end
-
-
1
def load_balance!
-
1
balance = client.json_rpc("/v1/chain/get_currency_balance",
-
"account" => @wallet.fetch(:address), "code" => TOKEN_STANDARD)
-
1
.find {|b| b.split[1] == @currency.dig(:options, :eos_token_name) }
-
1
balance.blank? ? 0 : normalize_balance(balance)
-
rescue Peatio::Eos::Client::Error => e
-
raise Peatio::Wallet::ClientError, e
-
end
-
-
1
private
-
-
1
def normalize_address(address)
-
1
address&.split('?memo=').first
-
end
-
-
1
def normalize_amount(amount)
-
1
"%.#{PRECISION}f" % amount + " #{@currency.dig(:options, :eos_token_name)}"
-
end
-
-
1
def normalize_balance(balance)
-
1
balance.chomp(@currency.dig(:options, :eos_token_name)).to_d
-
end
-
-
1
def normalize_expiration(time)
-
1
(Time.parse(time) + 3600).iso8601(6).split("+").first
-
end
-
-
1
def client
-
6
uri = @wallet.fetch(:uri) { raise Peatio::Wallet::MissingSettingError, :uri }
-
6
@client ||= Client.new(uri)
-
end
-
end
-
end
-
end