HCK BTC

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 36
At a glance
Powered by AI
The document discusses code related to handling bitcoin transactions and balances using a Money/Bitcoin class. The class contains methods for updating bitcoin node information, getting exchange rates, merging small outputs, and splitting large outputs.

The Money/Bitcoin class is used to manage interactions with bitcoin nodes like updating information, sending transactions, and retrieving balances/addresses. It contains static methods for common bitcoin operations.

The Money/Bitcoin class contains methods like update(), getRate(), mergeSmallOutputs(), splitBigOutputs() and others for tasks like updating node data, getting exchange rates, merging small outputs into one transaction, and splitting large outputs.

namespace Money;

class Bitcoin {
#const BITCOIN_NODE = '173.224.125.222'; // w001.mo.us temporary
const BITCOIN_NODE = '50.97.137.37';
static private $pending = array();

public static function update() {


// update all nodes
$list = \DB::DAO('Money_Bitcoin_Host')->search(null);
foreach($list as $bean) {
$bean->Last_Update = \DB::i()->now();
$client = \Controller::Driver('Bitcoin', $bean-
>Money_Bitcoin_Host__);
if (!$client->isValid()) continue;
$info = $client->getInfo();
if (!$info) {
$bean->Status = 'down';
$bean->commit();
continue;
}

if (($info['generate']) && ($bean->Generate == 'N')) {


$client->setGenerate(false);
} elseif ((!$info['generate']) && ($bean->Generate != 'N')) {
$client->setGenerate(true);
}

$bean->Version = $info['version'];
$bean->Coins = (int)round($info['balance'] * 100000000);
$bean->Connections = $info['connections'];
$bean->Blocks = $info['blocks'];
$bean->Hashes_Per_Sec = $info['hashespersec'];
$bean->Status = 'up';
$bean->commit();

if (is_null($bean->Address)) { // get in addr (generate if


needed)
$list = $client->getAddressesByLabel('_DEFAULT');
if ($list) {
$bean->Address = $list[0];
} else {
$bean->Address = $client->getNewAddress('_DEFAULT');
}
$bean->commit();
}

if (($bean->Keep_Empty == 'Y') && ($bean->Coins > 100000000)) {


// empty it!
$addr = self::getNullAddr();
try {
$client->sendToAddress($addr, $bean->Coins /
100000000);
} catch(\Exception $e) {
// try smaller amount (maybe failed because of fee)
try {
$c = $bean->Coins / 100000000;
$c = round($c/4, 2);
if ($c > 0)
$client->sendToAddress($addr, $c);
} catch(\Exception $e) {
// give up
}
}
}

if ($bean->Coins > (500*100000000)) {


// more than 500 coins on this host, shuffle some~
$client->sendToAddress($client->getNewAddress(),
(mt_rand(18,20000)/100));
}
}
}

public static function getRate() {


$ticker = \Money\Trade::ticker('BTC','EUR');
$btc = \DB::DAO('Currency')->searchOne(array('Currency__' => 'BTC'));

$btc->Ex_Bid = 1/$ticker['vwap']['value'];
$btc->Ex_Ask = 1/$ticker['vwap']['value'];
$btc->commit();

\DB::DAO('Currency_History')->insert(array('Currency__' => $btc-


>Currency__, 'Date' => gmdate('Y-m-d'), 'Ex_Bid' => $btc->Ex_Bid, 'Ex_Ask' => $btc-
>Ex_Ask));
}

public static function mergeSmallOutputs() {


$transaction = \DB::i()->transaction();
$lock = \DB::i()->lock('Money_Bitcoin_Available_Output');

$list = \DB::DAO('Money_Bitcoin_Available_Output')-
>search(array('Available' => 'Y', new \DB\Expr('`Value` < 100000000')), null,
array(5));
if (count($list) < 3) return false;

$list[] = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(['Available' => 'Y', new \DB\Expr('`Value` > 100000000')]);

$input = array();
$amount = 0;
foreach($list as $bean) {
$key = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean-
>Money_Bitcoin_Permanent_Address__));
if (!$key) throw new \Exception('Unusable output');
$tmp = array(
'privkey' => \Internal\Crypt::decrypt($key->Private_Key),
'tx' => $bean->Hash,
'N' => $bean->N,
'hash' => $bean->Money_Bitcoin_Permanent_Address__,
'amount' => $bean->Value,
'input_source' => $bean->Money_Bitcoin_Available_Output__,
);
$input[] = $tmp;
$amount += $bean->Value;
$bean->Available = 'N';
$bean->commit();
}
$output = \Money\Bitcoin::getNullAddr();
$output = \Util\Bitcoin::decode($output);
if (!$output) return false;

$tx = \Util\Bitcoin::makeNormalTx($input, $amount, $output, $output);


self::publishTransaction($tx);
return $transaction->commit();
}

public static function splitBigOutputs() {


$transaction = \DB::i()->transaction();
$lock = \DB::i()->lock('Money_Bitcoin_Available_Output');

$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array('Available' => 'Y', new \DB\Expr('`Value` > 1000000000')));
if (!$bean) return;

$input = array();
$amount = 0;

$key = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean-
>Money_Bitcoin_Permanent_Address__));
if (!$key) throw new \Exception('Unusable output');
$tmp = array(
'privkey' => \Internal\Crypt::decrypt($key->Private_Key),
'tx' => $bean->Hash,
'N' => $bean->N,
'hash' => $bean->Money_Bitcoin_Permanent_Address__,
'amount' => $bean->Value,
'input_source' => $bean->Money_Bitcoin_Available_Output__,
);
$input[] = $tmp;
$amount += $bean->Value;
$bean->Available = 'N';
$bean->commit();

$output1 = \Util\Bitcoin::decode(\Money\Bitcoin::getNullAddr());
$output2 = \Util\Bitcoin::decode(\Money\Bitcoin::getNullAddr());

$tx = \Util\Bitcoin::makeNormalTx($input, round(mt_rand($amount*0.4,


$amount*0.6)), $output1, $output2);
self::publishTransaction($tx);
return $transaction->commit();
}

public static function getTxInput($amount, $inputs = array()) {


// get input that covers at least $amount
$tx_list = array();
$total = 0;
if ($amount <= 0) throw new \Exception('Invalid TX amount');

// check for forced inputs


foreach($inputs as $input) {
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array('Hash' => $input['hash'], 'N' => $input['n']));
if (!$bean) continue; // not a valid input
$total += $bean->Value;
$tx_list[$bean->Money_Bitcoin_Available_Output__] = $bean;
$bean->Available = 'N';
$bean->commit();
if (count($tx_list) > 5) break; // even only one input is enough
to invalidate the old tx, let's grab 5
}

while(true) {
if ($total == $amount) break;
if (($total > $amount) && ($total - $amount > 1000000)) break;
// need more inputs
$skip_ok = false;
if (count($tx_list) >= 3) {
// need more inputs, and need those *fast*, take the
largest that would fit our remaining balance
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array('Available' => 'Y', new \DB\
Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()-
>quote(array_keys($tx_list), \DB::QUOTE_LIST).')'), new \DB\Expr('`Value` > '.
($amount - $total))), array(new \DB\Expr('RAND()')));
if (!$bean) {
// take largest one
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array('Available' => 'Y', new \DB\
Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()-
>quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array('Value' => 'DESC'));
}
if (!$bean)
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array(new \DB\Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\
DB::i()->quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array(new \DB\
Expr('RAND()')));
} elseif ($tx_list) {
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array('Available' => 'Y', new \DB\
Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()-
>quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array(new \DB\
Expr('RAND()')));
} else {
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array('Available' => 'Y'), array(new \DB\Expr('RAND()')));
}

if (!$bean) {
$skip_ok = true;
if ($tx_list) {
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(array(new \DB\Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\
DB::i()->quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array(new \DB\
Expr('RAND()')));
} else {
$bean = \DB::DAO('Money_Bitcoin_Available_Output')-
>searchOne(null, array(new \DB\Expr('RAND()')));
}
}

if (!$bean) throw new \Exception('No available output for this


TX');
// check if really available
if (!$skip_ok) {
$out = \DB::DAO('Money_Bitcoin_Block_Tx_Out')-
>searchOne(array('Hash' => $bean->Hash, 'N' => $bean->N));
if ($out) {
if ($out->Claimed == 'Y') {
$bean->Available = 'N';
$bean->commit();
continue;
}
}
}
$total += $bean->Value;
$tx_list[$bean->Money_Bitcoin_Available_Output__] = $bean;
}

$input = array();
foreach($tx_list as $bean) {
$key = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean-
>Money_Bitcoin_Permanent_Address__));
if (!$key) throw new \Exception('Unusable output');
$tmp = array(
'privkey' => \Internal\Crypt::decrypt($key->Private_Key),
'tx' => $bean->Hash,
'N' => $bean->N,
'hash' => $bean->Money_Bitcoin_Permanent_Address__,
'amount' => $bean->Value,
);
$input[] = $tmp;
$bean->Available = 'N';
$bean->commit();
}
shuffle($input); // randomize inputs order
return $input;
}

