Bitcoin! Getting transaction info for an address.
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

Just starting out with Bitcoin development, so bear with me.

I want to be able to get information on a given Bitcoin address. In particular, I want to query a list of incoming transactions (deposits) to that address, and get the BTC amount and date of those transactions.

I want to do this using third party API(s), specifically BlockExplorer, Coinbase, or Blockchain.info.

Deliverable: A Ruby function that takes an address and produces an array of the incoming transactions. Each transaction should list at least the amount and date.

This is probably super easy, just confused about how transactions are represented and about btc in general haha. Let me know if you have any questions.

awarded to ZeroCool

Crowdsource coding tasks.

3 Solutions


The Received: Address field lists incoming transactions. Unfortunately I couldn't find an API method (or I'm just blind) that returns appropriate transactions, an example of a page is here: http://blockexplorer.com/address/1Cvvr8AsCfbbVQ2xoWiFD1Gb2VRbGsEf28

My solution "scrapes" the website and lists transactions in an array.

require 'open-uri'

class Transaction
    def initialize(ammount, date)
        @ammount = ammount
        @date = date
    end

    def to_s
        "Date: #{@date}, Ammount: #{@ammount}\n"
    end
end

class TransactionParser
    def initialize(address, type)
        @address = address
        @type = type
    end

    def get_string_between(my_string, start_at, end_at)
        my_string = " #{my_string}"
        ini = my_string.index(start_at)
        return my_string if ini == 0
        ini += start_at.length
        length = my_string.index(end_at, ini).to_i - ini
        my_string[ini,length]
    end

    def get_array()
        transactions = []

        source = open("http://blockexplorer.com/address/#{@address}").read
        str = get_string_between(source, "<table class=\"txtable\">", "</table>")
        str = str[str.index("</tr>")+5, str.length]

        # Get all starts of transactions and ends
        starts = str.enum_for(:scan,/<tr>/).map {Regexp.last_match.begin(0) }
        ends = str.enum_for(:scan,/<\/tr>/).map {Regexp.last_match.begin(0) }

        count = 0
        for i in starts:
            data = str[i, ends[count]].split(/\n/)
            date = data[2]
            date = date[date.index("</a>")+6, date.index(")")]
            date = date[0, date.length-6]
            ammount = data[3]
            ammount = ammount[ammount.index("<td>")+4, ammount.index("</td>")-4]
            type = data[4]

            if(@type == "al"):
                transactions.push(Transaction.new(ammount, date))
            end
            if(@type == "ra"):
                if type.include? 'Received: Address':
                    transactions.push(Transaction.new(ammount, date))
                end
            end
            if(@type == "sp"):
                if type.include? 'Sent: Pubkey':
                    transactions.push(Transaction.new(ammount, date))
                end
            end
            if(@type == "sa"):
                if type.include? 'Sent: Address':
                    transactions.push(Transaction.new(ammount, date))
                end
            end

            count = count + 1
        end

        return transactions

    end

end


# How to use second parameter:
# ra - received address // Received: Address
# sa - sent address // Sent: Address
# sp - sent public key // Sent: Pubkey
# al - all transactions
y = TransactionParser.new("1Cvvr8AsCfbbVQ2xoWiFD1Gb2VRbGsEf28", "al")
print y.get_array()

Well I don't do RUBY, but here it is in coffeescript (looping through the JSON retrieved from blockchain.info). Perhaps @ZeroCool could translate this...

Code

$ = require "jquery"

bitcoin_address = "1Cvvr8AsCfbbVQ2xoWiFD1Gb2VRbGsEf28"

url = "https://blockchain.info/rawaddr/#{bitcoin_address}"

# get the JSON feed for an address, which will automatically parse into object `d`
$.get url, (d)->

  # create transactions array to populate with results
  transactions = []

  # loop through the transactions
  for tx in d.txs

    # build an object of inputs, with the address as the key, and bitcoin amount in satoshi as the value
    ins = {}
    for inputs in tx.inputs

      # if the key does not exist, then create it
      if !ins[inputs.prev_out.addr] then ins[inputs.prev_out.addr] = 0

      # add the value to the key
      ins[inputs.prev_out.addr] += inputs.prev_out.value

    # same as for inputs...
    outs = {}
    for out in tx.out
      if !outs[out.addr] then outs[out.addr] = 0
      outs[out.addr] += out.value

    # check if the transaction has our target address as output, and not input
    if !ins[bitcoin_address] && outs[bitcoin_address]

      # add the transaction to the array
      transactions.push
        date: (new Date(tx.time*1000)).toISOString()
        value: outs[bitcoin_address]

  # check the transactions array, which is now populated
  console.log transactions

Results

