#!/usr/bin/env ruby # frozen_string_literal: true # Copyright (c) 2019-2020 Yegor Bugayenko # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the 'Software'), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. STDOUT.sync = true require 'slop' require 'backtrace' require 'retriable_proxy' require_relative '../lib/sibit' require_relative '../lib/sibit/version' require_relative '../lib/sibit/blockchain' require_relative '../lib/sibit/btc' require_relative '../lib/sibit/bitcoinchain' require_relative '../lib/sibit/earn' require_relative '../lib/sibit/firstof' begin begin opts = Slop.parse(ARGV, strict: true, help: true) do |o| o.banner = "Usage (#{Sibit::VERSION}): sibit [options] command [args] Commands are: price: Get current price of BTC in USD fees: Get currently recommended transaction fees latest: Get hash of the latest block generate: Generate a new private key create: Create a public Bitcoin address from the key balance: Check the balance of the Bitcoin address pay: Send a new Bitcoin transaction Options are:" o.string '--proxy', 'HTTPS proxy for all requests, e.g. "localhost:3128"' o.integer( '--attempts', 'How many times should we try before failing', default: 1 ) o.bool '--dry', 'Don\'t send a real payment, run in a read-only mode' o.bool '--help', 'Read this: https://github.com/yegor256/sibit' do puts o exit end o.bool '--verbose', 'Print all possible debug messages' o.array( '--api', 'Ordered List of APIs to use, e.g. "earn,blockchain,btc,bitcoinchain"', default: %w[earn blockchain btc bitcoinchain] ) end rescue Slop::Error => ex raise ex.message end raise 'Try --help' if opts.arguments.empty? log = Sibit::Log.new(opts[:verbose] ? STDOUT : nil) http = opts[:proxy] ? Sibit::HttpProxy.new(opts[:proxy]) : Sibit::Http.new apis = opts[:api].map(&:downcase).map do |a| api = nil if a == 'blockchain' api = Sibit::Blockchain.new(http: http, log: log, dry: opts[:dry]) elsif a == 'btc' api = Sibit::Btc.new(http: http, log: log, dry: opts[:dry]) elsif a == 'bitcoinchain' api = Sibit::Bitcoinchain.new(http: http, log: log, dry: opts[:dry]) elsif a == 'earn' api = Sibit::Earn.new(http: http, log: log, dry: opts[:dry]) else raise Sibit::Error, "Unknown API \"#{a}\"" end api = RetriableProxy.for_object(api, on: Sibit::Error) if opts[:attempts] > 1 api end sibit = Sibit.new(log: log, api: Sibit::FirstOf.new(apis, log: log)) case opts.arguments[0] when 'price' puts sibit.price when 'fees' fees = sibit.fees text = %i[S M L XL].map do |m| sat = fees[m] * 250 usd = sat * sibit.price / 100_000_000 "#{m}: #{sat}sat / $#{format('%.02f', usd)}" end.join("\n") puts text when 'latest' puts sibit.latest when 'generate' puts sibit.generate when 'create' pvt = opts.arguments[1] raise 'Private key argument is required' if pvt.nil? puts sibit.create(pvt) when 'balance' address = opts.arguments[1] raise 'Address argument is required' if address.nil? puts sibit.balance(address) when 'pay' amount = opts.arguments[1] raise 'Amount argument is required' if amount.nil? amount = amount.to_i if /^[0-9]+$/.match?(amount) fee = opts.arguments[2] raise 'Miners fee argument is required' if fee.nil? fee = fee.to_i if /^[0-9]+$/.match?(fee) sources = opts.arguments[3] raise 'Addresses argument is required' if sources.nil? target = opts.arguments[4] raise 'Target argument is required' if target.nil? change = opts.arguments[5] raise 'Change argument is required' if change.nil? puts sibit.pay( amount, fee, sources.split(',').map { |p| p.split(':') }.to_h, target, change ) else raise "Command #{opts.arguments[0]} is not supported" end rescue StandardError => ex if opts[:verbose] puts Backtrace.new(ex).to_s else puts "ERROR: #{ex.message}" end exit(255) end