public static function getPaymentAddr($payment_id) {


$private = \Util\Bitcoin::genPrivKey();
$info = \Util\Bitcoin::decodePrivkey($private);

$insert = array(
'Money_Bitcoin_Permanent_Address__' => $info['hash'],
'Money_Bitcoin_Host__' => null,
'Money_Merchant_Transaction_Payment__' => $payment_id,
'Private_Key' => \Internal\Crypt::encrypt($private),
'Created' => \DB::i()->now(),
'Used' => 'Y',
'Callback' => 'Money/Merchant/Transaction::bitcoinEvent'
);

if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert))
return false;

return \Money\Bitcoin\Address::byHash($info['hash']);
}

public static function getNullAddr($priv = false) {


$private = \Util\Bitcoin::genPrivKey();
$info = \Util\Bitcoin::decodePrivkey($private);
$address = \Util\Bitcoin::encode($info);

$insert = array(
'Money_Bitcoin_Permanent_Address__' => $info['hash'],
'Money_Bitcoin_Host__' => null,
'User_Wallet__' => null,
'Private_Key' => \Internal\Crypt::encrypt($private),
'Created' => \DB::i()->now(),
'Used' => 'Y',
);

if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert))
return false;

if ($priv) return array('priv' => $private, 'info' => $info, 'address'


=> $address);

return $address;
}

public static function getVerboseAddr($wallet, $description, $ipn = null,


$user = null, $callback = null) {
if ($wallet && $wallet['Currency__'] != 'BTC') return false;

$private = \Util\Bitcoin::genPrivKey();
$info = \Util\Bitcoin::decodePrivkey($private);
$address = \Util\Bitcoin::encode($info);

$insert = array(
'Money_Bitcoin_Permanent_Address__' => $info['hash'],
'Money_Bitcoin_Host__' => null,
'User_Wallet__' => $wallet ? $wallet->getId() : null,
'Private_Key' => \Internal\Crypt::encrypt($private),
'Created' => \DB::i()->now(),
'Description' => $description,
'Ipn' => $ipn,
'Used' => 'Y', // do not use it for normal purposes
'Callback' => $callback
);
if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId();

if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert))
return false;

return $address;
}

public static function getPermanentAddr($wallet, $user = null) {


if ($wallet['Currency__'] != 'BTC') return false;

$unused = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('User_Wallet__' => $wallet->getId(), 'Used' => 'N'));
if ($unused) {
if (strlen($unused->Money_Bitcoin_Permanent_Address__) != 40)
return $unused->Money_Bitcoin_Permanent_Address__;
return \Util\Bitcoin::encode(array('version' => 0, 'hash' =>
$unused->Money_Bitcoin_Permanent_Address__));
}

$private = \Util\Bitcoin::genPrivKey();
$info = \Util\Bitcoin::decodePrivkey($private);
$address = \Util\Bitcoin::encode($info);

$insert = array(
'Money_Bitcoin_Permanent_Address__' => $info['hash'],
'Money_Bitcoin_Host__' => null,
'User_Wallet__' => $wallet->getId(),
'Private_Key' => \Internal\Crypt::encrypt($private),
'Created' => \DB::i()->now(),
);
if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId();

if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert))
return false;

return $address;
}

/**
* Create a bitcoin address with some dynamic configuration, like
autoselling, mails, etc...
* @param \User\Wallet $wallet
* @param array $options
* @param \User $user
* @return bool|string
* @throws \TokenException
*/
public static function getAddrWithOptions(\User\Wallet $wallet, array
$options = [], \User $user = null) {
if ($wallet['Currency__'] != 'BTC') throw new \TokenException('Invalid
currency provided', 'invalid_source_currency');

// filter fields in options


// autosell: bool Sell bitcoins when received
// email: bool Send email either when receiving bitcoins (no autosell)
or once sold
// data: string custom data returned in the mail
// currency: string The currency used for autosell, default to default
wallet
$filtered_options = [];
$fields = ['autosell' => 'bool', 'email' => 'bool', 'data' => 'string',
'currency' => 'string'];
foreach ($fields as $field => $type) {
if (isset($options[$field])) {
$value = $options[$field];
switch ($type) {
case 'bool':
$value = (bool)$value;
break;
default:
case 'string':
// truncate strings to 128 chars
$value = substr((string)$value, 0, 128);
break;
}
$filtered_options[$field] = $value;
}
}

if (isset($filtered_options['autosell']) &&
$filtered_options['autosell']) {
if (!isset($filtered_options['currency'])) {
throw new \TokenException('Missing currency for autosell',
'autosell_missing_currency');
}
}

// check currency if set


if (isset($filtered_options['currency'])) {
// check if that currency exists
$cur = \Currency::get($filtered_options['currency']);
if (!$cur || $cur->isVirtual()) {
throw new \TokenException('Invalid currency or virtual
currency', 'invalid_target_currency');
}
}

// generate a new bitcoin address


$private = \Util\Bitcoin::genPrivKey();
$info = \Util\Bitcoin::decodePrivkey($private);
$address = \Util\Bitcoin::encode($info);

$insert = array(
'Money_Bitcoin_Permanent_Address__' => $info['hash'],
'Money_Bitcoin_Host__' => null,
'User_Wallet__' => $wallet->getId(),
'Private_Key' => \Internal\Crypt::encrypt($private),
'Created' => \DB::i()->now(),
'Description' => json_encode($filtered_options),
'Used' => 'Y', // do not use it for normal purposes
'Callback' => 'Money/Bitcoin::optionAddrEvent'
);
// if the call was done through the API
if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId();

if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) {
throw new \TokenException('Couldn\'t create bitcoin address,
please contact mtgox', 'unknown_error');
};

return $address;
}

public static function optionAddrEvent($addr, $hash_n, $block, $amount) {


// ignore until we have enough confirmations
if (!$block) return;

$options = json_decode($addr->Description, true);


/** @var $source_wallet \User\Wallet */
$source_wallet = \User\Wallet::byId($addr->User_Wallet__);

// manage autosell
if (isset($options['autosell']) && $options['autosell']) {
$callback = null;
if (isset($options['email']) && $options['email']) {
$callback = 'Money/Bitcoin::optionAddrSellEmail';
if ($options['data']) {
$callback .= '|' . $options['data'];
}
}
\Money\Trade::addOrder($source_wallet->getUser(), 'ask', $amount,
$options['currency'], [], null, $callback);
} else {
// send email with details about the transaction
if (isset($options['email']) && $options['email']) {
$mail_page = \Registry::getInstance()->OptionAddrBlockEmail
?: 'mail/option_addr_bitcoin_rcvd.mail';
$mail_data = [
'_HASH' => $hash_n,
'_BLOCK' => $block,
'_AMOUNT' => $amount
];
if (isset($options['data'])) $mail_data['_DATA'] =
$options['data'];
\Tpl::userMail($mail_page, $source_wallet->getUser(),
$mail_data);
}
}
}

public static function optionAddrSellEmail($user, $oid, $type, $data = null)


{
$user = \User::byId($user, false, true);
$trade_info = \Money\Trade::getOrderExecutionResult($user, $oid, $type
== 'bid');
$mail_page = \Registry::getInstance()->OptionAddrOrderEmail ?:
'mail/option_addr_bitcoin_sold.mail';
$mail_data = [
'_TRADE_INFO' => $trade_info,
];
if ($data) $mail_data['_DATA'] = $data;
return \Tpl::userMail($mail_page, $user, $mail_data);
}

public static function checkOrders() {


// check data in Money_Bitcoin_Order to see if any order is completed
$db = \DB::i();
$list = $db['Money_Bitcoin_Order']->search(array('Status' =>
'pending'));
$clients = array();

foreach($list as $bean) {
if (!isset($clients[$bean->Money_Bitcoin_Host__]))
$clients[$bean->Money_Bitcoin_Host__] = \Controller::Driver('Bitcoin', $bean-
>Money_Bitcoin_Host__);
$client = $clients[$bean->Money_Bitcoin_Host__];
$total = (int)round($client->getReceivedByAddress($bean->Address,
3) * 100000000); // 3 confirmations

if ($bean->Coins == $total) { // nothing moved


if ($db->dateRead($bean->Expires) < time()) {
$bean->Status = 'expired';
$bean->commit();
continue;
}
}
$bean->Coins = $total;
$total += $bean->Coins_Extra;
if ($bean->Total <= $total) {
// payment complete!
$bean->Status = 'ok';
$bean->commit();

// mark order paid


$order = \Order::byId($bean->Order__);
if ($order->isPaid()) continue; // ?!
$info = array(
'method' => 'BITCOIN',
'class' => 'Bitcoin',
'stamp' => time(),
);
$order->paid($info);
continue;
}

$total_nc = (int)round($client->getReceivedByAddress($bean-
>Address, 0) * 100000000);
$bean->Coins_NC = $total_nc;
$bean->commit();
}
}

public static function getAddressForOrder($order) {


$total = $order->getTotal();
if ($total->getCurrency()->Currency__ != 'BTC') return false;
$btc = $total['value'];

$bean = \DB::DAO('Money_Bitcoin_Order')->searchOne(array('Order__' =>


$order->getId()));
if ($bean) {
if ($bean->Status != 'pending') return false;
$bean->Total = ((int)round($btc * 100))*1000000;
if ($bean->Address != '') {
$bean->commit();
return $bean;
} elseif ($bean->Coins == $bean->Coins_NC) {
$bean->Coins_Extra = $bean->Coins;
$bean->Coins = 0;
$bean->Coins_NC = 0;
// find a (new) random host
$host = \DB::DAO('Money_Bitcoin_Host')-
>searchOne(array('Status' => 'up', 'Allow_Order' => 'Y'), array(new \DB\
Expr('RAND()')));
if (!$host) return false; // no available host right now
$client = \Controller::Driver('Bitcoin', $host-
>Money_Bitcoin_Host__);
$addr = $client->getNewAddress('ORDER:'.$order->getId());
// update
$bean->Address = $addr;
$bean->commit();
return $bean;
}
}
// find a random host
$host = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' =>
'up', 'Allow_Order' => 'Y'), array(new \DB\Expr('RAND()')));
if (!$host) return false; // no available host right now

$client = \Controller::Driver('Bitcoin', $host->Money_Bitcoin_Host__);


$addr = $client->getNewAddress('ORDER:'.$order->getId());

// new entry
$db = \DB::i();
$uuid = \System::uuid();
$insert = array(
'Money_Bitcoin_Order__' => $uuid,
'Order__' => $order->getId(),
'Money_Bitcoin_Host__' => $host->Money_Bitcoin_Host__,
'Address' => $addr,
'Coins' => 0,
'Total' => ((int)round($btc * 100)) * 1000000,
'Created' => $db->now(),
'Expires' => $db->dateWrite(time()+(86400*10)),
);
$db['Money_Bitcoin_Order']->insert($insert);
$bean = $db['Money_Bitcoin_Order'][$uuid];
if (!$bean) return false;

return $bean;
}

