class OpenAssets::Api

Attributes

config[R]
output_cache[R]
provider[R]
tx_cache[R]

Public Class Methods

new(config = nil) click to toggle source
# File lib/openassets/api.rb, line 17
def initialize(config = nil)
  @config = {:network => 'mainnet',
             :provider => 'bitcoind', :cache => 'cache.db',
             :dust_limit => 600, :default_fees => 10000, :min_confirmation => 1, :max_confirmation => 9999999,
             :rpc => {:host => 'localhost', :port => 8332 , :user => '', :password => '', :wallet => '',
                      :schema => 'https', :timeout => 60, :open_timeout => 60}}
  if config
    @config.update(config)
  end
  OpenAssets.configuration = @config
  if @config[:provider] == 'bitcoind'
    @provider = Provider::BitcoinCoreProvider.new(@config[:rpc])
  else
    raise OpenAssets::Error, 'specified unsupported provider.'
  end
  @tx_cache = Cache::TransactionCache.new(@config[:cache])
  @output_cache = Cache::OutputCache.new(@config[:cache])
  change_network
end

Public Instance Methods

burn_asset(oa_address, asset_id, fees = nil, mode = 'broadcast') click to toggle source

Creates a transaction for burn asset. @param oa_address The open asset address to burn asset. @param asset_id The asset ID identifying the asset to burn. @param fees The fess in satoshis for the transaction. @param mode 'broadcast' (default) for signing and broadcasting the transaction, 'signed' for signing the transaction without broadcasting, 'unsigned' for getting the raw unsigned transaction without broadcasting“”“='broadcast'

# File lib/openassets/api.rb, line 192
def burn_asset(oa_address, asset_id, fees = nil, mode = 'broadcast')
  unspents = get_unspent_outputs([oa_address_to_address(oa_address)])
  tx = create_tx_builder.burn_asset(unspents, asset_id, fees.nil? ? @config[:default_fees]: fees)
  process_transaction(tx, mode)
end
get_balance(address = nil) click to toggle source

Returns the balance in both bitcoin and colored coin assets for all of the addresses available in your Bitcoin Core wallet. @param [String] address The open assets address. if unspecified nil.

# File lib/openassets/api.rb, line 57
def get_balance(address = nil)
  outputs = get_unspent_outputs(address.nil? ? [] : [oa_address_to_address(address)])
  colored_outputs = outputs.map{|o|o.output}
  sorted_outputs = colored_outputs.sort_by { |o|o.script.to_s}
  groups = sorted_outputs.group_by{|o| o.script.to_s}
  result = groups.map{|k, v|
    btc_address = v[0].script.addresses.first
    sorted_script_outputs = v.sort_by{|o|o.asset_id unless o.asset_id}
    group_assets = sorted_script_outputs.group_by{|o|o.asset_id}.select{|k,v| !k.nil?}
    assets = group_assets.map{|asset_id, outputs|
      {
          'asset_id' => asset_id,
          'quantity' => outputs.inject(0) { |sum, o| sum + o.asset_quantity }.to_s,
          'amount' => outputs.inject(0) { |sum, o| sum + o.asset_amount }.to_s,
          'asset_definition_url' => outputs[0].asset_definition_url,
          'proof_of_authenticity' => outputs[0].proof_of_authenticity
      }
    }
    {
        'address' => btc_address,
        'oa_address' => (btc_address.nil? || btc_address.is_a?(Array)) ? nil : address_to_oa_address(btc_address),
        'value' => satoshi_to_coin(v.inject(0) { |sum, o|sum +  o.value}),
        'assets' => assets,
        'account' => v[0].account
    }
  }
  address.nil? ? result : result.select{|r|r['oa_address'] == address}
end
get_color_outputs_from_tx(tx) click to toggle source

get coloring outputs from tx. @param [Bitcoin::Tx] tx regular transaction. @return [Array] array of coloring output.

