class Peatio::Infura::Wallet

Constants

DEFAULT_ERC20_FEE
DEFAULT_ETH_FEE

Public Class Methods

new(settings = {}) click to toggle source
# File lib/peatio/infura/wallet.rb, line 9
def initialize(settings = {})
  @settings = settings
end

Public Instance Methods

configure(settings = {}) click to toggle source
# File lib/peatio/infura/wallet.rb, line 13
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, :sign_uri, :address, :secret)

  @currency = @settings.fetch(:currency) do
    raise Peatio::Wallet::MissingSettingError, :currency
  end.slice(:id, :base_factor, :options)
end
create_address!(options = {}) click to toggle source
# File lib/peatio/infura/wallet.rb, line 28
def create_address!(options = {})
  secret = options.fetch(:secret) { PasswordGenerator.generate(64) }
  secret.yield_self do |password|
    { address: normalize_address(client.json_rpc(:personal_newAccount, [password], conn_type="sign")),
      secret:  password }
  end
rescue Infura::Client::Error => e
  raise Peatio::Wallet::ClientError, e
end
create_transaction!(transaction, options = {}) click to toggle source
# File lib/peatio/infura/wallet.rb, line 38
def create_transaction!(transaction, options = {})
  if @currency.dig(:options, :erc20_contract_address).present?
    create_erc20_transaction!(transaction)
  else
    create_eth_transaction!(transaction, options)
  end
rescue Infura::Client::Error => e
  raise Peatio::Wallet::ClientError, e
end
load_balance!() click to toggle source
# File lib/peatio/infura/wallet.rb, line 76
def load_balance!
  if @currency.dig(:options, :erc20_contract_address).present?
    load_erc20_balance(@wallet.fetch(:address))
  else
    client.json_rpc(:eth_getBalance, [normalize_address(@wallet.fetch(:address)), 'latest'])
    .hex
    .to_d
    .yield_self { |amount| convert_from_base_unit(amount) }
  end
rescue Infura::Client::Error => e
  raise Peatio::Wallet::ClientError, e
end
prepare_deposit_collection!(transaction, deposit_spread, deposit_currency) click to toggle source
# File lib/peatio/infura/wallet.rb, line 59
def prepare_deposit_collection!(transaction, deposit_spread, deposit_currency)
  # TODO: Add spec for this behaviour.
  # Don't prepare for deposit_collection in case of eth deposit.
  return [] if deposit_currency.dig(:options, :erc20_contract_address).blank?

  options = DEFAULT_ERC20_FEE.merge(deposit_currency.fetch(:options).slice(:gas_limit, :gas_price))

  # We collect fees depending on the number of spread deposit size
  # Example: if deposit spreads on three wallets need to collect eth fee for 3 transactions
  fees = convert_from_base_unit(options.fetch(:gas_limit).to_i * options.fetch(:gas_price).to_i)
  transaction.amount = fees * deposit_spread.size

  [create_eth_transaction!(transaction)]
rescue Infura::Client::Error => e
  raise Peatio::Wallet::ClientError, e
end
sign_transaction(data) click to toggle source
# File lib/peatio/infura/wallet.rb, line 48
def sign_transaction(data)
  client.json_rpc(:eth_signTransaction, [ data ], conn_type="sign").tap do |response|
    unless response
      raise Infura::WalletClient::Error, \
        "#{walletnormalize_address.name} withdrawal from #{normalize_address(issuer[:address])} to #{normalize_address(recipient[:address])} is not permitted."
    end
  end
rescue Infura::Client::Error => e
  raise Peatio::Wallet::ClientError, e
end

Private Instance Methods

abi_encode(method, *args) click to toggle source
# File lib/peatio/infura/wallet.rb, line 196
def abi_encode(method, *args)
  '0x' + args.each_with_object(Digest::SHA3.hexdigest(method, 256)[0...8]) do |arg, data|
    data.concat(arg.gsub(/\A0x/, '').rjust(64, '0'))
  end
end
client() click to toggle source
# File lib/peatio/infura/wallet.rb, line 216
def client
  uri = @wallet.fetch(:uri) { raise Peatio::Wallet::MissingSettingError, :uri }
  sign_uri = @wallet.fetch(:sign_uri) { raise Peatio::Wallet::MissingSettingError, :sign_uri }
  @client ||= Client.new(uri, sign_uri, idle_timeout: 1)
end
contract_address() click to toggle source
# File lib/peatio/infura/wallet.rb, line 188
def contract_address
  normalize_address(@currency.dig(:options, :erc20_contract_address))
end
convert_from_base_unit(value) click to toggle source
# File lib/peatio/infura/wallet.rb, line 202
def convert_from_base_unit(value)
  value.to_d / @currency.fetch(:base_factor) #/
end
convert_to_base_unit(value) click to toggle source
# File lib/peatio/infura/wallet.rb, line 206
def convert_to_base_unit(value)
  x = value.to_d * @currency.fetch(:base_factor)
  unless (x % 1).zero?
    raise Peatio::WalletClient::Error,
        "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