public static function sendAmount($address, $amount, $green = null, $inputs =


array(), $fee = 0) {
if ($amount instanceof \Internal\Price) $amount = $amount-
>convert('BTC', null, \Currency::DIRECTION_OUT)->getIntValue();
if ($fee instanceof \Internal\Price) $fee = $fee->convert('BTC',
null, \Currency::DIRECTION_OUT)->getIntValue();

$transaction = \DB::i()->transaction();
$lock = \DB::i()->lock('Money_Bitcoin_Available_Output');

$address = \Util\Bitcoin::decode($address);
if (!$address) throw new \Exception('Invalid bitcoin address');
$remainder = \Util\Bitcoin::decode(self::getNullAddr());
if (!$remainder) throw new \Exception('Failed to create output TX');

$input = self::getTxInput($amount+$fee, $inputs);

if (!is_null($green)) {
// green send
// default=d47c1c9afc2a18319e7b78762dc8814727473e90
$tmp_total = 0;
foreach($input as $tmp) $tmp_total += $tmp['amount'];
$key = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('Money_Bitcoin_Permanent_Address__' => $green));
if (!$key) throw new \Exception('Invalid green address for
transaction');
// intermediate tx
$tx = \Util\Bitcoin::makeNormalTx($input, $tmp_total,
array('hash' => $green), array('hash' => $green));
$txid = self::publishTransaction($tx);
\DB::DAO('Money_Bitcoin_Available_Output')-
>insert(array('Money_Bitcoin_Available_Output__' => \System::uuid(),
'Money_Bitcoin_Permanent_Address__' => $green, 'Value' => $tmp_total, 'Hash' =>
$txid, 'N' => 0, 'Available' => 'N'));
// final tx
$tx = \Util\Bitcoin::makeNormalTx(array(array('amount' =>
$tmp_total, 'tx' => $txid, 'N' => 0, 'privkey' => \Internal\Crypt::decrypt($key-
>Private_Key), 'hash' => $green)), $amount, $address, $remainder);
$txid = self::publishTransaction($tx);
} else {
$tx = \Util\Bitcoin::makeNormalTx($input, $amount, $address,
$remainder, $fee);
$txid = self::publishTransaction($tx);
}

if (!$transaction->commit()) return false;

return $txid;

// find a node with enough coins


$node = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' =>
'up', new \DB\Expr('`Coins` >= '.\DB::i()->quote($amount))), array(new \DB\
Expr('RAND()')));
if (!$node) return false;
$client = \Controller::Driver('Bitcoin', $node->Money_Bitcoin_Host__);
return $client->sendToAddress($address, $amount/100000000);
}

public function getWalletHost() {


throw new \Exception('Method is deprecated');
}

public static function parseVersion($v) {


if ($v == 0) return '[unknown]';
if ($v > 10000) {
// [22:06:18] <ArtForz> new is major * 10000 + minor * 100 +
revision
$rem = floor($v / 100);
$proto = $v - ($rem*100);
$v = $rem;
} else {
// [22:06:05] <ArtForz> old was major * 100 + minor
$proto = 0;
}
foreach(array('revision','minor','major') as $type) {
$rem = floor($v / 100);
$$type = $v - ($rem * 100);
$v = $rem;
}
// build string
return $major . '.' . $minor . '.' . $revision . ($proto?('[.'.
$proto.']'):'');
}

public static function _Route_getStats($path) {


switch($path) {
case 'version':
$req = 'SELECT `Version`, COUNT(1) AS `Count` FROM
`Money_Bitcoin_Node` WHERE `Status` != \'down\' GROUP BY `Version`';
$sqlres = \DB::i()->query($req);
$res = array();
while($row = $sqlres->fetch_assoc()) {
$res[self::parseVersion($row['Version'])] +=
$row['Count'];
}
break;
case 'ua':
$req = 'SELECT `User_Agent`, COUNT(1) AS `Count` FROM
`Money_Bitcoin_Node` WHERE `Status` != \'down\' GROUP BY `User_Agent`';
$sqlres = \DB::i()->query($req);
$res = array();
while($row = $sqlres->fetch_assoc()) {
$res[$row['User_Agent']] += $row['Count'];
}
break;
case 'nodes':
$req = 'SELECT COUNT(1) AS `Count` FROM
`Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 6 HOUR)';
$sqlres = \DB::i()->query($req);
$row = $sqlres->fetch_assoc();
header('Content-Type: text/plain');
echo $row['Count'];
exit;
case 'accepting':
$req = 'SELECT `Status`, COUNT(1) AS `Count` FROM
`Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 6 HOUR) GROUP BY
`Status`';
$sqlres = \DB::i()->query($req);
$res = array();
while($row = $sqlres->fetch_assoc()) {
$res[$row['Status']] = $row['Count'];
}
$res['total_known'] = $res['up'] + $res['down'];
$res['total'] = $res['total_known'] + $res['unknown'];
$res['rate_accepting'] = $res['up'] / $res['total_known'];
break;
case 'bootstrap':
// select a set of peers appropriate as seed
$limit = 50;
if (isset($_GET['limit'])) {
$limit = (int)$_GET['limit'];
if ($limit < 1) $limit = 1;
if ($limit > 10000) $limit = 10000;
}
$req = 'SELECT * FROM `Money_Bitcoin_Node` WHERE `Status` =
\'up\' AND `Last_Checked` > DATE_SUB(NOW(), INTERVAL 6 HOUR) AND `Version` >= 31500
AND (`Last_Down` IS NULL OR `Last_Down` < DATE_SUB(NOW(), INTERVAL 2 WEEK)) AND
`First_Seen` < DATE_SUB(NOW(), INTERVAL 2 WEEK) ORDER BY RAND() LIMIT '.$limit;
$sqlres = \DB::i()->query($req);
if ($sqlres->num_rows == 0) {
$req = 'SELECT * FROM `Money_Bitcoin_Node` WHERE
`Status` = \'up\' AND `Last_Checked` > DATE_SUB(NOW(), INTERVAL 6 HOUR) AND
`Version` >= 31500 ORDER BY RAND() LIMIT '.$limit;
$sqlres = \DB::i()->query($req);
}
$res = array();
while($row = $sqlres->fetch_assoc()) {
$res[] = array(
'ipv4' => $row['IP'],
'port' => $row['Port'],
'version' => $row['Version'],
'version_str' =>
self::parseVersion($row['Version']),
'user_agent' => $row['User_Agent'],
'timestamp' => \DB::i()-
>dateRead($row['Last_Checked']),
);
}
break;
case 'geomap':
// select all nodes
$req = 'SELECT `IP`, `Status`, `Version` FROM
`Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 3 HOUR)';
$sqlres = \DB::i()->query($req);
header('Content-Type: application/json');
echo '[';
$first = true;
$geoip = \ThirdParty\Geoip::getInstance();
while($row = $sqlres->fetch_assoc()) {
$res = array('ipv4' => $row['IP'], 'version' =>
$row['Version'], 'status' => $row['Status']);
$record = $geoip->lookup($row['IP'], false);
if (!$record) continue;
if (!isset($record['latitude'])) continue;
$res['latitude'] = $record['latitude'];
$res['longitude'] = $record['longitude'];
if ($first) {
$first = false;
} else {
echo ',';
}
echo json_encode($res);
}
echo ']';
exit;
case 'full':
// select all nodes
$req = 'SELECT * FROM `Money_Bitcoin_Node`';
$sqlres = \DB::i()->query($req);
header('Content-Type: application/json');
echo '[';
$first = true;
while($row = $sqlres->fetch_assoc()) {
if ($first) {
$first = false;
} else {
echo ',';
}
echo json_encode($row);
}
echo ']';
exit;
case 'bitcoin.kml':
header('Content-Type: application/vnd.google-
earth.kml+xml');
// check cache
$cache = \Cache::getInstance();
$data = $cache->get('bitcoin.kml_full');
if ($data) {
echo $data;
exit;
}
// select all nodes
$out = fopen('php://temp', 'w');
fwrite($out, "<?xml version=\"1.0\" encoding=\"UTF-
8\"?".">\n");
fwrite($out, '<kml xmlns="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom">'."\n");
fwrite($out, "<Document>\n<name>Bitcoin nodes in the
world</name>\n");
// styles
fwrite($out, "<Style
id=\"up\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-
blank.png</href></Icon></IconStyle></Style>\n");
fwrite($out, "<Style
id=\"down\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-
blank.png</href></Icon></IconStyle></Style>\n");
fwrite($out, "<Style
id=\"unknown\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/
wht-blank.png</href></Icon></IconStyle></Style>\n");
$req = 'SELECT `IP`, `Status`, `Version` FROM
`Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 3 HOUR) ORDER BY
`Status`';
$geoip = \ThirdParty\Geoip::getInstance();
$folder = '';
$sqlres = \DB::i()->query($req);
while($row = $sqlres->fetch_assoc()) {
// lookup
$record = $geoip->lookup($row['IP'], false);
if (!$record) continue;
if (!isset($record['latitude'])) continue;

if ($folder != $row['Status']) {
if ($folder) fwrite($out, "</Folder>\n");
$folder = $row['Status'];
fwrite($out, "<Folder><name>Bitcoin Nodes in
status ".$folder."</name>\n");
}
fwrite($out,
"<Placemark><name>".$row['IP']."</name><description><![CDATA[<p>IP: ".
$row['IP']."</p><p>Version:
".self::parseVersion($row['Version'])."</p>]]></description><styleUrl>#".
$folder."</styleUrl>");
fwrite($out, "<Point><coordinates>".
$record['longitude'].",".$record['latitude']."</coordinates></Point></Placemark>\
n");
}
fwrite($out, "</Folder>\n</Document>\n</kml>\n");
rewind($out);
$data = stream_get_contents($out);
fclose($out);
$cache->set('bitcoin.kml_full', $data, 1800);
echo $data;
exit;
default:
header('HTTP/1.0 404 Not Found');
die('Not available');
}
header('Content-Type: application/json');
echo json_encode($res);
exit;
}