# File lib/openassets/api.rb, line 236
def get_color_outputs_from_tx(tx)
  unless tx.coinbase_tx?
    tx.outputs.each_with_index { |out, i|
      # check marker output.
      marker_output_hex = OpenAssets::Protocol::MarkerOutput.parse_script(out.script_pubkey)
      unless marker_output_hex.nil?
        marker_output = OpenAssets::Protocol::MarkerOutput.parse_from_payload(marker_output_hex.htb)
        prev_outs = tx.inputs.map {|input|get_output(input.out_point.txid, input.out_point.index)}
        asset_ids = compute_asset_ids(prev_outs, i, tx, marker_output.asset_quantities)
        return asset_ids unless asset_ids.nil?
      end
    }
  end
  tx.outputs.map{|out| OpenAssets::Protocol::TransactionOutput.new(out.value, out.script_pubkey, nil, 0, OpenAssets::Protocol::OutputType::UNCOLORED)}
end
get_output(txid, index) click to toggle source

get output @param [String] txid txid of output. @param [Integer] index index of output. @return

# File lib/openassets/api.rb, line 221
def get_output(txid, index)
  if output_cache
    cached = output_cache.get(txid, index)
    return cached unless cached.nil?
  end
  decode_tx = load_cached_tx(txid)
  tx = Bitcoin::Tx.parse_from_payload(decode_tx.htb)
  colored_outputs = get_color_outputs_from_tx(tx)
  colored_outputs.each_with_index { |o, index| output_cache.put(txid, index, o)} if output_cache
  colored_outputs[index]
end
get_outputs_from_txid(txid, use_cache = false) click to toggle source

Get tx outputs. @param txid Transaction ID. @param use_cache If specified true use cache.(default value is false) @return Return array of the transaction output Hash with coloring information.

# File lib/openassets/api.rb, line 256
def get_outputs_from_txid(txid, use_cache = false)
  tx = get_tx(txid, use_cache)
  outputs = get_color_outputs_from_tx(tx)
  outputs.map.with_index{|out, i|out.to_hash.merge({'txid' => txid, 'vout' => i})}
end
get_tx(txid, use_cache = true) click to toggle source

Get tx. (This method returns plain Bitcoin::Tx object, so it not contains open asset information.) @param txid Transaction ID. @return Return the Bitcoin::Tx.

# File lib/openassets/api.rb, line 265
def get_tx(txid, use_cache = true)
  decode_tx = use_cache ? load_cached_tx(txid) : load_tx(txid)
  Bitcoin::Tx.parse_from_payload(decode_tx.htb)
end
get_unspent_outputs(addresses) click to toggle source

Get unspent outputs. @param [Array] addresses The array of Bitcoin address. @return [Array] The array of unspent outputs.

# File lib/openassets/api.rb, line 201
def get_unspent_outputs(addresses)
  validate_address(addresses)
  unspents = provider.list_unspent(addresses, @config[:min_confirmation], @config[:max_confirmation])
  result = unspents.map{|item|
    output_result = get_output(item['txid'], item['vout'])
    output_result.account = item['account']
    output = OpenAssets::Transaction::SpendableOutput.new(
        Bitcoin::OutPoint.new(item['txid'].rhex, item['vout']), output_result)
    output.confirmations = item['confirmations']
    output.spendable = item['spendable']
    output.solvable = item['solvable']
    output
  }
  result
end
is_testnet?() click to toggle source
# File lib/openassets/api.rb, line 41
def is_testnet?
  @config[:network] == 'testnet'
end
issue_asset(from, amount, metadata = nil, to = nil, fees = nil, mode = 'broadcast', output_qty = 1) click to toggle source

Creates a transaction for issuing an asset. @param from The open asset address to issue the asset from. @param amount The amount of asset units to issue. @param to The open asset address to send the asset to; if unspecified, the assets are sent back to the issuing address. @param metadata The metadata to embed in the transaction. The asset definition pointer defined by this metadata. @param fees The fess in satoshis for the transaction. @param mode Specify the following mode. 'broadcast' (default) for signing and broadcasting the transaction, 'signed' for signing the transaction without broadcasting, 'unsigned' for getting the raw unsigned transaction without broadcasting“”“='broadcast' @param output_qty The number of divides the issue output. Default value is 1. Ex. amount = 125 and output_qty = 2, asset quantity = [62, 63] and issue TxOut is two. @return The Bitcoin::Tx object.

