function print_help()
{
    $levels = mylogger()->get_level_map();
    $allcols = implode(',', walletaddrs::all_cols());
    $defaultcols = implode(',', walletaddrs::default_cols());
    $loglevels = implode(',', array_values($levels));
    $buf = <<<END

   hd-wallet-addrs.php

   This script discovers bitcoin HD wallet addresses that have been used.

   Options:

    -g                   go!  ( required )
    
    --xpub=<csv>         comma separated list of xpub keys
    --xpubfile=<path>    file containing xpub keys, one per line.
                           note: multiple keys implies multisig m of n.

    --derivation=<type>  bip32|bip44|bip45|copaylegacy|relative.
                           default=relative
    --numsig=<int>       number of required signers for m-of-n multisig wallet.
                           (required for multisig)
    
    --gap-limit=<int>    bip32 unused addr gap limit. default=20
    --include-unused     if present, unused addresses in gaps less than
                         gap limit will be included
    
    --gen-only=<n>      will generate n receive addresses and n change addresses
                          but will not query the blockchain to determine if they
                          have been used.
                          
    --type=<type>       receive|change|both.  default=both
    
    --api=<api>          toshi|insight|blockchaindotinfo|blockr|roundrobin
                           default = blockchaindotinfo  (fastest)
                           roundrobin will use a different API for each batch
                            to improve privacy.  It also sets --batch-size to
                            1 if set to auto.
                            
    --batch-size=<n>    integer|auto   default=auto.
                          The number of addresses to lookup in each batch.
    
    --cols=<cols>        a csv list of columns, or "all"
                         all:
                          ({$allcols})
                         default:
                          ({$defaultcols})

    --outfile=<path>     specify output file path.
    --format=<format>    txt|csv|json|jsonpretty|html|addrlist|all   default=txt
    
                         if all is specified then a file will be created
                         for each format with appropriate extension.
                         only works when outfile is specified.
                         
    --toshi=<url>       toshi server. defaults to https://bitcoin.toshi.io
    --insight=<url>     insight server. defaults to https://insight.bitpay.com/api
    
    --oracle-raw=<p>    path to save raw server response, optional.
    --oracle-json=<p>   path to save formatted server response, optional.
    
    --logfile=<file>    path to logfile. if not present logs to stdout.
    --loglevel=<level>  {$loglevels}
                          default = info
    


END;
    fprintf(STDERR, $buf);
}
 public static function query($pdo, $query, $type = null)
 {
     try {
         mylogger()->log(sprintf("Executing query: %s\n", $query), mylogger::debug);
         $tstart = microtime(true);
         $stmt = $pdo->query($query, PDO::FETCH_CLASS, 'stdClass');
         $duration = microtime(true) - $tstart;
         $query_was = $duration > 1 ? sprintf("Query was:\n%s", $query) : '';
         mylogger()->log(sprintf("Query took: %s seconds. %s", $duration, $query_was), mylogger::debug);
         if ($stmt->columnCount() == 0) {
             return array();
         }
         if ($type) {
             return $stmt->fetchAll($type);
         }
         return $stmt->fetchAll();
     } catch (PDOException $e) {
         mylogger()->log(sprintf("\n\nMySQL PDO Error %s: \n\nQuery was:\n  ---> %s\n\n%s:%s\n%s\nTrace:\n%s\n\n", $e->getCode(), $query, $e->getFile(), $e->getLine(), $e->getMessage(), $e->getTraceAsString()), mylogger::fatalerror);
         throw $e;
     }
 }
 protected static function print_results_worker($summary, $results, $outfile, $format)
 {
     $fname = $outfile ?: 'php://stdout';
     $fh = fopen($fname, 'w');
     switch ($format) {
         case 'txt':
             self::write_results_fixed_width($fh, $results, $summary);
             break;
         case 'addrlist':
             self::write_results_addrlist($fh, $results, $summary);
             break;
         case 'csv':
             self::write_results_csv($fh, $results);
             break;
         case 'json':
             self::write_results_json($fh, $results);
             break;
         case 'html':
             self::write_results_html($fh, $results);
             break;
         case 'jsonpretty':
             self::write_results_jsonpretty($fh, $results);
             break;
     }
     fclose($fh);
     if ($outfile) {
         mylogger()->log("Report was written to {$fname}", mylogger::specialinfo);
     }
 }
 private function get_addresses_info_worker($addr_list, $params)
 {
     $url_mask = "%s/api/v1/address/info/%s";
     $url = sprintf($url_mask, $params['blockr'], implode(',', $addr_list));
     mylogger()->log("Retrieving addresses metadata from {$url}", mylogger::debug);
     $result = httputil::http_get($url);
     $buf = $result['content'];
     if ($result['response_code'] == 404) {
         return array();
     } else {
         if ($result['response_code'] != 200) {
             throw new Exception("Got unexpected response code " . $result['response_code']);
         }
     }
     mylogger()->log("Received address info from blockr server.", mylogger::info);
     $oracle_raw = $params['oracle-raw'];
     if ($oracle_raw) {
         file_put_contents($oracle_raw, $buf);
     }
     $response = json_decode($buf, true);
     if (@$response['status'] != 'success') {
         throw new Exception("Got unexpected status from blockr.io API: " . @$response['status']);
     }
     $oracle_json = $params['oracle-json'];
     if ($oracle_json) {
         file_put_contents($oracle_json, json_encode($data, JSON_PRETTY_PRINT));
     }
     $data = $response['data'];
     // data may be a single object if only one address returned, or an array if multiple.
     // we normalize to an array.
     if (@$data['address']) {
         $data = [$data];
     }
     $addr_list_r = $data;
     $map = [];
     foreach ($addr_list_r as $info) {
         $normal = $this->normalize_address_info($info);
         $addr = $normal['addr'];
         $map[$addr] = $normal;
     }
     // addresses sometimes come back in different order than we sent them.  :(
     return $this->ensure_same_order($addr_list, $map);
 }