public static function checkNodes($sched) {


// get nodes to check
$db = \DB::i();
$list = $db['Money_Bitcoin_Node']->search(array(new \DB\
Expr('`Next_Check` < NOW()')), array(new \DB\Expr('`Status` IN
(\'up\', \'unknown\') DESC'), 'Last_Checked' => 'ASC'), array(701));
if (count($list) == 701) {
$sched->busy();
array_pop($list);
}

$end_time = (floor(time()/60)*60)+50;

$nodes = new Bitcoin\Nodes();


$info = array();
$up = array();

$nodes->on(null, 'ready', function($key) use (&$info, $nodes, $db,


&$up) {
$node = $info[$key];
$node->Version = $nodes->getVersion($key);
$node->User_Agent = $nodes->getUserAgent($key);
$node->Status = 'up';
$node->Last_Seen = $db->now();
$node->Last_Checked = $db->now();
$node->Next_Check = $db->dateWrite(time()+(1800));
$node->commit();
$up[$key] = true;
$nodes->getAddr($key); // initiate loading of addrs
});

$nodes->on(null, 'error', function($key, $error) use (&$info, $db,


&$up) {
if ($up[$key]) return; // probably getaddr failed
$node = $info[$key];
$node->Status = 'down';
$node->Last_Checked = $db->now();
$node->Next_Check = $db->dateWrite(time()+(3600*24));
$node->Last_Down = $db->now();
$node->Last_Error = $error;
if ($db->dateRead($node->Last_Seen) < (time() - (3600*24))) { //
no news for 24 hours, drop it
$node->delete();
return;
}
$node->commit();
});

$nodes->on(null, 'addr', function($key, $addr_list) use (&$info,


$nodes, $db) {
$node = $info[$key];
if (count($addr_list) > 1000) {
$node->Addresses = 0;
$node->commit();
return;
}
$node->Addresses = count($addr_list);
$node->commit();
foreach($addr_list as $addr) {
$bean = $db['Money_Bitcoin_Node']->searchOne(array('IP' =>
$addr['ipv4'], 'Port' => $addr['port']));
if ($bean) {
$bean->Last_Seen = $db->now();
$bean->commit();
continue;
}

$db['Money_Bitcoin_Node']->insert(array(
'IP' => $addr['ipv4'],
'Port' => $addr['port'],
'Next_Check' => $db->now(),
'First_Seen' => $db->now(),
'Last_Seen' => $db->now(),
));
}

$nodes->close($key);
});

foreach($list as $node) {
if ($node->Port < 1024) {
$node->Status = 'down';
$node->Last_Checked = $db->now();
$node->Next_Check = $db->dateWrite(time()+(3600*24));
$node->Last_Down = $db->now();
if ($db->dateRead($node->Last_Seen) < (time() - (3600*24)))
{ // no news for 24 hours, drop it
$node->delete();
return;
}
$node->Last_Error = 'invalid_port';
$node->commit();
continue;
}
$key = 'node_'.$node->Money_Bitcoin_Node__;
$info[$key] = $node;
if (!$nodes->connect($key, $node->IP, $node->Port)) {
$node->Status = 'down';
$node->Last_Checked = $db->now();
$node->Next_Check = $db->dateWrite(time()+(3600*24));
$node->Last_Down = $db->now();
if ($db->dateRead($node->Last_Seen) < (time() - (3600*24)))
{ // no news for 24 hours, drop it
$node->delete();
return;
}
$node->Last_Error = 'invalid_address';
$node->commit();
}
}

while($nodes->wait());
}

public static function importBlockClaim($hash, $n, $tx) {


$trx = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->searchOne(array('Hash'
=> $hash, 'N' => $n));
if (!$trx) throw new \Exception('Claim from unknown trx: '.$hash.':'.
$n);
$trx->Claimed = 'Y';
$trx->commit();
\DB::DAO('Money_Bitcoin_Available_Output')->delete(array('Hash' =>
$hash, 'N' => $n));
return true;
}

public static function parseScriptPubKey($pubkey) {


if (preg_match('/^([0-9a-f]{1,130}) OP_CHECKSIG$/', $pubkey, $matches))
{
return array('hash' => \Util\Bitcoin::decodePubkey($matches[1]),
'pubkey' => $matches[1]);
}
if (preg_match('/^OP_DUP OP_HASH160 ([0-9a-f]{40}) OP_EQUALVERIFY
OP_CHECKSIG.*$/', $pubkey, $matches)) {
return array('hash' => array('hash' => $matches[1], 'version' =>
0));
}
\Debug::exception(new \Exception('WEIRD scriptPubKey - dropping it: '.
$pubkey));
return array('hash' => ['hash' =>
'0000000000000000000000000000000000000000', 'version' => 0]);
}

public static function importBlock($id) {


$peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-
88e37a83bda3');
$block = $peer->getBlock($id);

$transaction = \DB::i()->transaction();

// insert block
$data = array(
'Money_Bitcoin_Block__' => $block['hash'],
'Parent_Money_Bitcoin_Block__' => $block['prev_block'],
'Depth' => $id,
'Version' => $block['version'],
'Mrkl_Root' => $block['mrkl_root'],
'Time' => \DB::i()->dateWrite($block['time']),
'Bits' => $block['bits'],
'Nonce' => $block['nonce'],
'Size' => $block['size'],
);
\DB::DAO('Money_Bitcoin_Block')->insert($data);

$retry = 0;
while($block['tx']) {
$tx = array_shift($block['tx']);
$tmp = \DB::DAO('Money_Bitcoin_Block_Tx')->search(array('Hash' =>
$tx['hash']));
if ($tmp) continue; // skip duplicate TXs
$tx['block'] = $id;
$data = array(
'Hash' => $tx['hash'],
'Block' => $block['hash'],
'Version' => $tx['version'],
'Lock_Time' => $tx['lock_time'],
'size' => $tx['size'],
);
\DB::DAO('Money_Bitcoin_Block_Tx')->insert($data);
\DB::DAO('Money_Bitcoin_Tx')->delete(array('Money_Bitcoin_Tx__'
=> $data['Hash']));
\DB::DAO('Money_Bitcoin_Tx_In')->delete(array('Hash' =>
$data['Hash']));
\DB::DAO('Money_Bitcoin_Tx_Out')->delete(array('Hash' =>
$data['Hash']));

$watch = null;
$taint = null;
$taint_c = 0;

try {
foreach($tx['in'] as $n => $in) {
$data = array(
'Hash' => $tx['hash'],
'N' => $n,
'Prev_Out_Hash' => $in['prev_out']['hash'],
'Prev_Out_N' => $in['prev_out']['n'],
);
if ($in['coinbase']) {
$data['CoinBase'] = $in['coinbase'];
} else {
$data['scriptSig'] = $in['scriptSig'];
self::importBlockClaim($in['prev_out']['hash'],
$in['prev_out']['n'], $tx);
}
// \DB::DAO('Money_Bitcoin_Block_Tx_In')->insert($data);
}
} catch(\Exception $e) {
// retry later
if ($retry++ > 10) throw $e;
$block['tx'][] = $tx;
continue;
}

if (!is_null($taint)) $taint = (int)floor($taint/$taint_c);

foreach($tx['out'] as $n => $out) {


$data = array(
'Hash' => $tx['hash'],
'N' => $n,
'Value' => round($out['value']*100000000),
);
$addr = self::parseScriptPubKey($out['scriptPubKey']);
$data['Addr'] = $addr['hash']['hash'];
\DB::DAO('Money_Bitcoin_Block_Tx_Out')->insert($data);
if (isset(\DB::DAO('Money_Bitcoin_Permanent_Address')
[$data['Addr']])) {
$data['Money_Bitcoin_Process_Tx_Out__'] = \
System::uuid();
\DB::DAO('Money_Bitcoin_Process_Tx_Out')-
>insert($data, true);
}
}
}

$transaction->commit();
}

public static function importBlocks($scheduler) {


// determine last imported block
$block = \DB::DAO('Money_Bitcoin_Block')->searchOne(null, array('Depth'
=> 'DESC'));
if ($block) {
$block_id = $block->Depth + 1;
} else {
$block_id = 0;
}
// read blocks from b54f4d35-dd1c-43aa-9096-88e37a83bda3
$peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-
88e37a83bda3');

$info = $peer->getInfo();
if ($info['errors']) {
// reschedule for in one hour
$scheduler->busy(3600);
throw new \Exception('Can\'t import blocks: '.$info['errors']);
}

$last_block = $peer->getCurrentBlock()-5; // 5 confirmations


if ($last_block < $block_id) {
// nothing new here
// self::runAddrTriggers();
return;
}

$deadline = time()+50;
$c = 0;

while($block_id <= $last_block) {


try {
self::importBlock($block_id);
} catch(\Exception $e) {
mail('mark@ookoo.org', 'BLOCK IMPORT ERROR', $e-
>getMessage()."\n\n".$e);
$scheduler->busy(600);
return;
// empty all!
$db = \DB::i();
$db->query('TRUNCATE `Money_Bitcoin_Block`');
// $db->query('TRUNCATE `Money_Bitcoin_Block_Addr`');
$db->query('TRUNCATE `Money_Bitcoin_Block_Tx`');
// $db->query('TRUNCATE `Money_Bitcoin_Block_Tx_In`');
$db->query('TRUNCATE `Money_Bitcoin_Block_Tx_Out`');
}
$block_id++;
if ((time() > $deadline) || ($c++>49)) {
$scheduler->busy(0);
break;
}
}

// run addr triggers


// self::runAddrTriggers();
}

public static function insertMisingAvailableOutputs($addr) {


// search all unclaimed on this addr
$list = \DB::DAO('Money_Bitcoin_Process_Tx_Out')->search(array('Addr'
=> $addr, 'Claimed' => 'N'));
foreach($list as $bean) {
$insert = array(
'Money_Bitcoin_Available_Output__' => \System::uuid(),
'Money_Bitcoin_Permanent_Address__' => $bean->Addr,
'Value' => $bean->Value,
'Hash' => $bean->Hash,
'N' => $bean->N,
);
\DB::DAO('Money_Bitcoin_Available_Output')->insert($insert,
true);
}
}

public static function runAddrTriggers() {


// lookup tx out with Trigger = new
$list = \DB::DAO('Money_Bitcoin_Process_Tx_Out')-
>search(array('Trigger' => 'new'), null, array(500)); // limit to 500
// $main_transaction = \DB::i()->transaction();

foreach($list as $bean) {
$transaction = \DB::i()->transaction();
$bean->reloadForUpdate();
if ($bean->Trigger != 'new') {
// rollback, exit
unset($transaction);
continue;
}
$bean->Trigger = 'executed';
$bean->commit();

$tx = $bean->Hash.':'.$bean->N;

$addr_str = \Util\Bitcoin::encode(array('version' => 0, 'hash' =>


$bean->Addr));
$wallet_info = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Addr));

$redirect_value = null;
if ($wallet_info) $redirect_value = $wallet_info->Redirect;

$base_tx_data = \DB::DAO('Money_Bitcoin_Block_Tx')-
>searchOne(array('Hash' => $bean->Hash));
$base_block_data = \DB::DAO('Money_Bitcoin_Block')-
>searchOne(['Money_Bitcoin_Block__' => $base_tx_data->Block]);

if (($wallet_info) && (!is_null($wallet_info->Private_Key)) &&


($redirect_value == 'none')) {
$insert = array(
'Money_Bitcoin_Available_Output__' => \
System::uuid(),
'Money_Bitcoin_Permanent_Address__' => $bean->Addr,
'Value' => $bean->Value,
'Hash' => $bean->Hash,
'N' => $bean->N,
'Block' => $base_block_data->Depth,
);
\DB::DAO('Money_Bitcoin_Available_Output')->insert($insert,
true);
}

if ($redirect_value == 'fixed') {
// redirect funds
$target = $wallet_info->Redirect_Value;
$pub = \Util\Bitcoin::decode($target);
$tx = \Util\Bitcoin::makeNormalTx(array(array('amount' =>
$bean->Value, 'tx' => $bean->Hash, 'N' => $bean->N, 'privkey' => \Internal\
Crypt::decrypt($wallet_info->Private_Key), 'hash' => $bean->Addr)), $bean->Value,
$pub, $pub);
self::publishTransaction($tx);
$transaction->commit();
continue;
}

if (($wallet_info) && (!is_null($wallet_info->Callback))) {


try {
$cb = explode('::', str_replace('/', '\\',
$wallet_info->Callback));
call_user_func($cb, $wallet_info, $tx, $base_tx_data-
>Block, \Internal\Price::spawnInt($bean->Value,'BTC'));
} catch(\Exception $e) {
\Debug::exception($e);
unset($transaction);
continue;
}
}

if (($wallet_info) && (!is_null($wallet_info->Ipn))) {


$base_tx_data = \DB::DAO('Money_Bitcoin_Block_Tx')-
>searchOne(array('Hash' => $bean->Hash));
$post = array(
'description' => $wallet_info->Description,
'tx' => $tx,
'block' => $base_tx_data->Block,
'status' => 'confirmed',
'amount_int' => $bean->Value,
'item' => 'BTC',
'addr' => \Util\Bitcoin::encode(array('version' => 0,
'hash' => $wallet_info->Money_Bitcoin_Permanent_Address__)),
);
\Scheduler::oneshotUrl($wallet_info->Ipn, $post, null,
null, null, $wallet_info->User_Rest__);
}

if (($wallet_info) && (!is_null($wallet_info->User_Wallet__))) {


$wallet_info->Used = 'Y';
$wallet_info->commit();
$wallet = \User\Wallet::byId($wallet_info->User_Wallet__);
if (($wallet) && ($wallet['Currency__'] == 'BTC')) {
// WALLET REDIRECT CODE 1
if ((!is_null($wallet_info->Private_Key)) &&
($wallet_info->Redirect == 'wallet') && ($bean->Value > 100000)) {
// redirect funds
$target = self::getVerboseAddr($wallet,
$wallet_info->Description);
$pub = \Util\Bitcoin::decode($target);
try {
$tx = \Util\
Bitcoin::makeNormalTx(array(array('amount' => $bean->Value, 'tx' => $bean->Hash,
'N' => $bean->N, 'privkey' => \Internal\Crypt::decrypt($wallet_info->Private_Key),
'hash' => $bean->Addr)), $bean->Value, $pub, $pub);
} catch(\Exception $e) {
mail('mark@tibanne.com', 'FAILED TO
GENERATE REDIRECT TX', 'Error '.$e->getMessage().' on: '.$wallet_info-
>Money_Bitcoin_Permanent_Address__."\n".print_r($bean->getProperties(), true));
throw $e;
}
self::publishTransaction($tx);
$transaction->commit();
continue;
}
// search for already add
$nfo = \DB::DAO('User_Wallet_History')-
>searchOne(array('Reference_Type' => 'Money_Bitcoin_Block_Tx_Out', 'Reference' =>
$tx));
if (!$nfo) {
$wallet->deposit(\Internal\
Price::spawnInt($bean->Value, 'BTC'), $addr_str.(is_null($wallet_info-
>Description)?'':"\n".$wallet_info->Description), 'deposit',
'Money_Bitcoin_Block_Tx_Out', $tx);
if ($wallet['Balance']['value'] > 10000)
$wallet->getUser()->aml('Balance in bitcoin is over 10000', 2); // force AML
\Money\Trade::updateUserOrders($wallet-
>getUser());
}
}
}

$transaction->commit();
}

// $main_transaction->commit();

return count($list);
}

