Class: BlockIo::Client
- Inherits:
-
Object
- Object
- BlockIo::Client
- Defined in:
- lib/block_io/client.rb
Constant Summary collapse
- USER_AGENT =
'gem:block_io:' << VERSION.to_s
- CONTENT_TYPE =
'application/json; charset=UTF-8'
- ACCEPT_TYPE =
'application/json'
- KEEP_ALIVE =
'Keep-Alive'
- TIMEOUT =
60
Instance Attribute Summary collapse
-
#api_key ⇒ Object
readonly
Returns the value of attribute api_key.
-
#api_request_headers ⇒ Object
readonly
Returns the value of attribute api_request_headers.
-
#network ⇒ Object
readonly
Returns the value of attribute network.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Instance Method Summary collapse
- #create_and_sign_transaction(data, keys = []) ⇒ Object
-
#initialize(args = {}) ⇒ Client
constructor
A new instance of Client.
- #method_missing(m, *args) ⇒ Object
- #padded_f(d) ⇒ Object
- #summarize_prepared_transaction(data) ⇒ Object
Constructor Details
#initialize(args = {}) ⇒ Client
Returns a new instance of Client.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/block_io/client.rb', line 13 def initialize(args = {}) # api_key # pin # version # hostname # proxy # pool_size # keys raise 'Must provide an API Key.' unless args.key?(:api_key) and args[:api_key].to_s.size > 0 @api_key = args[:api_key] @pin = args[:pin] @version = args[:version] || 2 @hostname = args[:hostname] || 'block.io' @keys = {} # prepare proxy settings if provided proxy = args[:proxy] || {} if proxy.keys.size > 0 then raise Exception.new('Must specify hostname, port, username, password if using a proxy.') if [:url, :username, :password].any?{|x| !proxy.key?(x)} @proxy = {:proxy => proxy[:url], :proxyuserpwd => "#{proxy[:username]}:#{proxy[:password]}"}.freeze else @proxy = {} end @api_request_headers = {'User-Agent' => USER_AGENT, 'Content-Type' => CONTENT_TYPE, 'Accept' => ACCEPT_TYPE, 'Expect' => '', 'Connection' => KEEP_ALIVE, 'Host' => @hostname}.freeze # this will get populated after a successful API call @network = nil end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(m, *args) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/block_io/client.rb', line 47 def method_missing(m, *args) method_name = m.to_s raise Exception.new('Must provide arguments as a Hash.') unless args.size <= 1 and args.all?{|x| x.is_a?(Hash)} raise Exception.new('Parameter keys must be symbols. For instance: :label => "default" instead of "label" => "default"') unless args[0].nil? or args[0].keys.all?{|x| x.is_a?(Symbol)} raise Exception.new('Cannot pass PINs to any calls. PINs can only be set when initiating this library.') if !args[0].nil? and args[0].key?(:pin) raise Exception.new('Do not specify API Keys here. Initiate a new BlockIo object instead if you need to use another API Key.') if !args[0].nil? and args[0].key?(:api_key) if method_name.eql?('prepare_sweep_transaction') then # we need to ensure @network is set before we allow this # we need to send only the public key, not the given private key # we're sweeping from an address internal_prepare_sweep_transaction(args[0], method_name) else api_call({:method_name => method_name, :params => args[0] || {}}) end end |
Instance Attribute Details
#api_key ⇒ Object (readonly)
Returns the value of attribute api_key.
5 6 7 |
# File 'lib/block_io/client.rb', line 5 def api_key @api_key end |
#api_request_headers ⇒ Object (readonly)
Returns the value of attribute api_request_headers.
5 6 7 |
# File 'lib/block_io/client.rb', line 5 def api_request_headers @api_request_headers end |
#network ⇒ Object (readonly)
Returns the value of attribute network.
5 6 7 |
# File 'lib/block_io/client.rb', line 5 def network @network end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
5 6 7 |
# File 'lib/block_io/client.rb', line 5 def version @version end |
Instance Method Details
#create_and_sign_transaction(data, keys = []) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/block_io/client.rb', line 115 def create_and_sign_transaction(data, keys = []) # takes data from prepare_transaction, prepare_dtrust_transaction, prepare_sweep_transaction # creates the transaction given the inputs and outputs from data # signs the transaction using keys (if not provided, decrypts the key using the PIN) set_network(data['data']['network']) if data['data'].key?('network') raise 'Data must be contain one or more inputs' unless data['data']['inputs'].size > 0 raise 'Data must contain one or more outputs' unless data['data']['outputs'].size > 0 raise 'Data must contain information about addresses' unless data['data']['input_address_data'].size > 0 # TODO make stricter private_keys = keys.map{|x| Key.from_private_key_hex(x)} inputs = data['data']['inputs'] outputs = data['data']['outputs'] tx = Bitcoin::Tx.new # populate the inputs tx.in << inputs.map do |input| Bitcoin::TxIn.new(:out_point => Bitcoin::OutPoint.from_txid(input['previous_txid'], input['previous_output_index'])) end tx.in.flatten! # populate the outputs tx.out << outputs.map do |output| Bitcoin::TxOut.new(:value => (BigDecimal(output['output_value']) * BigDecimal(100000000)).to_i, :script_pubkey => Bitcoin::Script.parse_from_addr(output['receiving_address'])) end tx.out.flatten! # some protection against misbehaving machines and/or code raise Exception.new('Expected unsigned transaction ID mismatch. Please report this error to [email protected].') unless (data['data']['expected_unsigned_txid'].nil? or data['data']['expected_unsigned_txid'].eql?(tx.txid)) # extract key encrypted_key = data['data']['user_key'] if !encrypted_key.nil? and !@keys.key?(encrypted_key['public_key']) then # decrypt the key with PIN raise Exception.new('PIN not set and no keys provided. Cannot sign transaction.') unless !@pin.nil? or @keys.size > 0 key = Helper.dynamicExtractKey(encrypted_key, @pin) raise Exception.new('Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.') unless key.public_key_hex.eql?(encrypted_key['public_key']) # store this key for later use @keys[key.public_key_hex] = key end # store the provided keys, if any, for later use @keys.merge!(private_keys.map{|key| [key.public_key_hex, key]}.to_h) signatures = [] if @keys.size > 0 then # try to sign whatever we can here and give the user the data back # Block.io will check to see if all signatures are present, or return an error otherwise saying insufficient signatures provided i = 0 loop do input = inputs[i] break if input.nil? input_address_data = data['data']['input_address_data'].detect{|d| d['address'].eql?(input['spending_address'])} sighash_for_input = Helper.getSigHashForInput(tx, i, input, input_address_data) # in bytes signatures << input_address_data['public_keys'].map do |signer_public_key| # sign what we can and append signatures to the signatures object next unless @keys.key?(signer_public_key) { 'input_index' => i, 'public_key' => signer_public_key, 'signature' => @keys[signer_public_key].sign(sighash_for_input).unpack1('H*') # in hex } end i += 1 # go to next input end end signatures.flatten! signatures.compact! # if we have everything we need for this transaction, just finalize the transaction if Helper.allSignaturesPresent?(tx, inputs, signatures, data['data']['input_address_data']) then Helper.finalizeTransaction(tx, inputs, signatures, data['data']['input_address_data']) signatures = [] # no signatures left to append end # reset keys @keys = {} # the response for submitting the transaction {'tx_type' => data['data']['tx_type'], 'tx_hex' => tx.to_hex, 'signatures' => (signatures.size.eql?(0) ? nil : signatures)} end |
#padded_f(d) ⇒ Object
67 68 69 70 71 72 |
# File 'lib/block_io/client.rb', line 67 def padded_f(d) # returns a padded decimal to 8 decimal places b = BigDecimal(d).to_s('F') b << '0' * (8 - (b.size - b.index('.') - 1)) b end |
#summarize_prepared_transaction(data) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/block_io/client.rb', line 74 def summarize_prepared_transaction(data) # takes the response from prepare_transaction/prepare_dtrust_transaction/prepare_sweep_transaction # returns the network fee being paid, the blockio fee being paid, amounts being sent input_sum = data['data']['inputs'].map{|input| BigDecimal(input['input_value'])}.inject(:+) output_values = [BigDecimal(0)] blockio_fees = [BigDecimal(0)] change_amounts = [BigDecimal(0)] i = 0 loop do output = data['data']['outputs'][i] break if output.nil? i += 1 if output['output_category'].eql?('blockio-fee') then blockio_fees << BigDecimal(output['output_value']) elsif output['output_category'].eql?('change') then change_amounts << BigDecimal(output['output_value']) else # user-specified output_values << BigDecimal(output['output_value']) end end output_sum = output_values.inject(:+) blockio_fee = blockio_fees.inject(:+) change_amount = change_amounts.inject(:+) network_fee = input_sum - output_sum - blockio_fee - change_amount { 'network' => data['data']['network'], 'network_fee' => padded_f(network_fee), 'blockio_fee' => padded_f(blockio_fee), 'total_amount_to_send' => padded_f(output_sum) } end |