# File lib/openassets/api.rb, line 99
def issue_asset(from, amount, metadata = nil, to = nil, fees = nil, mode = 'broadcast', output_qty = 1)
  to = from if to.nil?
  colored_outputs = get_unspent_outputs([oa_address_to_address(from)])
  issue_param = OpenAssets::Transaction::TransferParameters.new(colored_outputs, to, from, amount, output_qty)
  tx = create_tx_builder.issue_asset(issue_param, metadata, fees.nil? ? @config[:default_fees]: fees)
  tx = process_transaction(tx, mode)
  tx
end
list_unspent(oa_address_list = []) click to toggle source

get UTXO for colored coins. @param [Array] oa_address_list Obtain the balance of this open assets address only, or all addresses if unspecified. @return [Array] Return array of the unspent information Hash.

# File lib/openassets/api.rb, line 48
def list_unspent(oa_address_list = [])
  btc_address_list = oa_address_list.map { |oa_address| oa_address_to_address(oa_address)}
  outputs = get_unspent_outputs(btc_address_list)
  result = outputs.map{|out| out.to_hash}
  result
end
send_asset(from, asset_id, amount, to, fees = nil, mode = 'broadcast', output_qty = 1) click to toggle source

Creates a transaction for sending an asset from an address to another. @param from The open asset address to send the asset from. @param asset_id The asset ID identifying the asset to send. @param amount The amount of asset units to send. @param to The open asset address to send the asset to. @param fees The fess in satoshis for the transaction. @param mode 'broadcast' (default) for signing and broadcasting the transaction, 'signed' for signing the transaction without broadcasting, 'unsigned' for getting the raw unsigned transaction without broadcasting“”“='broadcast' @return The resulting transaction.

# File lib/openassets/api.rb, line 118
def send_asset(from, asset_id, amount, to, fees = nil, mode = 'broadcast', output_qty = 1)
  colored_outputs = get_unspent_outputs([oa_address_to_address(from)])
  asset_transfer_spec = OpenAssets::Transaction::TransferParameters.new(colored_outputs, to, from, amount, output_qty)
  tx = create_tx_builder.transfer_asset(asset_id, asset_transfer_spec, from, fees.nil? ? @config[:default_fees]: fees)
  tx = process_transaction(tx, mode)
  tx
end
send_assets(from, send_asset_params, fees = nil, mode = 'broadcast') click to toggle source

Creates a transaction for sending multiple asset from an address to another. @param from The open asset address to send the asset from when send_asset_param hasn't from. to send the bitcoins from, if needed. where to send bitcoin change, if any. @param[Array] send_asset_params The send Asset information(asset_id, amount, to, from). @param fees The fess in satoshis for the transaction. @param mode 'broadcast' (default) for signing and broadcasting the transaction, 'signed' for signing the transaction without broadcasting, 'unsigned' for getting the raw unsigned transaction without broadcasting“”“='broadcast' @return The resulting transaction.

# File lib/openassets/api.rb, line 135
def send_assets(from, send_asset_params, fees = nil, mode = 'broadcast')
  transfer_specs = send_asset_params.map{ |param|
    colored_outputs = get_unspent_outputs([oa_address_to_address(param.from || from)])
    [param.asset_id, OpenAssets::Transaction::TransferParameters.new(colored_outputs, param.to, param.from || from, param.amount)]
  }
  btc_transfer_spec = OpenAssets::Transaction::TransferParameters.new(
      get_unspent_outputs([oa_address_to_address(from)]), nil, oa_address_to_address(from), 0)
  tx = create_tx_builder.transfer_assets(transfer_specs, btc_transfer_spec, fees.nil? ? @config[:default_fees]: fees)
  tx = process_transaction(tx, mode)
  tx
end
send_bitcoin(from, amount, to, fees = nil, mode = 'broadcast', output_qty = 1) click to toggle source

Creates a transaction for sending bitcoins from an address to another. @param from The address to send the bitcoins from. @param amount The amount of satoshis to send. @param to The address to send the bitcoins to. @param fees The fess in satoshis for the transaction. @param mode 'broadcast' (default) for signing and broadcasting the transaction, 'signed' for signing the transaction without broadcasting, 'unsigned' for getting the raw unsigned transaction without broadcasting“”“='broadcast' @param [Integer] output_qty The number of divides the issue output. Default value is 1. Ex. amount = 125 and output_qty = 2, asset quantity = [62, 63] and issue TxOut is two. @return The resulting transaction.