public static function getAddressBalance($addr) {


$res = \Internal\Price::spawn(0,'BTC');
$list = \DB::DAO('Money_Bitcoin_Block_Tx_Out')-
>search(['Addr'=>$addr['hash']]);
foreach($list as $bean)
$res->add(\Internal\Price::spawnInt($bean->Value, 'BTC'));
return $res;
}
public static function getAddressOutputs($addr) {
// get all unclaimed outputs for that addr
$list = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->search(array('Addr' =>
$addr['hash'], 'Claimed' => 'N'));
$final = array();
foreach($list as $bean) $final[] = $bean->getProperties();
return $final;
}

public static function claimPrivateSha256($wallet, $priv, $desc = null) {


return self::claimPrivate($wallet, \Util\Bitcoin::hash_sha256($priv),
$desc);
}

public static function claimWalletFile($wallet, $data, $desc = null) {


$keys = \Util\Bitcoin::scanWalletFile($data);
if (!$keys) return array();

$res = array();

foreach($keys as $key) {
$tmp = self::claimPrivate($wallet, $key, $desc);
if (!$tmp) continue;
$res[] = $tmp;
}
return $res;
}

public static function claimPrivate($wallet, $priv, $desc = null) {


// get all the funds sent to that private addr and record it for future
deposits
if (strlen($priv) != 32) throw new \Exception('The private key must be
32 bytes');

// check if privkey is within range


$pk_num = gmp_init(bin2hex($priv), 16);
if (gmp_cmp($pk_num, '0') <= 0) return false;
if (gmp_cmp($pk_num,
gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16))
>= 0) return false;

$pub = \Util\Bitcoin::decodePrivkey($priv);
$addr = \Util\Bitcoin::encode($pub);
$outs = \Money\Bitcoin::getAddressOutputs($pub);

$find = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('Money_Bitcoin_Permanent_Address__' => $pub['hash']));
if ($find) {
if (!is_null($find->Private_Key)) return false; // already got
this one
$find->Private_Key = \Internal\Crypt::encrypt($priv);
$find->Redirect = 'wallet';
$find->Used = 'Y';
$find->commit();
$wallet = \User\Wallet::byId($find->User_Wallet__);
} else {
$insert = array(
'Money_Bitcoin_Permanent_Address__' => $pub['hash'],
'Money_Bitcoin_Host__' => null,
'Private_Key' => \Internal\Crypt::encrypt($priv),
'Description' => $desc,
'Redirect' => 'nulladdr',
'Used' => 'Y',
);
if (!is_null($wallet)) {
$insert['User_Wallet__'] = $wallet->getId();
$insert['Redirect'] = 'wallet';
}
\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert);
}

