As the other answer has noted, balances don't technically exist for a specific address. This is because there may be many outputs that are addressed to an address you own, but you can only spend these outputs discretely (I.e. You can say deduct x from my account, you can only say deduct this output amount which was previously awarded to this address).
We can define a pseudo-notion of a balance, though, let's call it the address' unlock-able value (UV), because the owner of the private key for that address can unlock some # of coins by knowing the private key for that address.
Now, to calculate the UV balance, total received, and total sent of an address, you follow this pseudo-algorithm:
Address a;
Uv = 0;
Received = 0;
Sent = 0;
For each (output in blockchain):
If (output is unlock-able by owner of address a):
Received += output.amount;
If (output has been spent):
Sent += output.amount;
Else:
Uv += output.amount;
At the end of all the outputs in the blockchain, the Uv amount will have a value that is similar in concept to a balance. But here we can also see why this is a very tricky notion:
- How do you know if an output is unlock-able by owner of address? Sometimes only the key holder themselves can know.
- What if an output can be unlocked by, say, 1 of a set of 3 keys? Do you credit the balances of all three keys, or none?
I suspect the way blockchain.info does this is by basically running the above pseudo algorithm (although much more optimized and using a database), but only considering outputs in the Pay-to-Pubkey-Hash format.
Alright, through contacting the blockchain.info support team and some testing I've found that the problem was the API link given in the question is both paginated and rate limited. The following PHP code produces the same values that blockchain.info shows on their site, assuming you don't run into rate limiting problems.
<?php
// Method: POST, PUT, GET etc
// Data: array("param" => "value") ==> index.php?param=value
function CallAPI($method, $url, $data = false)
{
$curl = curl_init();
switch ($method)
{
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data)
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
break;
case "PUT":
curl_setopt($curl, CURLOPT_PUT, 1);
break;
default:
if ($data)
$url = sprintf("%s?%s", $url, http_build_query($data));
}
// Optional Authentication:
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
// curl_setopt($curl, CURLOPT_USERPWD, "username:password");
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
$address = "1EZK42jGEJVniyBn1wXrUx92wzyUkYB8kJ";
$offset=0;
$txs = array();
while (true)
{
$val = json_decode(CallAPI("GET", "https://blockchain.info/address/$address?format=json&limit=10&offset=$offset"), true);
if (count($val["txs"]) == 0)
break;
else
$txs = array_merge($txs, $val["txs"]);
$offset += 10;
}
$uv = 0;
$received = 0;
$sent = 0;
$nrefs = 0;
foreach ($txs as $tx)
{
$ininputs = false;
$totalin = 0;
foreach ($tx["inputs"] as $input)
{
if ($input["prev_out"]["addr"] == $address)
{
$ininputs = true;
$nrefs += 1;
$totalin += $input["prev_out"]["value"];
}
}
$inoutputs = false;
$totalout = 0;
foreach ($tx["out"] as $output)
{
if ($output["addr"] == $address)
{
$inoutputs = true;
$nrefs += 1;
$totalout += $output["value"];
}
}
$uv += ($totalout - $totalin);
// $received += $totalout;
// $sent += $totalin;
if ($ininputs && $inoutputs && $totalout >= $totalin)
{
$received += $totalout - $totalin;
}
else if ($ininputs && $inoutputs && $totalout < $totalin)
{
$sent += $totalin - $totalout;
}
else if ($inoutputs)
{
$received += $totalout;
}
else if ($ininputs)
{
$sent += $totalin;
}
}
echo "\n";
echo "nrefs : " . $nrefs . "\n";
echo "uv : " . $uv . "\n";
echo "received : " . $received . "\n";
echo "sent : " . $sent . "\n";

