Initial commit
This commit is contained in:
58
lib/peatio/owhdwallet/client.rb
Normal file
58
lib/peatio/owhdwallet/client.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
module OWHDWallet
|
||||
class Client
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
class ConnectionError < Error; end
|
||||
|
||||
def initialize(endpoint, idle_timeout: 5)
|
||||
@endpoint = URI.parse(endpoint)
|
||||
@private_key = OpenSSL::PKey.read(Base64.urlsafe_decode64(ENV.fetch('PEATIO_JWT_PRIVATE_KEY')))
|
||||
@path = @endpoint.path.empty? ? "/" : @endpoint.path
|
||||
@idle_timeout = idle_timeout
|
||||
end
|
||||
|
||||
def rest_api(verb, path, data = nil)
|
||||
args = [@endpoint.to_s + path]
|
||||
jwt = JWT.encode({}, @private_key, 'RS256')
|
||||
|
||||
if data
|
||||
if %i[post put patch].include?(verb)
|
||||
args << data.compact.to_json
|
||||
args << { 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' + jwt }
|
||||
else
|
||||
args << data.compact
|
||||
args << {}
|
||||
end
|
||||
else
|
||||
args << nil
|
||||
args << {}
|
||||
end
|
||||
|
||||
args.last['Accept'] = 'application/json'
|
||||
|
||||
response = connection.send(verb, *args)
|
||||
response.assert_success!
|
||||
response = JSON.parse(response.body)
|
||||
rescue Faraday::Error => e
|
||||
raise ConnectionError, e
|
||||
rescue StandardError => e
|
||||
raise Error, e
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connection
|
||||
ca_file_path = ENV.fetch('HDWALLET_SSL_CERT_PATH', '')
|
||||
ssl = if ca_file_path.present?
|
||||
{ ca_file: ca_file_path }
|
||||
else
|
||||
Rails.logger.warn { "Peer verification turned off. SSL connection { verify: false }" }
|
||||
{ verify: false }
|
||||
end
|
||||
|
||||
@connection ||= Faraday.new(@endpoint, { ssl: ssl }) do |f|
|
||||
f.adapter :net_http_persistent, pool_size: 5, idle_timeout: @idle_timeout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
185
lib/peatio/owhdwallet/wallet.rb
Normal file
185
lib/peatio/owhdwallet/wallet.rb
Normal file
@@ -0,0 +1,185 @@
|
||||
module OWHDWallet
|
||||
class Wallet < Peatio::Wallet::Abstract
|
||||
DEFAULT_FEATURES = { skip_deposit_collection: false }.freeze
|
||||
DEFAULT_ERC20_FEE = { eth_gas_limit: 21_000, erc20_gas_limit: 90_000, gas_price: :standard }.freeze
|
||||
GAS_PRICE_THRESHOLDS = %w[standard safelow fast].freeze
|
||||
|
||||
def initialize(custom_features = {})
|
||||
@features = DEFAULT_FEATURES.merge(custom_features).slice(*SUPPORTED_FEATURES)
|
||||
@settings = {}
|
||||
end
|
||||
|
||||
def configure(settings = {})
|
||||
# Clean client state during configure.
|
||||
@client = nil
|
||||
|
||||
@settings.merge!(settings.slice(*SUPPORTED_SETTINGS))
|
||||
|
||||
@wallet = @settings.fetch(:wallet) do
|
||||
raise Peatio::Wallet::MissingSettingError, :wallet
|
||||
end.slice(:uri, :gateway_url, :address, :secret, :wallet_index)
|
||||
|
||||
@currency = @settings.fetch(:currency) do
|
||||
raise Peatio::Wallet::MissingSettingError, :currency
|
||||
end.slice(:id, :base_factor, :options)
|
||||
end
|
||||
|
||||
def create_address!(options = {})
|
||||
# TODO: To define coin type for btc-testnet, btc-mainnet
|
||||
response = client.rest_api(:post, '/wallet/new', {
|
||||
coin_type: coin_type
|
||||
})
|
||||
|
||||
{ address: response['address'], secret: response['passphrase'], details: response.except('address', 'passphrase') }
|
||||
rescue OWHDWallet::Client::Error => e
|
||||
raise Peatio::Wallet::ClientError, e
|
||||
end
|
||||
|
||||
def create_transaction!(transaction, options = {})
|
||||
eth_params = coin_type == 'eth' ? eth_transaction(transaction, options) : {}
|
||||
|
||||
amount = convert_to_base_unit(transaction.amount)
|
||||
response = client.rest_api(:post, '/tx/send', {
|
||||
coin_type: coin_type,
|
||||
to: transaction.to_address,
|
||||
amount: amount.to_i,
|
||||
gateway_url: wallet_gateway_url,
|
||||
wallet_index: wallet_index,
|
||||
passphrase: wallet_secret
|
||||
}.merge(eth_params))
|
||||
|
||||
transaction.hash = response['tx']
|
||||
transaction.options = response['options'] if response['options'].present?
|
||||
transaction
|
||||
rescue OWHDWallet::Client::Error => e
|
||||
raise Peatio::Wallet::ClientError, e
|
||||
end
|
||||
|
||||
# Only ERC-20 transaction
|
||||
def prepare_deposit_collection!(transaction, deposit_spread, deposit_currency)
|
||||
# Don't prepare for deposit_collection in case of eth deposit.
|
||||
return [] if deposit_currency.dig(:options, :erc20_contract_address).blank?
|
||||
return [] if deposit_spread.blank?
|
||||
|
||||
options = DEFAULT_ERC20_FEE.merge(deposit_currency.fetch(:options).slice(:gas_limit, :gas_price))
|
||||
gas_speed = options[:gas_price].in?(GAS_PRICE_THRESHOLDS) ? options[:gas_price] : 'standard'
|
||||
gas_limit = options[:gas_limit].present? ? options[:gas_limit].to_i : options[:erc20_gas_limit]
|
||||
|
||||
response = client.rest_api(:post, '/tx/before_collect', {
|
||||
coin_type: coin_type,
|
||||
gas_limit: gas_limit,
|
||||
gas_speed: gas_speed,
|
||||
spread_size: deposit_spread.size,
|
||||
to: transaction.to_address,
|
||||
gateway_url: wallet_gateway_url,
|
||||
wallet_index: wallet_index,
|
||||
passphrase: wallet_secret
|
||||
})
|
||||
|
||||
transaction.currency_id = 'eth' if transaction.currency_id.blank?
|
||||
transaction.hash = response['tx']
|
||||
transaction.options = {}
|
||||
transaction.options[:gas_limit] = gas_limit
|
||||
transaction.options[:gas_price] = response['gas_price']
|
||||
[transaction]
|
||||
rescue OWHDWallet::Client::Error => e
|
||||
raise Peatio::Wallet::ClientError, e
|
||||
end
|
||||
|
||||
def load_balance!
|
||||
response = client.rest_api(:post, '/wallet/balance', {
|
||||
coin_type: coin_type,
|
||||
gateway_url: wallet_gateway_url,
|
||||
address: wallet_address,
|
||||
contract_address: erc20_contract_address
|
||||
}.compact).fetch('balance')
|
||||
|
||||
if coin_type == 'eth'
|
||||
response = response.yield_self { |amount| convert_from_base_unit(amount) }
|
||||
end
|
||||
|
||||
response.to_d
|
||||
rescue OWHDWallet::Client::Error => e
|
||||
raise Peatio::Wallet::ClientError, e
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def eth_transaction(transaction, options)
|
||||
currency_options = @currency.fetch(:options).slice(:gas_limit, :gas_price)
|
||||
options.merge!(currency_options, DEFAULT_ERC20_FEE)
|
||||
|
||||
if transaction.options.present?
|
||||
gas_price = transaction.options[:gas_price]
|
||||
else
|
||||
gas_speed = options[:gas_price].in?(GAS_PRICE_THRESHOLDS) ? options[:gas_price] : 'standard'
|
||||
end
|
||||
|
||||
params = {
|
||||
gas_price: gas_price,
|
||||
gas_speed: gas_speed,
|
||||
subtract_fee: options.dig(:subtract_fee).present?
|
||||
}.compact!
|
||||
|
||||
if erc20_contract_address.present?
|
||||
gas_limit = options[:gas_limit].present? ? options[:gas_limit].to_i : options[:erc20_gas_limit]
|
||||
|
||||
params.merge!(contract_address: erc20_contract_address, gas_limit: gas_limit)
|
||||
else
|
||||
params[:gas_limit] = options[:gas_limit].present? ? options[:gas_limit].to_i : options[:eth_gas_limit]
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def coin_type
|
||||
if erc20_contract_address.present?
|
||||
'eth'
|
||||
else
|
||||
currency_id
|
||||
end
|
||||
end
|
||||
|
||||
def convert_to_base_unit(value)
|
||||
x = value.to_d * @currency.fetch(:base_factor)
|
||||
unless (x % 1).zero?
|
||||
raise Peatio::Wallet::ClientError,
|
||||
"Failed to convert value to base (smallest) unit because it exceeds the maximum precision: " \
|
||||
"#{value.to_d} - #{x.to_d} must be equal to zero."
|
||||
end
|
||||
x.to_i
|
||||
end
|
||||
|
||||
def convert_from_base_unit(value)
|
||||
value.to_d / @currency.fetch(:base_factor)
|
||||
end
|
||||
|
||||
def wallet_secret
|
||||
@wallet.fetch(:secret)
|
||||
end
|
||||
|
||||
def wallet_index
|
||||
@wallet.fetch(:wallet_index)
|
||||
end
|
||||
|
||||
def wallet_gateway_url
|
||||
@wallet.fetch(:gateway_url)
|
||||
end
|
||||
|
||||
def wallet_address
|
||||
@wallet.fetch(:address)
|
||||
end
|
||||
|
||||
def currency_id
|
||||
@currency.fetch(:id)
|
||||
end
|
||||
|
||||
def erc20_contract_address
|
||||
@currency.dig(:options, :erc20_contract_address)
|
||||
end
|
||||
|
||||
def client
|
||||
@client ||= Client.new(@wallet.fetch(:uri), idle_timeout: 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user