$total = 0;
if ($outs) {
if (is_null($wallet)) {
$out = self::getNullAddr();
} else {
$out = self::getVerboseAddr($wallet, $desc);
}
$outpub = \Util\Bitcoin::decode($out);
$input = array();
foreach($outs as $t) {
$input[] = array('amount' => $t['Value'], 'tx' =>
$t['Hash'], 'N' => $t['N'], 'privkey' => $priv, 'hash' => $pub['hash']);
$total += $t['Value'];
}

$tx = \Util\Bitcoin::makeNormalTx($input, $total, $outpub,


$outpub);
self::publishTransaction($tx);
}
return array('amount' => \Internal\Price::spawnInt($total, 'BTC'),
'address' => $addr);
}

public static function makeNormalTx($input, $amount, $final_output,


$remainder, $fee = 0) {
// make a normal tx, merge inputs if preferable
$res = array();
while(count($input) > 5) {
// merge some inputs
$xinput = array();
$output = self::getNullAddr(true);

// merge as many inputs as we can in a single tx


while(true) {
$extra = array_shift($input);
if (is_null($extra)) break;
$tinput = $xinput;
$tinput[] = $extra;
$total = 0;
foreach($tinput as $t) $total+=$t['amount'];
$ttx = \Util\Bitcoin::makeNormalTx($tinput, $total,
$output['info'], $output['info']);
if (strlen($ttx) >= 1000) break;
$xinput[] = $extra;
}
if (!is_null($extra))
array_unshift($input, $extra);
$total = 0;
foreach($xinput as $t) $total += $t['amount'];
$ttx = \Util\Bitcoin::makeNormalTx($xinput, $total,
$output['info'], $output['info']);
$res[] = $ttx;
$thash = bin2hex(strrev(\Util\Bitcoin::hash_sha256(\Util\
Bitcoin::hash_sha256($ttx))));
$input[] = array(
'amount' => $total,
'tx' => $thash,
'N' => 0,
'privkey' => $output['priv'],
'hash' => $output['info']['hash'],
);
\DB::DAO('Money_Bitcoin_Available_Output')-
>insert(array('Money_Bitcoin_Available_Output__' => \System::uuid(),
'Money_Bitcoin_Permanent_Address__' => $output['info']['hash'], 'Value' => $total,
'Hash' => $thash, 'N' => 0, 'Available' => 'N'));
}
// do the final tx
$res[] = \Util\Bitcoin::makeNormalTx($input, $amount, $final_output,
$remainder, $fee);
return $res;
}

public static function publishTransaction($txs) {


// generate tx id
if (!is_array($txs)) $txs = array($txs);
foreach($txs as $tx) {
$txid = bin2hex(strrev(\Util\Bitcoin::hash_sha256(\Util\
Bitcoin::hash_sha256($tx))));
$insert = array(
'Hash' => $txid,
'Blob' => base64_encode($tx),
'Created' => \DB::i()->now(),
);
\DB::DAO('Money_Bitcoin_Pending_Tx')->insert($insert);
self::$pending[$txid] = $tx;
}
return $txid;
}

public static function broadcastPublished() {


if (!self::$pending) return;

\Controller::MQ('RabbitMQ')->invoke('Money/Bitcoin::broadcastPublished',
['txs' => self::$pending]);
self::$pending = [];
}