[ { date: '2013-04-05T17:38:40.000Z', value: 10000000 },
  { date: '2013-03-22T20:56:22.000Z', value: 1900001 },
  { date: '2013-03-22T14:00:55.000Z', value: 999997 },
  { date: '2013-03-19T21:11:15.000Z', value: 4000000 },
  { date: '2013-03-19T21:09:43.000Z', value: 4000000 },
  { date: '2013-03-12T06:00:14.000Z', value: 4 },
  { date: '2013-03-01T00:17:40.000Z', value: 30000000 },
  { date: '2013-02-19T01:57:27.000Z', value: 38978929 },
  { date: '2013-02-13T05:46:54.000Z', value: 10000000 },
  { date: '2013-01-21T22:14:46.000Z', value: 30000000 },
  { date: '2013-01-20T06:57:15.000Z', value: 1000000 },
  { date: '2013-01-06T06:34:43.000Z', value: 10451960 },
  { date: '2013-01-05T23:50:43.000Z', value: 198000000 },
  { date: '2012-12-16T23:01:24.000Z', value: 100000000 },
  { date: '2012-12-06T22:53:42.000Z', value: 100000000 },
  { date: '2012-12-05T04:50:49.000Z', value: 11100110 },
  { date: '2012-11-12T00:53:05.000Z', value: 443000 },
  { date: '2012-11-10T00:02:07.000Z', value: 13000000 },
  { date: '2012-10-31T02:22:36.000Z', value: 75000000 },
  { date: '2012-09-28T21:44:18.000Z', value: 1000000 },
  { date: '2012-09-25T02:31:49.000Z', value: 1000000 },
  { date: '2012-09-07T16:52:59.000Z', value: 300000000 },
  { date: '2012-08-29T23:10:32.000Z', value: 12000000 },
  { date: '2012-07-17T22:37:57.000Z', value: 100000000 },
  { date: '2012-05-28T14:50:10.000Z', value: 450000 },
  { date: '2012-05-03T15:09:34.000Z', value: 50000000 },
  { date: '2012-04-29T17:46:04.000Z', value: 20000000 } ]
I am suspicious of the 6th result, which is probably incorrectly parsing a value of 4e-8 which I don't understand the format of.
billymoon over 6 years ago
Maybe I could write it to Ruby, if it wasn't such an ass ... It's giving me errors while trying to install the JSON lib :D
ZeroCool over 6 years ago
I am running it on Node.JS, which does not require a JSON library. Or do you mean Ruby is not able to handle JSON on your machine?
billymoon over 6 years ago
I've installed JSON through gem, but it's still whining that it does not find it, I also installed rubygems.
ZeroCool over 6 years ago
I see - a port is pretty much a non-starter without JSON :)
billymoon over 6 years ago
I'm really wondering about the logic here, the value is supposed to be how many bitcoins there are?? Because that is really a big number?
ZeroCool over 6 years ago
The value is measured in satoshi, which is a tiny amount - I think 0.00000001 bitcoins. It is the smallest current denomination of botcoins, and acts like it's cent - but is not 100th, but rather a 10^7th.
billymoon over 6 years ago
Winning solution

This is another solution, based on @billymoons, converted to Ruby.

require 'open-uri'
require 'json'
require 'rubygems'
require 'date'
require 'bigdecimal'

class Transaction
    def initialize(ammount, date)
        @ammount = ammount
        @date = date
    end

    def to_s
        date1 = DateTime.strptime(@date, '%s').to_s()
        return "Date: " + date1 + ", Amount: " + @ammount
    end

    def ammount()
        return @ammount
    end

    def date()
        return @date
    end

end

class TransactionParser
    def initialize(address)
        @address = address
    end

    def get_array()
        transactions = []

        source = open("http://blockchain.info/rawaddr/#{@address}").read
        result = JSON.parse(source)

        for i in result['txs']
            ins = {}
            for inputs in i['inputs']
                addr = inputs['prev_out']['addr']
                value = inputs['prev_out']['value']
                if ins.has_key?(addr)
                    ins[addr] += BigDecimal.new(value.to_s())
                else
                    ins[addr] = BigDecimal.new("0.0")
                    ins[addr] += BigDecimal.new(value.to_s())
                end             
            end

            outs = {}
            for out in i['out']
                addr = out['addr']
                value = out['value']
                if outs.has_key?(addr)
                    outs[addr] += BigDecimal.new(value.to_s())
                else
                    outs[addr] = BigDecimal.new("0.0")
                    outs[addr] += BigDecimal.new(value.to_s())
                end
            end

            if !ins.has_key?(@address) && outs.has_key?(@address)
                time = (i['time']).to_s()
                transactions.push(Transaction.new(outs[@address], time))
            end

        end

        transactions_map = []
        for t in transactions
            ammount = t.ammount() / BigDecimal.new("100000000")
            transactions_map.push(Transaction.new(ammount.to_s('F'), t.date))
        end

        return transactions_map         
    end
end

y = TransactionParser.new("1JKnk8oWwtcL4pmTzt6MECQuPFyjjdHfqH")
puts y.get_array()
The 6th result is suspicious. I think the JSON says 4e-8 - do you know how to interpret that? I don't. I know what 4e8 is - but not with the - in there. Perhaps the Ruby number parser also does not know, or perhaps it does.
billymoon over 6 years ago
Good job on the port by the way :)
billymoon over 6 years ago
Even if it was an overflow or a number that is too big, I used BigDecimal, so that probably isn't the case.
ZeroCool over 6 years ago
This makes sense now, thanks for working it out! (BTW there are two missing zeros on the "1000000", should be "100000000")
L-von over 6 years ago
Oh, sorry about that :) Fixed.
ZeroCool over 6 years ago
View Timeline