# File lib/openassets/api.rb, line 158
def send_bitcoin(from, amount, to, fees = nil, mode = 'broadcast', output_qty = 1)
  validate_address([from, to])
  colored_outputs = get_unspent_outputs([from])
  btc_transfer_spec = OpenAssets::Transaction::TransferParameters.new(colored_outputs, to, from, amount, output_qty)
  tx = create_tx_builder.transfer_btc(btc_transfer_spec, fees.nil? ? @config[:default_fees]: fees)
  process_transaction(tx, mode)
end
send_bitcoins(from, send_params, fees = nil, mode = 'broadcast') click to toggle source

Creates a transaction for sending multiple bitcoins from an address to others. @param from The address to send the bitcoins from. @param[Array] send_params The send information(amount of satoshis and to). @param fees The fees in satoshis for the transaction. @param mode 'broadcast' (default) for signing and broadcasting the transaction, 'signed' for signing the transaction without broadcasting, 'unsigned' for getting the raw unsigned transaction without broadcasting“”“='broadcast' @return The resulting transaction.

# File lib/openassets/api.rb, line 174
def send_bitcoins(from, send_params, fees = nil, mode = 'broadcast')
  colored_outputs = get_unspent_outputs([from])
  btc_transfer_specs = send_params.map{|param|
    OpenAssets::Transaction::TransferParameters.new(colored_outputs, param.to, from, param.amount)
  }
  tx = create_tx_builder.transfer_btcs(btc_transfer_specs, fees.nil? ? @config[:default_fees]: fees)
  tx = process_transaction(tx, mode)
  tx
end

Private Instance Methods

change_network() click to toggle source
# File lib/openassets/api.rb, line 368
def change_network
  case @config[:network]
    when 'testnet'
      Bitcoin.chain_params = :testnet
    when 'regtest'
      Bitcoin.chain_params = :regtest
    when 'litecoin'
      Bitcoin.chain_params = :litecoin
    when 'litecoin_testnet'
      Bitcoin.chain_params = :litecoin_testnet
    else
      Bitcoin.chain_params = :mainnet
  end
end
compute_asset_ids(prev_outs, marker_output_index, tx, asset_quantities) click to toggle source