public static function _MQ_broadcastPublished($info) {


$list = $info['txs'];
$node = new \Money\Bitcoin\Node(self::BITCOIN_NODE);
foreach($list as $tx) {
$node->pushTx($tx);
}
$node->getAddr(); // force sync
}
public static function broadcastTransactions() {
$list = \DB::DAO('Money_Bitcoin_Pending_Tx')->search(array(new \DB\
Expr('`Last_Broadcast` < DATE_SUB(NOW(), INTERVAL 30 MINUTE)')), ['Last_Broadcast'
=> 'ASC'], array(100));
if (!$list) return;

// $ip = gethostbyname('relay.eligius.st');
$ip = gethostbyname('mtgox.relay.eligius.st');
$node = new \Money\Bitcoin\Node(self::BITCOIN_NODE);
$peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-
88e37a83bda3');
$el_todo = array();

foreach($list as $bean) {
// check if successful
$success = \DB::DAO('Money_Bitcoin_Block_Tx')-
>searchOne(array('Hash' => $bean->Hash));
if ($success) {
$bean->delete();
continue;
}
$bean->Last_Broadcast = \DB::i()->now();
if ((\DB::i()->dateRead($bean->Created) < (time()-7000)) &&
($bean->Eligius == 'N')) {
try {
if (!$el_node) $el_node = new \Money\Bitcoin\
Node($ip);
$el_node->pushTx(base64_decode($bean->Blob));
$bean->Eligius = 'P';
} catch(\Exception $e) {
// too bad
}
} elseif ($bean->Eligius == 'P') {
$bean->Eligius = 'Y';
$el_todo[] = $bean->Hash;
}
try {
$bean->Last_Result = $peer-
>sendRawTransaction(bin2hex(base64_decode($bean->Blob)));
} catch(\Exception $e) {
$bean->Last_Result = $e->getMessage();
}
$bean->commit();
$node->pushTx(base64_decode($bean->Blob));
}

$node->getAddr(); // force sync reply from bitcoin daemon so we know


the stuff went through
if ($el_node) $el_node->getAddr();
if ($el_todo) {
$ssh = new \Network\SSH($ip);
if (!$ssh->authKeyUuid('freetxn', '14a70b11-5f36-4890-82ca-
5de820882c7f')) {
mail('mark@tibanne.com,luke+eligius@dashjr.org', 'SSH
connection to freetxn@'.$ip.' failed', 'Used ssh key 14a70b11-5f36-4890-82ca-
5de820882c7f, but couldn\'t login to push those txs:'."\n".implode("\n",
$el_todo));
return; // failed
}
foreach($el_todo as $tx) {
$channel = $ssh->channel();
$channel->exec($tx);
$channel->wait();
}
}
}

/**
* Returns the total amount of bitcoins in the world based on that last block
generated
*
* @return int The total amount of bitcoins
*/
public static function getTotalCount() {
// get total count of BTC in the world based on latest block #
$last_block = \DB::DAO('Money_Bitcoin_Block')->searchOne(null,
['Depth'=>'DESC']);
$current = $last_block->Depth;

// this is a chunk of blocks, bitcoins generated per chunk start at 50


and halve every chunks
$block_size = 210000;

// first compute the total amount of bitcoins for the chunks that are
fully done
$full_block_count = floor($current / $block_size);
$full_block_coeff = (1 - pow(0.5, $full_block_count)) * 100;

// those are the bitcoins on the full block chunks


$total_bitcoins = $full_block_coeff * $block_size;

// then for the last chunk


$last_block_coeff = pow(0.5, $full_block_count + 1) * 100;
$total_bitcoins += $last_block_coeff * ($current - ($full_block_count *
$block_size));

return $total_bitcoins;
}

public static function _Route_bitcoind($path) {


$post = file_get_contents('php://input');
$post = json_decode($post, true);
if (!$post) return;
$method = $post['method'];
$params = $post['params'];
$id = $post['id']?:\System::uuid();
try {
throw new \Exception('Meh: '.$method);
die(json_encode(array('result' => $res, 'id' => $id)));
} catch(\Exception $e) {
die(json_encode(array('error' => $e->getMessage(), 'id' =>
$id)));
}
}

public static function _Route_handleTx() {


// posted by halfnode with a TX
$tx_bin = pack('H*', $_POST['tx']);
$tx = \Util\Bitcoin::parseTx($tx_bin);
if (!$tx) die('BAD TX');
$hash = $tx['hash'];
$dao = \DB::DAO('Money_Bitcoin_Tx');
if (isset($dao[$hash])) die('DUP');
if (\DB::DAO('Money_Bitcoin_Block_Tx')->countByField(array('Hash' =>
$hash))) die('DUP(blockchain)');

$insert = array(
'Money_Bitcoin_Tx__' => $hash,
'Data' => base64_encode($tx_bin),
'Size' => strlen($tx_bin),
);
$dao->insert($insert);

foreach($tx['in'] as $i => $txin) {


\DB::DAO('Money_Bitcoin_Tx_In')->insert(array(
'Hash' => $hash,
'N' => $i,
'Prev_Out_Hash' => $txin['prev_out']['hash'],
'Prev_Out_N' => $txin['prev_out']['n'],
'scriptSig' => $txin['scriptSig'],
'Addr' => $txin['addr'],
));
}

foreach($tx['out'] as $i => $txout) {


\DB::DAO('Money_Bitcoin_Tx_Out')->insert(array(
'Hash' => $hash,
'N' => $i,
'Value' => $txout['value_int'],
'scriptPubKey' => $txout['scriptPubKey'],
'Addr' => $txout['addr'],
));

// check if one of our addrs


$info = \DB::DAO('Money_Bitcoin_Permanent_Address')-
>searchOne(array('Money_Bitcoin_Permanent_Address__' => $txout['addr']));
if (($info) && (!is_null($info->Callback))) {
$cb = explode('::', str_replace('/', '\\', $info-
>Callback));
call_user_func($cb, $info, $hash.':'.$i, null, \Internal\
Price::spawnInt($txout['value_int'],'BTC'));
}
if (($info) && (!is_null($info->Ipn))) {
$post = array(
'description' => $info->Description,
'tx' => $hash.':'.$i,
'status' => 'published',
'amount_int' => $txout['value_int'],
'item' => 'BTC',
'addr' => \Util\Bitcoin::encode(array('version' => 0,
'hash' => $info->Money_Bitcoin_Permanent_Address__)),
);
\Scheduler::oneshotUrl($info->Ipn, $post, null, null, null,
$info->User_Rest__);
}

// REDIRECT CODE 2
if (($info) && (!is_null($info->Private_Key)) && ($info->Redirect
!= 'none') && ($txout['value_int'] > 10000)) {
// issue redirect now!
switch($info->Redirect) {
case 'wallet':
$wallet = \User\Wallet::byId($info-
>User_Wallet__);
$target = self::getVerboseAddr($wallet, $info-
>Description);
break;
case 'fixed':
$target = $info->Redirect_Value;
break;
case 'nulladdr':
$target = self::getNullAddr();
break;
}
$pub = \Util\Bitcoin::decode($target);
$tx = \Util\Bitcoin::makeNormalTx(array(array('amount' =>
$txout['value_int'], 'tx' => $hash, 'N' => $i, 'privkey' => \Internal\
Crypt::decrypt($info->Private_Key), 'hash' => $txout['addr'])),
$txout['value_int'], $pub, $pub);
self::publishTransaction($tx);
// self::broadcastPublished();
}
}
die('OK');
}

public static function getTablesStruct() {


return array(
'Money_Bitcoin_Host' => array(
'Money_Bitcoin_Host__' => 'UUID',
'Name' => array('type' => 'VARCHAR', 'size' => 16, 'null'
=> false),
'IP' => array('type' => 'VARCHAR', 'size' => 39, 'null' =>
false, 'key' => 'UNIQUE:IP'),
'Address' => array('type' => 'VARCHAR', 'size' => 35,
'null' => true),
'Version' => array('type' => 'INT', 'size' => 10,
'unsigned' => true),
'Coins' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true), /* stored in smallest unit of coin */
'Connections' => array('type' => 'INT', 'size' => 10,
'unsigned' => true),
'Blocks' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Hashes_Per_Sec' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Status' => array('type' => 'ENUM', 'values' =>
array('up','down'), 'default' => 'down'),
'Last_Update' => array('type' => 'DATETIME', 'null' =>
true),
'Keep_Empty' => array('type' => 'ENUM', 'values' =>
array('Y','N','E'), 'default' => 'N'), /* if set, any money on there will be sent
somewhere else. E=exclude */
'Allow_Order' => array('type' => 'ENUM', 'values' =>
array('Y','N'), 'default' => 'Y'), /* should we use this node for incoming
payments? */
'Generate' => array('type' => 'ENUM', 'values' =>
array('Y','N'), 'default' => 'Y'),
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
),
'Money_Bitcoin_Tx' => array(
'Money_Bitcoin_Tx__' => array('type' => 'CHAR', 'size' =>
64, 'null' => false, 'key' => 'PRIMARY'),
'Data' => array('type' => 'LONGTEXT', 'null' => false),
'Network' => array('type' => 'VARCHAR', 'size' => 32,
'default' => 'bitcoin', 'key' => 'Network'),
'Size' => array('type' => 'INT', 'unsigned' => true, 'size'
=> 10, 'null' => false),
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
),
'Money_Bitcoin_Tx_In' => array(
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'UNIQUE:Key'),
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' =>
true, 'key' => 'UNIQUE:Key'),
'Prev_Out_Hash' => array('type' => 'CHAR', 'size' => 64,
'null' => false),
'Prev_Out_N' => array('type' => 'INT', 'size' => 10,
'unsigned' => true),
'CoinBase' => array('type' => 'TEXT', 'null' => true),
'scriptSig' => array('type' => 'TEXT', 'null' => true),
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' =>
true, 'key' => 'Addr'),
'_keys' => array(
'Prev_Out' => array('Prev_Out_Hash','Prev_Out_N'),
),
),
'Money_Bitcoin_Tx_Out' => array(
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'UNIQUE:Key'),
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' =>
true, 'key' => 'UNIQUE:Key'),
'Value' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true, 'null' => false),
'scriptPubKey' => array('type' => 'TEXT'),
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' =>
true, 'key' => 'Addr'),
),
'Money_Bitcoin_Permanent_Address' => array(
'Money_Bitcoin_Permanent_Address__' => array('type' =>
'CHAR', 'size' => 40, 'key' => 'PRIMARY'),
'Money_Bitcoin_Host__' => 'UUID/N',
'User_Wallet__' => 'UUID/N',
'User_Rest__' => 'UUID/N',
'Money_Merchant_Transaction_Payment__' => 'UUID/N',
'Private_Key' => array('type' => 'VARCHAR', 'size' => 255,
'null' => true),
'Redirect' => array('type' => 'ENUM', 'values' =>
array('wallet','fixed','nulladdr','none'), 'default' => 'none'), // wallet =>
redirect to new addr on same wallet
'Redirect_Value' => array('type' => 'VARCHAR', 'size' =>
35, 'null' => true),
'Description' => array('type' => 'VARCHAR', 'size' => 255,
'null' => true),
'Ipn' => array('type' => 'VARCHAR', 'size' => 255, 'null'
=> true),
'Callback' => array('type' => 'VARCHAR', 'size' => 255,
'null' => true),
'Used' => array('type' => 'ENUM', 'values' =>
array('Y','N'), 'default' => 'N'),
'Created' => array('type' => 'DATETIME', 'null' => false),
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
'_keys' => array(
'Unused_Addr_Key' => array('User_Wallet__','Used'),
'User_Wallet__' => ['User_Wallet__'],
),
),
'Money_Bitcoin_Available_Output' => array( // list available
funds
'Money_Bitcoin_Available_Output__' => 'UUID',
'Money_Bitcoin_Permanent_Address__' => array('type' =>
'CHAR', 'size' => 40, 'key' => 'Money_Bitcoin_Permanent_Address__'),
'Network' => array('type' => 'VARCHAR', 'size' => 32,
'default' => 'bitcoin', 'key' => 'Network'),
'Value' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true, 'null' => false),
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'UNIQUE:Key'),
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' =>
true, 'key' => 'UNIQUE:Key'),
'Block' => array('type' => 'INT', 'size' => 10, 'unsigned'
=> true, 'key' => 'Block'),
'Available' => array('type' => 'ENUM', 'values' =>
array('Y','N'), 'default' => 'Y'),
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
),
'Money_Bitcoin_Order' => array(
'Money_Bitcoin_Order__' => 'UUID',
'Order__' => 'UUID',
'Money_Bitcoin_Host__' => 'UUID',
'Address' => array('type' => 'VARCHAR', 'size' => 35,
'null' => true), /* generated only for this order */
'Coins' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Coins_NC' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Coins_Extra' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Total' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Created' => array('type' => 'DATETIME', 'null' => false),
'Expires' => array('type' => 'DATETIME', 'null' => false),
'Status' => array('type' => 'ENUM', 'values' =>
array('pending','expired','ok'), 'default' => 'pending'),
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
'_keys' => array(
'@Order__' => array('Order__'),
'@Address' => array('Address'),
),
),
'Money_Bitcoin_Wallet' => array(
'Money_Bitcoin_Wallet__' => 'UUID',
'User__' => 'UUID',
'Money_Bitcoin_Host__' => 'UUID',
'Address' => array('type' => 'VARCHAR', 'size' => 35,
'null' => true),
'Coins' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Coins_NC' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true),
'Withdrawn_Coins' => array('type' => 'BIGINT', 'size' =>
21, 'unsigned' => false),
'Refresh' => array('type' => 'DATETIME', 'null' => false),
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
'_keys' => array(
'@User__' => array('User__'),
'@Address' => array('Address'),
),
),
'Money_Bitcoin_Node' => array(
'Money_Bitcoin_Node__' => NULL,
'IP' => array('type' => 'VARCHAR', 'size' => 15, 'null' =>
false, 'key' => 'UNIQUE:Unique_Host'),
'Port' => array('type' => 'INT', 'size' => 5, 'unsigned' =>
true, 'key' => 'UNIQUE:Unique_Host'),
'Version' => array('type' => 'INT', 'unsigned' => true,
'size' => 10),
'User_Agent' => array('type' => 'VARCHAR', 'size' => 256,
'null' => true),
'Status' => array('type' => 'ENUM', 'values' =>
array('up','down','unknown'), 'default' => 'unknown'),
'Addresses' => array('type' => 'INT', 'unsigned' => true,
'size' => 10, 'default' => 0),
'Last_Checked' => array('type' => 'DATETIME'),
'Next_Check' => array('type' => 'DATETIME'),
'First_Seen' => array('type' => 'DATETIME'),
'Last_Seen' => array('type' => 'DATETIME'),
'Last_Down' => array('type' => 'DATETIME', 'null' => true,
'default' => NULL),
'Last_Error' => array('type' => 'VARCHAR', 'size' => 32,
'null' => true),
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
'_keys' => [
'Next_Check' => ['Next_Check'],
'Status' => ['Status'],
],
),
'Money_Bitcoin_Pending_Tx' => array(
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'PRIMARY'),
'Network' => array('type' => 'VARCHAR', 'size' => 32,
'default' => 'bitcoin', 'key' => 'Network'),
'Blob' => array('type' => 'LONGTEXT'),
'Eligius' => array('type' => 'ENUM', 'values' =>
array('Y','P','N'), 'default' => 'N'),
'Created' => array('type' => 'DATETIME'),
'Input_Total' => ['type' => 'BIGINT', 'size' => 20,
'unsigned' => true, 'null' => true],
'Last_Broadcast' => array('type' => 'DATETIME'),
'Last_Result' => ['type' => 'VARCHAR', 'size' => 128,
'null' => true],
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
),
'Money_Bitcoin_Block' => array(
'Money_Bitcoin_Block__' => array('type' => 'CHAR', 'size'
=> 64, 'null' => false, 'key' => 'PRIMARY'),
'Parent_Money_Bitcoin_Block__' => array('type' => 'CHAR',
'size' => 64, 'null' => false, 'key' => 'Parent_Money_Bitcoin_Block__'),
'Depth' => array('type' => 'BIGINT', 'size' => 20, 'null'
=> false, 'key' => 'Depth'),
'Network' => array('type' => 'VARCHAR', 'size' => 32,
'default' => 'bitcoin', 'key' => 'Network'),
'Version' => array('type' => 'INT', 'size' => 10,
'unsigned' => true),
'Mrkl_Root' => array('type' => 'CHAR', 'size' => 64, 'null'
=> false),
'Time' => array('type' => 'DATETIME'),
'Bits' => array('type' => 'INT', 'size' => 10, 'unsigned'
=> true),
'Nonce' => array('type' => 'INT', 'size' => 10, 'unsigned'
=> true),
'Size' => array('type' => 'INT', 'size' => 10, 'unsigned'
=> true),
'Status' => array('type' => 'ENUM', 'values' =>
array('pending','confirmed','dropped'), 'default' => 'confirmed', 'null' => false),
),
'Money_Bitcoin_Process_Tx_Out' => [
'Money_Bitcoin_Process_Tx_Out__' => 'UUID',
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'UNIQUE:Key'),
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' =>
true, 'key' => 'UNIQUE:Key'),
'Value' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true, 'null' => false),
'scriptPubKey' => array('type' => 'TEXT'),
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' =>
false, 'key' => 'Addr'),
'Trigger' => array('type' => 'ENUM', 'values' =>
array('new','executed','nil'), 'default' => 'new'),
'_keys' => array(
'Trigger' => array('Trigger'),
),
],
'Money_Bitcoin_Block_Tx' => array(
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'UNIQUE:Hash'),
'Block' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'Block'),
'Version' => array('type' => 'INT', 'size' => 10,
'unsigned' => true),
'Lock_Time' => array('type' => 'INT', 'size' => 10,
'unsigned' => true),
'Size' => array('type' => 'INT', 'size' => 10, 'unsigned'
=> true),
),
/* 'Money_Bitcoin_Block_Tx_In' => array(
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'UNIQUE:Key'),
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' =>
true, 'key' => 'UNIQUE:Key'),
'Prev_Out_Hash' => array('type' => 'CHAR', 'size' => 64,
'null' => false),
'Prev_Out_N' => array('type' => 'INT', 'size' => 10,
'unsigned' => true),
'CoinBase' => array('type' => 'TEXT', 'null' => true),
'scriptSig' => array('type' => 'TEXT', 'null' => true),
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' =>
true, 'key' => 'Addr'),
'_keys' => array(
'Prev_Out' => array('Prev_Out_Hash','Prev_Out_N'),
),
),*/
'Money_Bitcoin_Block_Tx_Out' => array(
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' =>
false, 'key' => 'UNIQUE:Key'),
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' =>
true, 'key' => 'UNIQUE:Key'),
'Value' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true, 'null' => false),
'scriptPubKey' => array('type' => 'TEXT'),
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' =>
false, 'key' => 'Addr'),
'Claimed' => array('type' => 'ENUM', 'values' =>
array('Y','N'), 'default' => 'N'),
'Trigger' => array('type' => 'ENUM', 'values' =>
array('new','executed','nil'), 'default' => 'new'),
'_keys' => array(
'Trigger' => array('Trigger'),
),
),
/* 'Money_Bitcoin_Block_Addr' => array(
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' =>
false, 'key' => 'PRIMARY'),
'Network' => array('type' => 'VARCHAR', 'size' => 32,
'default' => 'bitcoin', 'key' => 'Network'),
'Pubkey' => array('type' => 'CHAR', 'size' => 130, 'null'
=> true),
'Balance' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true, 'null' => false),
'Watch' => array('type' => 'VARCHAR', 'size' => 128, 'null'
=> true, 'default' => NULL, 'key' => 'Watch'),
'Taint' => array('type' => 'BIGINT', 'size' => 20,
'unsigned' => true, 'null' => true, 'default' => NULL),
'Clean' => array('type' => 'VARCHAR', 'size' => 64, 'null'
=> true, 'default' => NULL, 'key' => 'Clean'),
), */
'Money_Bitcoin_Vanity' => array(
'Money_Bitcoin_Vanity__' => array('type' => 'VARCHAR',
'size' => 35, 'null' => false, 'key' => 'PRIMARY'),
'Private_Key' => array('type' => 'VARCHAR', 'size' => 255,
'null' => false),
),
);
}
}

