Initial commit
This commit is contained in:
117
lib/peatio/bitcoin/blockchain.rb
Normal file
117
lib/peatio/bitcoin/blockchain.rb
Normal file
@@ -0,0 +1,117 @@
|
||||
module Bitcoin
|
||||
# TODO: Processing of unconfirmed transactions from mempool isn't supported now.
|
||||
class Blockchain < Peatio::Blockchain::Abstract
|
||||
|
||||
DEFAULT_FEATURES = {case_sensitive: true, cash_addr_format: false}.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))
|
||||
end
|
||||
|
||||
def fetch_block!(block_number)
|
||||
block_hash = client.json_rpc(:getblockhash, [block_number])
|
||||
|
||||
client.json_rpc(:getblock, [block_hash, 2])
|
||||
.fetch('tx').each_with_object([]) do |tx, txs_array|
|
||||
txs = build_transaction(tx).map do |ntx|
|
||||
Peatio::Transaction.new(ntx.merge(block_number: block_number))
|
||||
end
|
||||
txs_array.append(*txs)
|
||||
end.yield_self { |txs_array| Peatio::Block.new(block_number, txs_array) }
|
||||
rescue Bitcoin::Client::Error => e
|
||||
raise Peatio::Blockchain::ClientError, e
|
||||
end
|
||||
|
||||
def latest_block_number
|
||||
client.json_rpc(:getblockcount)
|
||||
rescue Bitcoin::Client::Error => e
|
||||
raise Peatio::Blockchain::ClientError, e
|
||||
end
|
||||
|
||||
def fetch_transaction(transaction)
|
||||
transaction_hash = client.json_rpc(:getrawtransaction, [transaction.hash, 1])
|
||||
tx = nil
|
||||
transaction_hash.fetch('vout').select do |entry|
|
||||
entry.fetch('value').to_d > 0 &&
|
||||
entry['scriptPubKey'].has_key?('addresses')
|
||||
end.each do |entry|
|
||||
if transaction.to_address == entry['scriptPubKey']['addresses'][0] && entry.fetch('value').to_d == transaction.amount
|
||||
tx = { hash: transaction_hash['txid'], txout: entry['n'],
|
||||
to_address: entry['scriptPubKey']['addresses'][0],
|
||||
amount: entry.fetch('value').to_d,
|
||||
status: 'success' }
|
||||
break
|
||||
else
|
||||
next
|
||||
end
|
||||
end
|
||||
if tx.present?
|
||||
Peatio::Transaction.new(tx)
|
||||
else
|
||||
Peatio::Transaction.new
|
||||
end
|
||||
end
|
||||
|
||||
def transaction_sources(transaction)
|
||||
transaction_hash = client.json_rpc(:getrawtransaction, [transaction.hash, 1])
|
||||
transaction_hash['vin'].each_with_object([]) do |vin, source_addresses|
|
||||
next if vin['txid'].blank?
|
||||
|
||||
vin_transaction = client.json_rpc(:getrawtransaction, [vin['txid'], 1])
|
||||
source = vin_transaction['vout'].find { |hash| hash['n'] == vin['vout'] }
|
||||
source_addresses << source['scriptPubKey']['addresses'][0]
|
||||
end.compact.uniq
|
||||
end
|
||||
|
||||
def load_balance_of_address!(address, _currency_id)
|
||||
address_with_balance = client.json_rpc(:listaddressgroupings)
|
||||
.flatten(1)
|
||||
.find { |addr| addr[0] == address }
|
||||
|
||||
if address_with_balance.blank?
|
||||
raise Peatio::Blockchain::UnavailableAddressBalanceError, address
|
||||
end
|
||||
|
||||
address_with_balance[1].to_d
|
||||
rescue Bitcoin::Client::Error => e
|
||||
raise Peatio::Blockchain::ClientError, e
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_transaction(tx_hash)
|
||||
tx_hash.fetch('vout')
|
||||
.select do |entry|
|
||||
entry.fetch('value').to_d > 0 &&
|
||||
entry['scriptPubKey'].has_key?('addresses')
|
||||
end
|
||||
.each_with_object([]) do |entry, formatted_txs|
|
||||
no_currency_tx =
|
||||
{ hash: tx_hash['txid'], txout: entry['n'],
|
||||
to_address: entry['scriptPubKey']['addresses'][0],
|
||||
amount: entry.fetch('value').to_d,
|
||||
status: 'success' }
|
||||
|
||||
# Build transaction for each currency belonging to blockchain.
|
||||
settings_fetch(:currencies).pluck(:id).each do |currency_id|
|
||||
formatted_txs << no_currency_tx.merge(currency_id: currency_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def client
|
||||
@client ||= Bitcoin::Client.new(settings_fetch(:server))
|
||||
end
|
||||
|
||||
def settings_fetch(key)
|
||||
@settings.fetch(key) { raise Peatio::Blockchain::MissingSettingError, key.to_s }
|
||||
end
|
||||
end
|
||||
end
|
||||
49
lib/peatio/bitcoin/client.rb
Normal file
49
lib/peatio/bitcoin/client.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
module Bitcoin
|
||||
class Client
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
class ConnectionError < Error; end
|
||||
|
||||
class ResponseError < Error
|
||||
def initialize(code, msg)
|
||||
super "#{msg} (#{code})"
|
||||
end
|
||||
end
|
||||
|
||||
extend Memoist
|
||||
|
||||
def initialize(endpoint, idle_timeout: 5)
|
||||
@json_rpc_endpoint = URI.parse(endpoint)
|
||||
@path = @json_rpc_endpoint.path.empty? ? "/" : @json_rpc_endpoint.path
|
||||
@idle_timeout = idle_timeout
|
||||
end
|
||||
|
||||
def json_rpc(method, params = [])
|
||||
response = connection.post \
|
||||
@path,
|
||||
{ jsonrpc: '1.0', method: method, params: params }.to_json,
|
||||
{ 'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json' }
|
||||
response.assert_success!
|
||||
response = JSON.parse(response.body)
|
||||
response['error'].tap { |error| raise ResponseError.new(error['code'], error['message']) if error }
|
||||
response.fetch('result')
|
||||
rescue Faraday::Error => e
|
||||
raise ConnectionError, e
|
||||
rescue StandardError => e
|
||||
raise Error, e
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connection
|
||||
@connection ||= Faraday.new(@json_rpc_endpoint) do |f|
|
||||
f.adapter :net_http_persistent, pool_size: 5, idle_timeout: @idle_timeout
|
||||
end.tap do |connection|
|
||||
unless @json_rpc_endpoint.user.blank?
|
||||
connection.basic_auth(@json_rpc_endpoint.user, @json_rpc_endpoint.password)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
61
lib/peatio/bitcoin/wallet.rb
Normal file
61
lib/peatio/bitcoin/wallet.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
module Bitcoin
|
||||
class Wallet < Peatio::Wallet::Abstract
|
||||
|
||||
DEFAULT_FEATURES = { skip_deposit_collection: false }.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, :address)
|
||||
|
||||
@currency = @settings.fetch(:currency) do
|
||||
raise Peatio::Wallet::MissingSettingError, :currency
|
||||
end.slice(:id, :base_factor, :options)
|
||||
end
|
||||
|
||||
def create_address!(_options = {})
|
||||
{ address: client.json_rpc(:getnewaddress) }
|
||||
rescue Bitcoin::Client::Error => e
|
||||
raise Peatio::Wallet::ClientError, e
|
||||
end
|
||||
|
||||
def create_transaction!(transaction, options = {})
|
||||
txid = client.json_rpc(:sendtoaddress,
|
||||
[
|
||||
transaction.to_address,
|
||||
transaction.amount,
|
||||
'',
|
||||
'',
|
||||
options[:subtract_fee].to_s == 'true' # subtract fee from transaction amount.
|
||||
])
|
||||
transaction.hash = txid
|
||||
transaction
|
||||
rescue Bitcoin::Client::Error => e
|
||||
raise Peatio::Wallet::ClientError, e
|
||||
end
|
||||
|
||||
def load_balance!
|
||||
client.json_rpc(:getbalance).to_d
|
||||
|
||||
rescue Bitcoin::Client::Error => e
|
||||
raise Peatio::Wallet::ClientError, e
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def client
|
||||
uri = @wallet.fetch(:uri) { raise Peatio::Wallet::MissingSettingError, :uri }
|
||||
@client ||= Client.new(uri, idle_timeout: 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user