@param [Array prev_outs The outputs referenced by the inputs of the transaction. @param [Integer] marker_output_index The position of the marker output in the transaction. @param [Bitcoin::Tx] tx The transaction. @param [Array] asset_quantities The list of asset quantities of the outputs.

# File lib/openassets/api.rb, line 275
def compute_asset_ids(prev_outs, marker_output_index, tx, asset_quantities)
  outputs = tx.outputs
  return nil if asset_quantities.length > outputs.length - 1 || prev_outs.length == 0
  result = []

  marker_output = outputs[marker_output_index]

  # Add the issuance outputs
  issuance_asset_id = script_to_asset_id(prev_outs[0].script.to_payload.bth)

  for i in (0..marker_output_index-1)
    value = outputs[i].value
    script = outputs[i].script_pubkey
    if i < asset_quantities.length && asset_quantities[i] > 0
      hex = OpenAssets::Protocol::MarkerOutput.parse_script(marker_output.script_pubkey)
      metadata = OpenAssets::Protocol::MarkerOutput.parse_from_payload(hex.htb).metadata
      if (metadata.nil? || metadata.length == 0) && prev_outs[0].script.p2sh?
        metadata = parse_issuance_p2sh_pointer(tx.in[0].script_sig.to_payload)
      end
      metadata = '' unless metadata
      output = OpenAssets::Protocol::TransactionOutput.new(value, script, issuance_asset_id, asset_quantities[i], OpenAssets::Protocol::OutputType::ISSUANCE, metadata)
    else
      output = OpenAssets::Protocol::TransactionOutput.new(value, script, nil, 0, OpenAssets::Protocol::OutputType::ISSUANCE)
    end
    result << output
  end

  # Add the marker output
  result << OpenAssets::Protocol::TransactionOutput.new(marker_output.value, marker_output.script_pubkey, nil, 0, OpenAssets::Protocol::OutputType::MARKER_OUTPUT)

  # remove invalid marker
  remove_outputs = []
  for i in (marker_output_index + 1)..(outputs.length-1)
    marker_output_payload = OpenAssets::Protocol::MarkerOutput.parse_script(outputs[i].script_pubkey)
    unless marker_output_payload.nil?
      remove_outputs << outputs[i]
      result << OpenAssets::Protocol::TransactionOutput.new(
          outputs[i].value, outputs[i].script_pubkey, nil, 0, OpenAssets::Protocol::OutputType::MARKER_OUTPUT)
      next
    end
  end
  remove_outputs.each{|o|outputs.delete(o)}

  # Add the transfer outputs
  input_enum = prev_outs.each
  input_units_left = 0
  index = 0
  for i in (marker_output_index + 1)..(outputs.length-1)
    output_asset_quantity = (i <= asset_quantities.length) ? asset_quantities[i-1] : 0
    output_units_left = output_asset_quantity
    asset_id,metadata = nil
    while output_units_left > 0
      index += 1
      if input_units_left == 0
        begin
        current_input = input_enum.next
        input_units_left = current_input.asset_quantity
        rescue StopIteration => e
          return nil
        end
      end
      unless current_input.asset_id.nil?
        progress = [input_units_left, output_units_left].min
        output_units_left -= progress
        input_units_left -= progress
        if asset_id.nil?
          # This is the first input to map to this output
          asset_id = current_input.asset_id
          metadata = current_input.metadata
        elsif asset_id != current_input.asset_id
          return nil
        end
      end
    end
    result << OpenAssets::Protocol::TransactionOutput.new(outputs[i].value, outputs[i].script_pubkey,
                                                          asset_id, output_asset_quantity, OpenAssets::Protocol::OutputType::TRANSFER, metadata)
  end
  result
end
create_tx_builder() click to toggle source
# File lib/openassets/api.rb, line 383
def create_tx_builder
  if @config[:default_fees] == :auto
    # Estimate a transaction fee rate (satoshis/KB) if fee is specified by :auto
    efr = coin_to_satoshi(provider.estimatefee(1).to_s).to_i
    if efr < 0
      # Negative efr means "estimatefee" of bitcoin-api returns false
      # In this case, use default minimum fees rate (10_000 satoshis/KB)
      efr = 10_000
    end
    OpenAssets::Transaction::TransactionBuilder.new(@config[:dust_limit], efr)
  else
    # If fee is specified by a fixed value (or the default value)
    OpenAssets::Transaction::TransactionBuilder.new(@config[:dust_limit])
  end

end
load_cached_tx(txid) click to toggle source
# File lib/openassets/api.rb, line 406
def load_cached_tx(txid)
  return load_tx(txid) unless tx_cache
  decode_tx = tx_cache.get(txid)
  if decode_tx.nil?
    decode_tx = load_tx(txid)
    tx_cache.put(txid, decode_tx)
  end
  decode_tx
end
load_tx(txid) click to toggle source
# File lib/openassets/api.rb, line 400
def load_tx(txid)
  decode_tx = provider.get_transaction(txid, 0)
  raise OpenAssets::Transaction::TransactionBuildError, "txid #{txid} could not be retrieved." if decode_tx.nil?
  decode_tx
end
parse_issuance_p2sh_pointer(script_sig) click to toggle source

parse issuance p2sh which contains asset definition pointer @param [String] script_sig script_sig with binary format. @return [String] Return asset definition pointer string i f script_sig has asset definition pointer, otherwise nil

# File lib/openassets/api.rb, line 419
def parse_issuance_p2sh_pointer(script_sig)
  script = Bitcoin::Script.parse_from_payload(script_sig).chunks.last.pushed_data
  redeem_script = Bitcoin::Script.parse_from_payload(script)
  return nil unless redeem_script.chunks[1].ord == Bitcoin::Opcodes::OP_DROP
  return nil unless redeem_script.chunks[0].pushdata?
  asset_def = to_bytes(redeem_script.chunks[0].pushed_data.bth)[0..-1].map{|x|x.to_i(16).chr}.join
  asset_def && asset_def.start_with?('u=') ? asset_def : nil
end
process_transaction(tx, mode) click to toggle source
# File lib/openassets/api.rb, line 355
def process_transaction(tx, mode)
  if mode == 'broadcast' || mode == 'signed'
    # sign the transaction
    signed_tx = provider.sign_transaction(tx.to_payload.bth)
    if mode == 'broadcast'
      puts provider.send_transaction(signed_tx.to_payload.bth)
    end
    signed_tx
  else
    tx
  end
end