\Scheduler::schedule('MoneyBitcoinUpdate', '10min', 'Money/Bitcoin::update');


\Scheduler::schedule('MoneyBitcoinCheckOrders', '5min',
'Money/Bitcoin::checkOrders');
\Scheduler::schedule('MoneyBitcoinGetRate', array('daily', '5i'),
'Money/Bitcoin::getRate');
\Scheduler::schedule('MoneyBitcoinCheckNodes', '10min',
'Money/Bitcoin::checkNodes');
\Scheduler::schedule('MoneyBitcoinImportBlocks', '1min',
'Money/Bitcoin::importBlocks');
\Scheduler::schedule('MoneyBitcoinAddrTriggers', '1min',
'Money/Bitcoin::runAddrTriggers');
\Scheduler::schedule('MoneyBitcoinBroadcastTxs', '1min',
'Money/Bitcoin::broadcastTransactions');
\Scheduler::schedule('MoneyBitcoinMergeSmallOutputs', '10min',
'Money/Bitcoin::mergeSmallOutputs');
\Scheduler::schedule('MoneyBitcoinSplitBigOutputs', '10min',
'Money/Bitcoin::splitBigOutputs');
\DB::i()->validateStruct(Bitcoin::getTablesStruct());
hacks.css
/* IE 6 and below */
* html .box {
color: red;
}

/* IE 7 */
*:first-child+html .box {
color: pink;
}
*+html .box {
color: pink;
}

/* Refs http://paulirish.com/2009/browser-specific-css-hacks/ */

/* Min-height hack */
.box {
height: auto !important; /* Standard browsers will apply this */
height: 30px; /* IE6 will ignore prior !important rules (IE6 always
has default */
min-height: 30px; /* Standard browsers will apply this */
}

You might also like