create_erc20_transaction!(transaction, options = {}) click to toggle source
# File lib/peatio/infura/wallet.rb, line 151
def create_erc20_transaction!(transaction, options = {})
  currency_options = @currency.fetch(:options).slice(:gas_limit, :gas_price, :erc20_contract_address)
  options.merge!(DEFAULT_ERC20_FEE, currency_options)

  amount = convert_to_base_unit(transaction.amount)
  data = abi_encode('transfer(address,uint256)',
                    normalize_address(transaction.to_address),
                    '0x' + amount.to_s(16))

  permit_transaction
  signed_hash = sign_transaction({
    from:     normalize_address(@wallet.fetch(:address)),
    to:       normalize_address(transaction.to_address),
    data:     data,
    gas:      '0x' + options.fetch(:gas_limit).to_i.to_s(16),
    gasPrice: '0x' + options.fetch(:gas_price).to_i.to_s(16),
    nonce:    get_transaction_count
  }.compact)

  txid = client.json_rpc(:eth_sendRawTransaction, [ signed_hash.fetch('raw') ] )

  unless valid_txid?(normalize_txid(txid))
    raise Infura::WalletClient::Error, \
          "Withdrawal from #{@wallet.fetch(:address)} to #{transaction.to_address} failed."
  end
  transaction.hash = normalize_txid(txid)
  transaction
end
create_eth_transaction!(transaction, options = {}) click to toggle source
# File lib/peatio/infura/wallet.rb, line 121
def create_eth_transaction!(transaction, options = {})
  currency_options = @currency.fetch(:options).slice(:gas_limit, :gas_price)
  options.merge!(DEFAULT_ETH_FEE, currency_options)

  amount = convert_to_base_unit(transaction.amount)

  # Subtract fees from initial deposit amount in case of deposit collection
  amount -= options.fetch(:gas_limit).to_i * options.fetch(:gas_price).to_i if options.dig(:subtract_fee)

  permit_transaction
  signed_hash = sign_transaction({
    from:     normalize_address(@wallet.fetch(:address)),
    to:       normalize_address(transaction.to_address),
    value:    '0x' + amount.to_s(16),
    gas:      '0x' + options.fetch(:gas_limit).to_i.to_s(16),
    gasPrice: '0x' + options.fetch(:gas_price).to_i.to_s(16),
    nonce:    get_transaction_count
  }.compact)

  txid = client.json_rpc(:eth_sendRawTransaction, [ signed_hash.fetch('raw') ] )

  unless valid_txid?(normalize_txid(txid))
    raise Infura::WalletClient::Error, \
          "Withdrawal from #{@wallet.fetch(:address)} to #{transaction.to_address} failed."
  end
  transaction.amount = convert_from_base_unit(amount)
  transaction.hash = normalize_txid(txid)
  transaction
end
get_transaction_count() click to toggle source
# File lib/peatio/infura/wallet.rb, line 110
def get_transaction_count
  json_rpc(:eth_getTransactionCount, [normalize_address(@wallet.fetch(:address)), "latest"]).tap do |response|
    unless response
      raise Infura::WalletClient::Error, \
        "#{wallet.name} withdrawal from #{normalize_address(@wallet.fetch(:address))} to address is not permitted."
    end
  end
rescue Infura::Client::Error => e
  raise Peatio::Wallet::ClientError, e
end
load_erc20_balance(address) click to toggle source
# File lib/peatio/infura/wallet.rb, line 91
def load_erc20_balance(address)
  data = abi_encode('balanceOf(address)', normalize_address(address))
  client.json_rpc(:eth_call, [{ to: contract_address, data: data }, 'latest'])
    .hex
    .to_d
    .yield_self { |amount| convert_from_base_unit(amount) }
end
normalize_address(address) click to toggle source
# File lib/peatio/infura/wallet.rb, line 180
def normalize_address(address)
  address.downcase
end
normalize_txid(txid) click to toggle source
# File lib/peatio/infura/wallet.rb, line 184
def normalize_txid(txid)
  txid.downcase
end
permit_transaction() click to toggle source
# File lib/peatio/infura/wallet.rb, line 99
def permit_transaction
  client.json_rpc(:personal_unlockAccount, [normalize_address(@wallet.fetch(:address)), @wallet.fetch(:secret), 5], conn_type="sign").tap do |response|
    unless response
      raise Infura::WalletClient::Error, \
        "#{wallet.name} withdrawal from #{normalize_address(@wallet.fetch(:address))} to address is not permitted."
    end
  end
rescue Infura::Client::Error => e
  raise Peatio::Wallet::ClientError, e
end
valid_txid?(txid) click to toggle source
# File lib/peatio/infura/wallet.rb, line 192
def valid_txid?(txid)
  txid.to_s.match?(/\A0x[A-F0-9]{64}\z/i)
end