From 41e2e6aae2405e302e8068e98aa46d64c4791787 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 22 Jan 2015 20:00:08 +0100 Subject: [PATCH 01/16] WIP command line tool for SDK --- bin/blocktrail | 5 ++ composer.json | 3 +- src/Console/Application.php | 53 +++++++++++ src/Console/Commands/AbstractCommand.php | 88 +++++++++++++++++++ .../Commands/AbstractWalletCommand.php | 64 ++++++++++++++ src/Console/Commands/ConfigureCommand.php | 61 +++++++++++++ .../Commands/CreateNewWalletCommand.php | 83 +++++++++++++++++ src/Console/Commands/GetNewAddressCommand.php | 35 ++++++++ src/Console/Commands/ListWalletsCommand.php | 57 ++++++++++++ src/Console/Commands/UseWalletCommand.php | 54 ++++++++++++ 10 files changed, 502 insertions(+), 1 deletion(-) create mode 100755 bin/blocktrail create mode 100644 src/Console/Application.php create mode 100644 src/Console/Commands/AbstractCommand.php create mode 100644 src/Console/Commands/AbstractWalletCommand.php create mode 100644 src/Console/Commands/ConfigureCommand.php create mode 100644 src/Console/Commands/CreateNewWalletCommand.php create mode 100644 src/Console/Commands/GetNewAddressCommand.php create mode 100644 src/Console/Commands/ListWalletsCommand.php create mode 100644 src/Console/Commands/UseWalletCommand.php diff --git a/bin/blocktrail b/bin/blocktrail new file mode 100755 index 0000000..5d284e0 --- /dev/null +++ b/bin/blocktrail @@ -0,0 +1,5 @@ +#!/usr/bin/php +run(); diff --git a/composer.json b/composer.json index a0ae8bd..bc2ef1f 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ "rych/hash_pbkdf2-compat": "~1.0", "ramsey/array_column": "~1.1", "dompdf/dompdf" : "0.6.*", - "endroid/qrcode": "1.5.*" + "endroid/qrcode": "1.5.*", + "symfony/console": "~2.6" }, "require-dev": { "phpunit/phpunit": "4.3.*", diff --git a/src/Console/Application.php b/src/Console/Application.php new file mode 100644 index 0000000..32daca9 --- /dev/null +++ b/src/Console/Application.php @@ -0,0 +1,53 @@ +getFormatter()->setStyle('success', new OutputFormatterStyle('green', null, ['bold'])); + $output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold'])); + $output->getFormatter()->setStyle('info-bold', new OutputFormatterStyle('green', null, ['bold'])); + $output->getFormatter()->setStyle('question-bold', new OutputFormatterStyle('yellow', 'cyan', ['bold'])); + $output->getFormatter()->setStyle('comment-bold', new OutputFormatterStyle('yellow', null, ['bold'])); + + return parent::run($input, $output); + } +} diff --git a/src/Console/Commands/AbstractCommand.php b/src/Console/Commands/AbstractCommand.php new file mode 100644 index 0000000..7731675 --- /dev/null +++ b/src/Console/Commands/AbstractCommand.php @@ -0,0 +1,88 @@ +addOption('api_key', null, InputOption::VALUE_REQUIRED, 'API_KEY to be used') + ->addOption('api_secret', null, InputOption::VALUE_REQUIRED, 'API_SECRET to be used') + ->addOption('testnet', null, InputOption::VALUE_NONE, 'use testnet instead of mainnet'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + $config = $this->getConfig(); + + $this->apiKey = $input->hasOptionInput('api_key') ? trim($input->getOption('api_key')) : (isset($config['api_key']) ? $config['api_key'] : null); + $this->apiSecret = $input->hasOptionInput('api_secret') ? trim($input->getOption('api_secret')) : (isset($config['api_secret']) ? $config['api_secret'] : null); + $this->testnet = $input->hasOptionInput('testnet') ? $input->getOption('testnet') : (isset($config['testnet']) ? $config['testnet'] : false); + + if (!$this->apiKey) { + throw new \RuntimeException('API_KEY is required.'); + } + if (!$this->apiSecret) { + throw new \RuntimeException('API_SECRET is required.'); + } + } + + /** + * @return BlocktrailSDKInterface + */ + public function getBlocktrailSDK() { + return new BlocktrailSDK($this->apiKey, $this->apiSecret, "BTC", $this->testnet); + } + + public function getConfig() { + $dir = "{$_SERVER['HOME']}/.blocktrail"; + $file = "{$dir}/config.json"; + + if (!file_exists($file)) { + return []; + } + + return json_decode(file_get_contents($file), true); + } + + public function replaceConfig(array $config) { + $dir = "{$_SERVER['HOME']}/.blocktrail"; + + if (!file_exists($dir)) { + mkdir($dir); + } + + file_put_contents("{$dir}/config.json", json_encode($config)); + + return null; + } + + public function updateConfig(array $config) { + return $this->replaceConfig(array_replace($this->getConfig(), $config)); + } +} diff --git a/src/Console/Commands/AbstractWalletCommand.php b/src/Console/Commands/AbstractWalletCommand.php new file mode 100644 index 0000000..ebcea6e --- /dev/null +++ b/src/Console/Commands/AbstractWalletCommand.php @@ -0,0 +1,64 @@ +addOption('identifier', null, InputOption::VALUE_REQUIRED, 'Wallet identifier') + ->addOption('passphrase', null, InputOption::VALUE_REQUIRED, 'Wallet passphrase'); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + parent::execute($input, $output); + + $config = $this->getConfig(); + + $this->identifier = $input->hasOptionInput('identifier') ? trim($input->getOption('identifier')) : (isset($config['identifier']) ? $config['identifier'] : null); + $this->passphrase = $input->hasOptionInput('passphrase') ? trim($input->getOption('passphrase')) : (isset($config['passphrase']) ? $config['passphrase'] : null); + + if (!$this->identifier) { + throw new \RuntimeException('indentifier is required.'); + } + } + + protected function getWallet(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $sdk = $this->getBlocktrailSDK(); + + while (!trim($this->passphrase)) { + $question = new Question("Please provide passphrase for wallet [{$this->identifier}]: \n"); + $question->setHidden(true); + $this->passphrase = $questionHelper->ask($input, $output, $question); + } + + $wallet = $sdk->initWallet($this->identifier, $this->passphrase); + + $output->isVerbose() && $output->writeln("Wallet initialized"); + + return $wallet; + } +} diff --git a/src/Console/Commands/ConfigureCommand.php b/src/Console/Commands/ConfigureCommand.php new file mode 100644 index 0000000..cbf8023 --- /dev/null +++ b/src/Console/Commands/ConfigureCommand.php @@ -0,0 +1,61 @@ +setName('configure') + // ->setAliases(['setup']) + ->setDescription("Configure credentials") + ->addOption('api_key', null, InputOption::VALUE_REQUIRED, 'API_KEY to be used') + ->addOption('api_secret', null, InputOption::VALUE_REQUIRED, 'API_SECRET to be used') + ->addOption('testnet', null, InputOption::VALUE_NONE, 'use testnet instead of mainnet'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $interactive = true; // @TODO + $apiKey = trim($input->getOption('api_key')); + $apiSecret = trim($input->getOption('api_secret')); + $testnet = !!$input->getOption('testnet'); + + if ($interactive) { + if (!$apiKey) { + $question = new Question("Set the default API_KEY to use (blank to not set a default API_KEY): \n"); + $apiKey = $questionHelper->ask($input, $output, $question); + } + if (!$apiSecret) { + $question = new Question("Set the default API_SECRET to use (blank to not set a default API_SECRET): \n"); + $apiSecret = $questionHelper->ask($input, $output, $question); + } + + if (!$testnet) { + $question = new ConfirmationQuestion("Set weither to use TESTNET by default? [y/N] \n", false); + $testnet = $questionHelper->ask($input, $output, $question); + } + } + + $this->replaceConfig([ + 'api_key' => $apiKey, + 'api_secret' => $apiSecret, + 'testnet' => $testnet + ]); + } +} diff --git a/src/Console/Commands/CreateNewWalletCommand.php b/src/Console/Commands/CreateNewWalletCommand.php new file mode 100644 index 0000000..642c2c6 --- /dev/null +++ b/src/Console/Commands/CreateNewWalletCommand.php @@ -0,0 +1,83 @@ +setName('create_new_wallet') + // ->setAliases(['create_new_wallet', 'create_wallet']) + ->setDescription("Create a new wallet") + ->addArgument('identifier', InputArgument::REQUIRED, 'A unique identifier to be used as wallet identifier') + ->addArgument('passphrase', InputArgument::OPTIONAL, 'A strong passphrase to be used as wallet passphrase'); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $identifier = trim($input->getArgument('identifier')); + $passphrase = trim($input->getArgument('passphrase')); + + if (!$identifier) { + $output->writeln("Identifier is required!"); + exit(1); + } + + $output->writeln("Creating wallet with identifier [{$identifier}]"); + + while (!$passphrase) { + do { + $question = new Question("Please choose a strong passphrase for the wallet: \n"); + $question->setHidden(true); + $passphrase1 = $questionHelper->ask($input, $output, $question); + } while (!trim($passphrase1)); + + do { + $question = new Question("Please repeat the passphrase for the wallet: \n"); + $question->setHidden(true); + $passphrase2 = $questionHelper->ask($input, $output, $question); + } while (!trim($passphrase2)); + + if ($passphrase1 != $passphrase2) { + $output->writeln("Both passwords must be the same, please try again! \n"); + } else { + $passphrase = trim($passphrase1); + } + } + + $sdk = $this->getBlocktrailSDK(); + + /** @var WalletInterface $wallet */ + list($wallet, $primaryMnemonic, $backupMnemonic) = $sdk->createNewWallet($identifier, $passphrase, 9999); + + $output->writeln("Wallet created"); + + $output->writeln("Make sure to backup the following information somewhere safe;"); + $output->writeln("Primary Mnemonic:\n {$primaryMnemonic}"); + $output->writeln("Backup Mnemonic:\n {$backupMnemonic}"); + + while (!$questionHelper->ask($input, $output, new ConfirmationQuestion("Did you store the information? [y/N] ", false))) { + $output->writeln("..."); + } + + $output->writeln("DONE!"); + } +} diff --git a/src/Console/Commands/GetNewAddressCommand.php b/src/Console/Commands/GetNewAddressCommand.php new file mode 100644 index 0000000..6655edd --- /dev/null +++ b/src/Console/Commands/GetNewAddressCommand.php @@ -0,0 +1,35 @@ +setName('new_address') + // ->setAliases(['get_new_address', 'address']) + ->setDescription("Get a new address for a wallet"); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + $wallet = $this->getWallet($input, $output); + + $output->writeln($wallet->getNewAddress()); + } +} diff --git a/src/Console/Commands/ListWalletsCommand.php b/src/Console/Commands/ListWalletsCommand.php new file mode 100644 index 0000000..787a7fd --- /dev/null +++ b/src/Console/Commands/ListWalletsCommand.php @@ -0,0 +1,57 @@ +setName('list_wallets') + // ->setAliases(['create_new_wallet', 'create_wallet']) + ->setDescription("List all wallets"); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + $sdk = $this->getBlocktrailSDK(); + $config = $this->getConfig(); + + // $wallets = $sdk->getWallets(); + $wallets = [ + ['identifier' => 'cli-created-wallet', 'balance' => BlocktrailSDK::toSatoshi(28311.3238283)], + ['identifier' => 'another-cli-wallet', 'balance' => BlocktrailSDK::toSatoshi(0.32)], + ]; + + if (!$wallets) { + $output->writeln("There are no wallets!"); + exit(1); + } + + $table = new Table($output); + $table->setHeaders(['identifier', 'balance', '']); + foreach ($wallets as $wallet) { + $isDefault = isset($config['identifier']) && $config['identifier'] == $wallet['identifier']; + + $table->addRow([$wallet['identifier'], BlocktrailSDK::toBTCString($wallet['balance']), $isDefault ? 'IS_DEFAULT' : '']); + } + + $table->render(); + } +} diff --git a/src/Console/Commands/UseWalletCommand.php b/src/Console/Commands/UseWalletCommand.php new file mode 100644 index 0000000..742e571 --- /dev/null +++ b/src/Console/Commands/UseWalletCommand.php @@ -0,0 +1,54 @@ +setName('default_wallet') + ->setDescription("Configure default wallet to use") + ->addOption('identifier', null, InputOption::VALUE_REQUIRED, 'Wallet identifier') + ->addOption('passphrase', null, InputOption::VALUE_REQUIRED, 'Wallet passphrase'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $interactive = true; // @TODO; + $identifier = trim($input->getOption('identifier')); + $passphrase = trim($input->getOption('passphrase')); + + if ($interactive) { + if (!$identifier) { + $question = new Question("Set the default wallet identifier to use (blank to not set a default identifier): \n"); + $identifier = $questionHelper->ask($input, $output, $question); + } + if (!$passphrase) { + $question = new Question("Set the default wallet passphrase to use (blank to not set a default passphrase): \n"); + $question->setHidden(true); + $passphrase = $questionHelper->ask($input, $output, $question); + } + } + + $this->updateConfig([ + 'identifier' => $identifier, + 'passphrase' => $passphrase, + ]); + } +} From 2bc96e1672fe4488e3e697974489aa39adaba821 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Fri, 23 Jan 2015 12:03:01 +0100 Subject: [PATCH 02/16] added Compiler to create blocktrail.phar executable --- bin/blocktrail | 2 +- bin/compile | 21 ++++++ composer.json | 4 +- src/Compiler.php | 183 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 2 deletions(-) create mode 100755 bin/compile create mode 100644 src/Compiler.php diff --git a/bin/blocktrail b/bin/blocktrail index 5d284e0..ad7834f 100755 --- a/bin/blocktrail +++ b/bin/blocktrail @@ -1,4 +1,4 @@ -#!/usr/bin/php +#!/usr/bin/env php compile($phar); + + chmod($phar, 0755); +} catch (\Exception $e) { + echo 'Failed to compile phar: ['.get_class($e).'] '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine(); + exit(1); +} diff --git a/composer.json b/composer.json index bc2ef1f..eb8cd0c 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,8 @@ }, "require-dev": { "phpunit/phpunit": "4.3.*", - "squizlabs/php_codesniffer": "1.*" + "squizlabs/php_codesniffer": "1.*", + "symfony/process": "~2.6", + "symfony/finder": "~2.6" } } diff --git a/src/Compiler.php b/src/Compiler.php new file mode 100644 index 0000000..11ef146 --- /dev/null +++ b/src/Compiler.php @@ -0,0 +1,183 @@ +run() != 0) { + throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from blocktrail git repository clone and that git binary is available.'); + } + $this->version = trim($process->getOutput()); + + $process = new Process('git log -n1 --pretty=%ci HEAD', __DIR__); + if ($process->run() != 0) { + throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from blocktrail git repository clone and that git binary is available.'); + } + + $date = new \DateTime(trim($process->getOutput())); + $date->setTimezone(new \DateTimeZone('UTC')); + $this->versionDate = $date->format('Y-m-d H:i:s'); + + $process = new Process('git describe --tags --exact-match HEAD'); + if ($process->run() == 0) { + $this->version = trim($process->getOutput()); + } else { + $this->version = "dev"; + } + + $phar = new \Phar($pharFile, 0, 'blocktrail.phar'); + $phar->setSignatureAlgorithm(\Phar::SHA1); + + $phar->startBuffering(); + + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->notName('Compiler.php') + ->in(__DIR__); + + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->exclude('Test') + ->exclude('Tests') + ->exclude('test') + ->exclude('tests') + ->exclude('phpunit') + ->exclude('php_codesniffer') + ->in(__DIR__ . '/../vendor/'); + + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../vendor/autoload.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../vendor/composer/autoload_namespaces.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../vendor/composer/autoload_psr4.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../vendor/composer/autoload_classmap.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../vendor/composer/autoload_real.php')); + if (file_exists(__DIR__ . '/../vendor/composer/include_paths.php')) { + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../vendor/composer/include_paths.php')); + } + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../vendor/composer/ClassLoader.php')); + $this->addBin($phar); + + // Stubs + $phar->setStub($this->getStub()); + + $phar->stopBuffering(); + + // can be disabled for interoperability with systems without gzip ext + $phar->compressFiles(\Phar::GZ); + + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../LICENSE.md'), false); + + unset($phar); + } + + private function addFile($phar, $file, $strip = true) { + $path = strtr(str_replace(dirname(__DIR__) . DIRECTORY_SEPARATOR, '', $file->getRealPath()), '\\', '/'); + + $content = file_get_contents($file); + if ($strip) { + $content = $this->stripWhitespace($content); + } elseif ('LICENSE.md' === basename($file)) { + $content = "\n" . $content . "\n"; + } + + $phar->addFromString($path, $content); + } + + private function addBin($phar) { + $content = file_get_contents(__DIR__ . '/../bin/blocktrail'); + $content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content); + $phar->addFromString('bin/blocktrail', $content); + } + + /** + * Removes whitespace from a PHP source string while preserving line numbers. + * + * @param string $source A PHP string + * @return string The PHP string with the whitespace removed + */ + private function stripWhitespace($source) { + if (!function_exists('token_get_all')) { + return $source; + } + + $output = ''; + foreach (token_get_all($source) as $token) { + if (is_string($token)) { + $output .= $token; + } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $output .= str_repeat("\n", substr_count($token[1], "\n")); + } elseif (T_WHITESPACE === $token[0]) { + // reduce wide spaces + $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); + // normalize newlines to \n + $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + // trim leading spaces + $whitespace = preg_replace('{\n +}', "\n", $whitespace); + $output .= $whitespace; + } else { + $output .= $token[1]; + } + } + + return $output; + } + + private function getStub() { + $stub = <<<'EOF' +#!/usr/bin/env php +=')) { + ini_set('apc.cache_by_default', 0); + } else { + fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running blocktrail commands.'.PHP_EOL); + fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL); + } +} + +Phar::mapPhar('blocktrail.phar'); + +EOF; + + return $stub . <<<'EOF' +require 'phar://blocktrail.phar/bin/blocktrail'; + +__HALT_COMPILER(); +EOF; + } +} From 3777889abedbdd4d948e8fee81c00c41b29e983a Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Fri, 23 Jan 2015 13:21:47 +0100 Subject: [PATCH 03/16] added balance and pay CLI commands --- src/Console/Application.php | 4 + src/Console/Commands/BalanceCommand.php | 42 ++++++++++ src/Console/Commands/PayCommand.php | 103 ++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 src/Console/Commands/BalanceCommand.php create mode 100644 src/Console/Commands/PayCommand.php diff --git a/src/Console/Application.php b/src/Console/Application.php index 32daca9..1ae69d6 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -4,10 +4,12 @@ use Blocktrail\SDK\BlocktrailSDK; use Blocktrail\SDK\BlocktrailSDKInterface; +use Blocktrail\SDK\Console\Commands\BalanceCommand; use Blocktrail\SDK\Console\Commands\ConfigureCommand; use Blocktrail\SDK\Console\Commands\CreateNewWalletCommand; use Blocktrail\SDK\Console\Commands\GetNewAddressCommand; use Blocktrail\SDK\Console\Commands\ListWalletsCommand; +use Blocktrail\SDK\Console\Commands\PayCommand; use Blocktrail\SDK\Console\Commands\UseWalletCommand; use Symfony\Component\Console\Application as ConsoleApplication; use Symfony\Component\Console\Formatter\OutputFormatterStyle; @@ -29,6 +31,8 @@ protected function getDefaultCommands() { $commands[] = new ConfigureCommand(); $commands[] = new UseWalletCommand(); $commands[] = new ListWalletsCommand(); + $commands[] = new PayCommand(); + $commands[] = new BalanceCommand(); return $commands; } diff --git a/src/Console/Commands/BalanceCommand.php b/src/Console/Commands/BalanceCommand.php new file mode 100644 index 0000000..e96b51d --- /dev/null +++ b/src/Console/Commands/BalanceCommand.php @@ -0,0 +1,42 @@ +setName('balance') + ->setDescription("Get wallet balance"); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + $wallet = $this->getWallet($input, $output); + + list($confirmed, $unconfirmed) = $wallet->getBalance(); + $final = $confirmed + $unconfirmed; + + $output->writeln("Confirmed Balance; {$confirmed} Satoshi = " . BlocktrailSDK::toBTCString($confirmed) . " BTC"); + $output->writeln("Unconfirmed Balance; {$unconfirmed} Satoshi = " . BlocktrailSDK::toBTCString($unconfirmed) . " BTC"); + $output->writeln("Final Balance; {$final} Satoshi = " . BlocktrailSDK::toBTCString($final) . " BTC"); + } +} diff --git a/src/Console/Commands/PayCommand.php b/src/Console/Commands/PayCommand.php new file mode 100644 index 0000000..27480d5 --- /dev/null +++ b/src/Console/Commands/PayCommand.php @@ -0,0 +1,103 @@ +setName('pay') + // ->setAliases(['send']) + ->setDescription("Send a payment") + ->addArgument("recipient", InputArgument::IS_ARRAY, "
:") + ->addOption('silent', 's', InputOption::VALUE_NONE, "don't ask for confirmation"); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $wallet = $this->getWallet($input, $output); + + $pay = []; + $address = null; + foreach ($input->getArgument("recipient") as $recipient) { + $recipient = explode(":", $recipient); + + if (count($recipient) == 2) { + if ($address) { + throw new \Exception("Bad input"); + } + + $address = $recipient[0]; + $value = $recipient[1]; + } else if (count($recipient) == 1) { + if (!$address) { + $address = $recipient[0]; + continue; + } else { + $value = $recipient[0]; + } + } else { + throw new \Exception("Bad input"); + } + + if (!BitcoinLib::validate_address($address)) { + throw new \Exception("Invalid address"); + } + + if (isset($pay[$address])) { + throw new \Exception("Same address apears twice in input"); + } + + if (strpos($value, ".") !== false || strpos($value, "," !== false)) { + $value = BlocktrailSDK::toSatoshi($value); + } else { + if (!$questionHelper->ask($input, $output, new ConfirmationQuestion("Did you specify this value in satoshi? [y/N] ", false))) { + $value = BlocktrailSDK::toSatoshi($value); + } + } + + $pay[$address] = $value; + $address = null; + } + + if ($address) { + throw new \Exception("Bad input"); + } + + if (!$input->getOption('silent')) { + $output->writeln("Sending payment to:"); + foreach ($pay as $address => $value) { + $output->writeln("[{$address}] {$value} Satoshi = " . BlocktrailSDK::toBTCString($value) . " BTC"); + } + + if (!$questionHelper->ask($input, $output, new ConfirmationQuestion("Send? [Y/n] ", true))) { + exit(1); + } + } + + $txHash = $wallet->pay($pay); + + $output->writeln("TX {$txHash}"); + } +} From 51ce1a0e9b56710173121a5960a4b0b0d33d2c46 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Fri, 23 Jan 2015 18:51:34 +0100 Subject: [PATCH 04/16] use API to list wallets --- src/Console/Commands/ListWalletsCommand.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Console/Commands/ListWalletsCommand.php b/src/Console/Commands/ListWalletsCommand.php index 787a7fd..2636cbb 100644 --- a/src/Console/Commands/ListWalletsCommand.php +++ b/src/Console/Commands/ListWalletsCommand.php @@ -10,6 +10,7 @@ use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\Output; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; @@ -21,7 +22,9 @@ protected function configure() { $this ->setName('list_wallets') // ->setAliases(['create_new_wallet', 'create_wallet']) - ->setDescription("List all wallets"); + ->setDescription("List all wallets") + ->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'pagination page', 1) + ->addOption('per-page', 'pp', InputOption::VALUE_REQUIRED, 'pagination limit', 50); parent::configure(); } @@ -33,11 +36,10 @@ protected function execute(InputInterface $input, OutputInterface $output) { $sdk = $this->getBlocktrailSDK(); $config = $this->getConfig(); - // $wallets = $sdk->getWallets(); - $wallets = [ - ['identifier' => 'cli-created-wallet', 'balance' => BlocktrailSDK::toSatoshi(28311.3238283)], - ['identifier' => 'another-cli-wallet', 'balance' => BlocktrailSDK::toSatoshi(0.32)], - ]; + $page = $input->getOption('page'); + $perpage = $input->getOption('per-page'); + + $wallets = $sdk->allWallets($page, $perpage)['data']; if (!$wallets) { $output->writeln("There are no wallets!"); @@ -53,5 +55,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { } $table->render(); + + if (count($wallets) >= $perpage) { + $output->writeln("there are more wallets, use --page and --perpage to see all of them ..."); + } } } From afa75e2d8c795e89d3d05cc76fc54cfc8b9b0dab Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Fri, 23 Jan 2015 19:22:31 +0100 Subject: [PATCH 05/16] prompt with option list for wallet --- src/Console/Commands/UseWalletCommand.php | 78 ++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/src/Console/Commands/UseWalletCommand.php b/src/Console/Commands/UseWalletCommand.php index 742e571..42518c6 100644 --- a/src/Console/Commands/UseWalletCommand.php +++ b/src/Console/Commands/UseWalletCommand.php @@ -2,6 +2,7 @@ namespace Blocktrail\SDK\Console\Commands; +use Blocktrail\SDK\BlocktrailSDKInterface; use Blocktrail\SDK\Console\Application; use Blocktrail\SDK\WalletInterface; use Symfony\Component\Console\Command\Command; @@ -12,33 +13,44 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\Output; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; class UseWalletCommand extends AbstractCommand { + const OPTION_MORE = "more ..."; + const OPTION_LESS = "less ..."; + const OPTION_FREEFORM = "let me type it"; + const OPTION_NO_DEFAULT = "no default"; + protected function configure() { $this ->setName('default_wallet') ->setDescription("Configure default wallet to use") ->addOption('identifier', null, InputOption::VALUE_REQUIRED, 'Wallet identifier') ->addOption('passphrase', null, InputOption::VALUE_REQUIRED, 'Wallet passphrase'); + + parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output) { /** @var Output $output */ + parent::execute($input, $output); + /** @var QuestionHelper $questionHelper */ $questionHelper = $this->getHelper('question'); + $sdk = $this->getBlocktrailSDK(); $interactive = true; // @TODO; $identifier = trim($input->getOption('identifier')); $passphrase = trim($input->getOption('passphrase')); if ($interactive) { if (!$identifier) { - $question = new Question("Set the default wallet identifier to use (blank to not set a default identifier): \n"); - $identifier = $questionHelper->ask($input, $output, $question); + $identifier = $this->promptForIdentifier($input, $output, $sdk); } + if (!$passphrase) { $question = new Question("Set the default wallet passphrase to use (blank to not set a default passphrase): \n"); $question->setHidden(true); @@ -46,9 +58,71 @@ protected function execute(InputInterface $input, OutputInterface $output) { } } + if ($identifier && $passphrase) { + $this->getBlocktrailSDK()->initWallet($identifier, $passphrase); + } + $this->updateConfig([ 'identifier' => $identifier, 'passphrase' => $passphrase, ]); + + $output->writeln("OK!"); + } + + protected function promptForIdentifier($input, $output, BlocktrailSDKInterface $sdk) { + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $identifier = null; + + $page = 1; + $perpage = 50; + + while (!$identifier) { + $wallets = $sdk->allWallets($page, $perpage)['data']; + $fill = ($perpage * ($page - 1) + 1); + $options = array_slice( + array_merge( + array_fill(0, $fill, ''), + array_column($wallets, 'identifier') + ), + $fill, + null, + true + ); + + if (count($wallets) >= $perpage) { + $options['more'] = self::OPTION_MORE; + } + if ($page > 1) { + $options['less'] = self::OPTION_LESS; + } + + $options['no'] = self::OPTION_NO_DEFAULT; + $options['manual'] = self::OPTION_FREEFORM; + + $question = new ChoiceQuestion("Please select the wallet you'd like to use as default", $options, null); + $question->setAutocompleterValues([]); + $choice = $questionHelper->ask($input, $output, $question); + + if ($choice == self::OPTION_NO_DEFAULT) { + $identifier = null; + break; + } else if ($choice == self::OPTION_FREEFORM) { + $question = new Question("Please fill in the wallet identifier you'd like to use as default? "); + $identifier = $questionHelper->ask($input, $output, $question); + } else if ($choice == self::OPTION_MORE) { + $page += 1; + + } else if ($choice == self::OPTION_LESS) { + $page -= 1; + + } else { + $identifier = $choice; + } + } + + return $identifier; } } From 16032494de57cc59b59b952ef8b67e3af72cfac0 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Wed, 28 Jan 2015 16:25:36 +0100 Subject: [PATCH 06/16] make CLI tool config file configurable --- src/Console/Commands/AbstractCommand.php | 24 +++++++++++-------- .../Commands/AbstractWalletCommand.php | 2 +- src/Console/Commands/ConfigureCommand.php | 9 ++++--- src/Console/Commands/ListWalletsCommand.php | 2 +- src/Console/Commands/UseWalletCommand.php | 2 +- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Console/Commands/AbstractCommand.php b/src/Console/Commands/AbstractCommand.php index 7731675..1a3b8bc 100644 --- a/src/Console/Commands/AbstractCommand.php +++ b/src/Console/Commands/AbstractCommand.php @@ -30,15 +30,19 @@ public function getApplication() { } protected function configure() { + $dir = "{$_SERVER['HOME']}/.blocktrail"; + $file = "{$dir}/config.json"; + $this ->addOption('api_key', null, InputOption::VALUE_REQUIRED, 'API_KEY to be used') ->addOption('api_secret', null, InputOption::VALUE_REQUIRED, 'API_SECRET to be used') - ->addOption('testnet', null, InputOption::VALUE_NONE, 'use testnet instead of mainnet'); + ->addOption('testnet', null, InputOption::VALUE_NONE, 'use testnet instead of mainnet') + ->addOption('config', 'c', InputOption::VALUE_REQUIRED, "config file to use; defaults to {$file}", $file); } protected function execute(InputInterface $input, OutputInterface $output) { /** @var Output $output */ - $config = $this->getConfig(); + $config = $this->getConfig($input); $this->apiKey = $input->hasOptionInput('api_key') ? trim($input->getOption('api_key')) : (isset($config['api_key']) ? $config['api_key'] : null); $this->apiSecret = $input->hasOptionInput('api_secret') ? trim($input->getOption('api_secret')) : (isset($config['api_secret']) ? $config['api_secret'] : null); @@ -59,9 +63,8 @@ public function getBlocktrailSDK() { return new BlocktrailSDK($this->apiKey, $this->apiSecret, "BTC", $this->testnet); } - public function getConfig() { - $dir = "{$_SERVER['HOME']}/.blocktrail"; - $file = "{$dir}/config.json"; + public function getConfig(InputInterface $input) { + $file = $input->getOption('config'); if (!file_exists($file)) { return []; @@ -70,19 +73,20 @@ public function getConfig() { return json_decode(file_get_contents($file), true); } - public function replaceConfig(array $config) { - $dir = "{$_SERVER['HOME']}/.blocktrail"; + public function replaceConfig(InputInterface $input, array $config) { + $file = $input->getOption('config'); + $dir = dirname($file); if (!file_exists($dir)) { mkdir($dir); } - file_put_contents("{$dir}/config.json", json_encode($config)); + file_put_contents($file, json_encode($config)); return null; } - public function updateConfig(array $config) { - return $this->replaceConfig(array_replace($this->getConfig(), $config)); + public function updateConfig(InputInterface $input, array $config) { + return $this->replaceConfig($input, array_replace($this->getConfig($input), $config)); } } diff --git a/src/Console/Commands/AbstractWalletCommand.php b/src/Console/Commands/AbstractWalletCommand.php index ebcea6e..c548cf1 100644 --- a/src/Console/Commands/AbstractWalletCommand.php +++ b/src/Console/Commands/AbstractWalletCommand.php @@ -32,7 +32,7 @@ protected function configure() { protected function execute(InputInterface $input, OutputInterface $output) { parent::execute($input, $output); - $config = $this->getConfig(); + $config = $this->getConfig($input); $this->identifier = $input->hasOptionInput('identifier') ? trim($input->getOption('identifier')) : (isset($config['identifier']) ? $config['identifier'] : null); $this->passphrase = $input->hasOptionInput('passphrase') ? trim($input->getOption('passphrase')) : (isset($config['passphrase']) ? $config['passphrase'] : null); diff --git a/src/Console/Commands/ConfigureCommand.php b/src/Console/Commands/ConfigureCommand.php index cbf8023..f42ab23 100644 --- a/src/Console/Commands/ConfigureCommand.php +++ b/src/Console/Commands/ConfigureCommand.php @@ -20,10 +20,9 @@ protected function configure() { $this ->setName('configure') // ->setAliases(['setup']) - ->setDescription("Configure credentials") - ->addOption('api_key', null, InputOption::VALUE_REQUIRED, 'API_KEY to be used') - ->addOption('api_secret', null, InputOption::VALUE_REQUIRED, 'API_SECRET to be used') - ->addOption('testnet', null, InputOption::VALUE_NONE, 'use testnet instead of mainnet'); + ->setDescription("Configure credentials"); + + parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output) { @@ -52,7 +51,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { } } - $this->replaceConfig([ + $this->replaceConfig($input, [ 'api_key' => $apiKey, 'api_secret' => $apiSecret, 'testnet' => $testnet diff --git a/src/Console/Commands/ListWalletsCommand.php b/src/Console/Commands/ListWalletsCommand.php index 2636cbb..b5eb707 100644 --- a/src/Console/Commands/ListWalletsCommand.php +++ b/src/Console/Commands/ListWalletsCommand.php @@ -34,7 +34,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { parent::execute($input, $output); $sdk = $this->getBlocktrailSDK(); - $config = $this->getConfig(); + $config = $this->getConfig($input); $page = $input->getOption('page'); $perpage = $input->getOption('per-page'); diff --git a/src/Console/Commands/UseWalletCommand.php b/src/Console/Commands/UseWalletCommand.php index 42518c6..909543c 100644 --- a/src/Console/Commands/UseWalletCommand.php +++ b/src/Console/Commands/UseWalletCommand.php @@ -62,7 +62,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $this->getBlocktrailSDK()->initWallet($identifier, $passphrase); } - $this->updateConfig([ + $this->updateConfig($input, [ 'identifier' => $identifier, 'passphrase' => $passphrase, ]); From d1e0b23e96372624de5bd8ed3c5a94d4b76792ba Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 29 Jan 2015 12:06:54 +0100 Subject: [PATCH 07/16] better wallet management --- src/BlocktrailSDK.php | 1 + src/Console/Application.php | 6 +- src/Console/Commands/AbstractCommand.php | 6 +- .../Commands/AbstractWalletCommand.php | 9 +- src/Console/Commands/ListWalletsCommand.php | 2 +- src/Console/Commands/PayCommand.php | 2 +- .../Commands/StoreWalletPassphraseCommand.php | 131 ++++++++++++++++++ src/Console/Commands/UseWalletCommand.php | 25 ++-- src/Exceptions/WalletChecksumException.php | 11 ++ src/Wallet.php | 3 +- 10 files changed, 174 insertions(+), 22 deletions(-) create mode 100644 src/Console/Commands/StoreWalletPassphraseCommand.php create mode 100644 src/Exceptions/WalletChecksumException.php diff --git a/src/BlocktrailSDK.php b/src/BlocktrailSDK.php index 7d42bd9..6882da9 100644 --- a/src/BlocktrailSDK.php +++ b/src/BlocktrailSDK.php @@ -7,6 +7,7 @@ use BitWasp\BitcoinLib\BitcoinLib; use BitWasp\BitcoinLib\RawTransaction; use Blocktrail\SDK\Connection\RestClient; +use Blocktrail\SDK\Exceptions\WalletChecksumException; /** * Class BlocktrailSDK diff --git a/src/Console/Application.php b/src/Console/Application.php index 1ae69d6..5b6db63 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -10,6 +10,7 @@ use Blocktrail\SDK\Console\Commands\GetNewAddressCommand; use Blocktrail\SDK\Console\Commands\ListWalletsCommand; use Blocktrail\SDK\Console\Commands\PayCommand; +use Blocktrail\SDK\Console\Commands\StoreWalletPassphraseCommand; use Blocktrail\SDK\Console\Commands\UseWalletCommand; use Symfony\Component\Console\Application as ConsoleApplication; use Symfony\Component\Console\Formatter\OutputFormatterStyle; @@ -26,11 +27,12 @@ class Application extends ConsoleApplication { protected function getDefaultCommands() { $commands = parent::getDefaultCommands(); + $commands[] = new ConfigureCommand(); + $commands[] = new ListWalletsCommand(); $commands[] = new CreateNewWalletCommand(); $commands[] = new GetNewAddressCommand(); - $commands[] = new ConfigureCommand(); + $commands[] = new StoreWalletPassphraseCommand(); $commands[] = new UseWalletCommand(); - $commands[] = new ListWalletsCommand(); $commands[] = new PayCommand(); $commands[] = new BalanceCommand(); diff --git a/src/Console/Commands/AbstractCommand.php b/src/Console/Commands/AbstractCommand.php index 1a3b8bc..f1ee669 100644 --- a/src/Console/Commands/AbstractCommand.php +++ b/src/Console/Commands/AbstractCommand.php @@ -63,6 +63,10 @@ public function getBlocktrailSDK() { return new BlocktrailSDK($this->apiKey, $this->apiSecret, "BTC", $this->testnet); } + protected function getNetwork() { + return $this->testnet ? "tBTC" : "BTC"; + } + public function getConfig(InputInterface $input) { $file = $input->getOption('config'); @@ -87,6 +91,6 @@ public function replaceConfig(InputInterface $input, array $config) { } public function updateConfig(InputInterface $input, array $config) { - return $this->replaceConfig($input, array_replace($this->getConfig($input), $config)); + return $this->replaceConfig($input, array_replace_recursive($this->getConfig($input), $config)); } } diff --git a/src/Console/Commands/AbstractWalletCommand.php b/src/Console/Commands/AbstractWalletCommand.php index c548cf1..b6db316 100644 --- a/src/Console/Commands/AbstractWalletCommand.php +++ b/src/Console/Commands/AbstractWalletCommand.php @@ -34,12 +34,17 @@ protected function execute(InputInterface $input, OutputInterface $output) { $config = $this->getConfig($input); - $this->identifier = $input->hasOptionInput('identifier') ? trim($input->getOption('identifier')) : (isset($config['identifier']) ? $config['identifier'] : null); - $this->passphrase = $input->hasOptionInput('passphrase') ? trim($input->getOption('passphrase')) : (isset($config['passphrase']) ? $config['passphrase'] : null); + $this->identifier = $input->hasOptionInput('identifier') ? trim($input->getOption('identifier')) : (isset($config[$this->getNetwork()]['default_wallet']) ? $config[$this->getNetwork()]['default_wallet'] : null); if (!$this->identifier) { throw new \RuntimeException('indentifier is required.'); } + + if ($input->hasOptionInput('passphrase')) { + $this->passphrase = trim($input->getOption('passphrase')); + } else if (isset($config[$this->getNetwork()]['wallet_passphrase'][$this->identifier])) { + $this->passphrase = $config[$this->getNetwork()]['wallet_passphrase'][$this->identifier]; + } } protected function getWallet(InputInterface $input, OutputInterface $output) { diff --git a/src/Console/Commands/ListWalletsCommand.php b/src/Console/Commands/ListWalletsCommand.php index b5eb707..9c4a345 100644 --- a/src/Console/Commands/ListWalletsCommand.php +++ b/src/Console/Commands/ListWalletsCommand.php @@ -49,7 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $table = new Table($output); $table->setHeaders(['identifier', 'balance', '']); foreach ($wallets as $wallet) { - $isDefault = isset($config['identifier']) && $config['identifier'] == $wallet['identifier']; + $isDefault = isset($config['default_wallet']) && $config['default_wallet'] == $wallet['identifier']; $table->addRow([$wallet['identifier'], BlocktrailSDK::toBTCString($wallet['balance']), $isDefault ? 'IS_DEFAULT' : '']); } diff --git a/src/Console/Commands/PayCommand.php b/src/Console/Commands/PayCommand.php index 27480d5..2fab3f9 100644 --- a/src/Console/Commands/PayCommand.php +++ b/src/Console/Commands/PayCommand.php @@ -86,7 +86,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { } if (!$input->getOption('silent')) { - $output->writeln("Sending payment to:"); + $output->writeln("Sending payment from [{$wallet->getIdentifier()}] to:"); foreach ($pay as $address => $value) { $output->writeln("[{$address}] {$value} Satoshi = " . BlocktrailSDK::toBTCString($value) . " BTC"); } diff --git a/src/Console/Commands/StoreWalletPassphraseCommand.php b/src/Console/Commands/StoreWalletPassphraseCommand.php new file mode 100644 index 0000000..63a66ef --- /dev/null +++ b/src/Console/Commands/StoreWalletPassphraseCommand.php @@ -0,0 +1,131 @@ +setName('store_wallet_passphrase') + ->setDescription("Store a wallets passphrase") + ->addOption('identifier', null, InputOption::VALUE_REQUIRED, 'Wallet identifier') + ->addOption('passphrase', null, InputOption::VALUE_REQUIRED, 'Wallet passphrase'); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $sdk = $this->getBlocktrailSDK(); + $interactive = true; // @TODO; + $identifier = trim($input->getOption('identifier')); + $passphrase = trim($input->getOption('passphrase')); + + if ($interactive) { + if (!$identifier) { + $identifier = $this->promptForIdentifier($input, $output, $sdk); + } + + if (!$passphrase) { + $question = new Question("Input the wallet passphrase to store (blank to not/un set the default passphrase): \n"); + $question->setHidden(true); + $passphrase = $questionHelper->ask($input, $output, $question); + } + } + + if ($identifier && $passphrase) { + $this->getBlocktrailSDK()->initWallet($identifier, $passphrase); + } + + $this->updateConfig($input, [ + $this->getNetwork() => [ + 'wallet_passphrase' => [ + $identifier => $passphrase + ] + ] + ]); + + $output->writeln("OK!"); + } + + protected function promptForIdentifier($input, $output, BlocktrailSDKInterface $sdk) { + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $identifier = null; + + $page = 1; + $perpage = 50; + + while (!$identifier) { + $wallets = $sdk->allWallets($page, $perpage)['data']; + $fill = ($perpage * ($page - 1) + 1); + $options = array_slice( + array_merge( + array_fill(0, $fill, ''), + array_column($wallets, 'identifier') + ), + $fill, + null, + true + ); + + if (count($wallets) >= $perpage) { + $options['more'] = self::OPTION_MORE; + } + if ($page > 1) { + $options['less'] = self::OPTION_LESS; + } + + $options['no'] = self::OPTION_NO_DEFAULT; + $options['manual'] = self::OPTION_FREEFORM; + + $question = new ChoiceQuestion("Please select the wallet you'd like to store a passphrase for", $options, null); + $question->setAutocompleterValues([]); + $choice = $questionHelper->ask($input, $output, $question); + + if ($choice == self::OPTION_NO_DEFAULT) { + $identifier = null; + break; + } else if ($choice == self::OPTION_FREEFORM) { + $question = new Question("Please fill in the wallet identifier you'd like to store a passphrase for? "); + $identifier = $questionHelper->ask($input, $output, $question); + } else if ($choice == self::OPTION_MORE) { + $page += 1; + + } else if ($choice == self::OPTION_LESS) { + $page -= 1; + + } else { + $identifier = $choice; + } + } + + return $identifier; + } +} diff --git a/src/Console/Commands/UseWalletCommand.php b/src/Console/Commands/UseWalletCommand.php index 909543c..7038405 100644 --- a/src/Console/Commands/UseWalletCommand.php +++ b/src/Console/Commands/UseWalletCommand.php @@ -4,6 +4,8 @@ use Blocktrail\SDK\BlocktrailSDKInterface; use Blocktrail\SDK\Console\Application; +use Blocktrail\SDK\Exceptions\BlocktrailSDKException; +use Blocktrail\SDK\Exceptions\WalletChecksumException; use Blocktrail\SDK\WalletInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\QuestionHelper; @@ -38,33 +40,28 @@ protected function execute(InputInterface $input, OutputInterface $output) { /** @var Output $output */ parent::execute($input, $output); - /** @var QuestionHelper $questionHelper */ - $questionHelper = $this->getHelper('question'); - $sdk = $this->getBlocktrailSDK(); $interactive = true; // @TODO; $identifier = trim($input->getOption('identifier')); - $passphrase = trim($input->getOption('passphrase')); if ($interactive) { if (!$identifier) { $identifier = $this->promptForIdentifier($input, $output, $sdk); } - - if (!$passphrase) { - $question = new Question("Set the default wallet passphrase to use (blank to not set a default passphrase): \n"); - $question->setHidden(true); - $passphrase = $questionHelper->ask($input, $output, $question); - } } - if ($identifier && $passphrase) { - $this->getBlocktrailSDK()->initWallet($identifier, $passphrase); + if ($identifier) { + try { + $this->getBlocktrailSDK()->initWallet($identifier, ""); + } catch (WalletChecksumException $e) { + // OK + } } $this->updateConfig($input, [ - 'identifier' => $identifier, - 'passphrase' => $passphrase, + $this->getNetwork() => [ + 'default_wallet' => $identifier + ] ]); $output->writeln("OK!"); diff --git a/src/Exceptions/WalletChecksumException.php b/src/Exceptions/WalletChecksumException.php new file mode 100644 index 0000000..2b98246 --- /dev/null +++ b/src/Exceptions/WalletChecksumException.php @@ -0,0 +1,11 @@ +checksum) { - throw new \Exception("Checksum [{$checksum}] does not match [{$this->checksum}], most likely due to incorrect password"); + throw new WalletChecksumException("Checksum [{$checksum}] does not match [{$this->checksum}], most likely due to incorrect password"); } $this->locked = false; From 8d01fa27dcc887b04c5c9c0f7a3f8f264bfb1b8d Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 12 Feb 2015 19:28:06 +0100 Subject: [PATCH 08/16] fixed bad method usage hasOptionInput --- src/Console/Commands/AbstractCommand.php | 6 +++--- src/Console/Commands/AbstractWalletCommand.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Console/Commands/AbstractCommand.php b/src/Console/Commands/AbstractCommand.php index f1ee669..a7a099f 100644 --- a/src/Console/Commands/AbstractCommand.php +++ b/src/Console/Commands/AbstractCommand.php @@ -44,9 +44,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { /** @var Output $output */ $config = $this->getConfig($input); - $this->apiKey = $input->hasOptionInput('api_key') ? trim($input->getOption('api_key')) : (isset($config['api_key']) ? $config['api_key'] : null); - $this->apiSecret = $input->hasOptionInput('api_secret') ? trim($input->getOption('api_secret')) : (isset($config['api_secret']) ? $config['api_secret'] : null); - $this->testnet = $input->hasOptionInput('testnet') ? $input->getOption('testnet') : (isset($config['testnet']) ? $config['testnet'] : false); + $this->apiKey = trim($input->getOption('api_key')) ?: (isset($config['api_key']) ? $config['api_key'] : null); + $this->apiSecret = trim($input->getOption('api_secret')) ?: (isset($config['api_secret']) ? $config['api_secret'] : null); + $this->testnet = $input->getOption('testnet') ?: (isset($config['testnet']) ? $config['testnet'] : false); if (!$this->apiKey) { throw new \RuntimeException('API_KEY is required.'); diff --git a/src/Console/Commands/AbstractWalletCommand.php b/src/Console/Commands/AbstractWalletCommand.php index b6db316..0396058 100644 --- a/src/Console/Commands/AbstractWalletCommand.php +++ b/src/Console/Commands/AbstractWalletCommand.php @@ -34,13 +34,13 @@ protected function execute(InputInterface $input, OutputInterface $output) { $config = $this->getConfig($input); - $this->identifier = $input->hasOptionInput('identifier') ? trim($input->getOption('identifier')) : (isset($config[$this->getNetwork()]['default_wallet']) ? $config[$this->getNetwork()]['default_wallet'] : null); + $this->identifier = trim($input->getOption('identifier')) ?: (isset($config[$this->getNetwork()]['default_wallet']) ? $config[$this->getNetwork()]['default_wallet'] : null); if (!$this->identifier) { throw new \RuntimeException('indentifier is required.'); } - if ($input->hasOptionInput('passphrase')) { + if ($input->getOption('passphrase')) { $this->passphrase = trim($input->getOption('passphrase')); } else if (isset($config[$this->getNetwork()]['wallet_passphrase'][$this->identifier])) { $this->passphrase = $config[$this->getNetwork()]['wallet_passphrase'][$this->identifier]; From 175a27dd502ef80698e5b881e6695662979048aa Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 12 Feb 2015 20:32:23 +0100 Subject: [PATCH 09/16] UTXO list in CLI --- src/Console/Application.php | 2 + src/Console/Commands/ListUTXOCommand.php | 55 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/Console/Commands/ListUTXOCommand.php diff --git a/src/Console/Application.php b/src/Console/Application.php index 5b6db63..583c6a9 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -8,6 +8,7 @@ use Blocktrail\SDK\Console\Commands\ConfigureCommand; use Blocktrail\SDK\Console\Commands\CreateNewWalletCommand; use Blocktrail\SDK\Console\Commands\GetNewAddressCommand; +use Blocktrail\SDK\Console\Commands\ListUTXOCommand; use Blocktrail\SDK\Console\Commands\ListWalletsCommand; use Blocktrail\SDK\Console\Commands\PayCommand; use Blocktrail\SDK\Console\Commands\StoreWalletPassphraseCommand; @@ -35,6 +36,7 @@ protected function getDefaultCommands() { $commands[] = new UseWalletCommand(); $commands[] = new PayCommand(); $commands[] = new BalanceCommand(); + $commands[] = new ListUTXOCommand(); return $commands; } diff --git a/src/Console/Commands/ListUTXOCommand.php b/src/Console/Commands/ListUTXOCommand.php new file mode 100644 index 0000000..2c5718e --- /dev/null +++ b/src/Console/Commands/ListUTXOCommand.php @@ -0,0 +1,55 @@ +setName('list_utxos') + // ->setAliases(['utxos']) + ->setDescription("List UTXO set for a wallet") + ->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'pagination page', 1) + ->addOption('per-page', 'pp', InputOption::VALUE_REQUIRED, 'pagination limit', 50);; + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + $wallet = $this->getWallet($input, $output); + + $page = $input->getOption('page'); + $perpage = $input->getOption('per-page'); + + $UTXOs = $wallet->utxos($page, $perpage)['data']; + + $table = new Table($output); + $table->setHeaders(['tx', 'idx', 'value', 'address']); + foreach ($UTXOs as $UTXO) { + $table->addRow([$UTXO['hash'], $UTXO['idx'], BlocktrailSDK::toBTCString($UTXO['value']), $UTXO['address']]); + } + + $table->render(); + + if (count($UTXOs) >= $perpage) { + $output->writeln("there are more wallets, use --page and --perpage to see all of them ..."); + } + } +} From 1f14577812c33b99f182bf0f085cab27c6f75e87 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 12 Feb 2015 20:42:15 +0100 Subject: [PATCH 10/16] added wallet discovery command --- src/Console/Application.php | 2 ++ src/Console/Commands/DiscoveryCommand.php | 42 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/Console/Commands/DiscoveryCommand.php diff --git a/src/Console/Application.php b/src/Console/Application.php index 583c6a9..f9f8ebb 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -7,6 +7,7 @@ use Blocktrail\SDK\Console\Commands\BalanceCommand; use Blocktrail\SDK\Console\Commands\ConfigureCommand; use Blocktrail\SDK\Console\Commands\CreateNewWalletCommand; +use Blocktrail\SDK\Console\Commands\DiscoveryCommand; use Blocktrail\SDK\Console\Commands\GetNewAddressCommand; use Blocktrail\SDK\Console\Commands\ListUTXOCommand; use Blocktrail\SDK\Console\Commands\ListWalletsCommand; @@ -36,6 +37,7 @@ protected function getDefaultCommands() { $commands[] = new UseWalletCommand(); $commands[] = new PayCommand(); $commands[] = new BalanceCommand(); + $commands[] = new DiscoveryCommand(); $commands[] = new ListUTXOCommand(); return $commands; diff --git a/src/Console/Commands/DiscoveryCommand.php b/src/Console/Commands/DiscoveryCommand.php new file mode 100644 index 0000000..460335f --- /dev/null +++ b/src/Console/Commands/DiscoveryCommand.php @@ -0,0 +1,42 @@ +setName('discovery') + ->setDescription("Do wallet discovery"); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + $wallet = $this->getWallet($input, $output); + + list($confirmed, $unconfirmed) = $wallet->doDiscovery(); + $final = $confirmed + $unconfirmed; + + $output->writeln("Confirmed Balance; {$confirmed} Satoshi = " . BlocktrailSDK::toBTCString($confirmed) . " BTC"); + $output->writeln("Unconfirmed Balance; {$unconfirmed} Satoshi = " . BlocktrailSDK::toBTCString($unconfirmed) . " BTC"); + $output->writeln("Final Balance; {$final} Satoshi = " . BlocktrailSDK::toBTCString($final) . " BTC"); + } +} From 89a95803e851a700f53b7749f8bab41cdba86c53 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Fri, 13 Feb 2015 10:11:00 +0100 Subject: [PATCH 11/16] fixed IS_DEFAULT flag display --- src/Console/Commands/ListWalletsCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Commands/ListWalletsCommand.php b/src/Console/Commands/ListWalletsCommand.php index 9c4a345..3a128a2 100644 --- a/src/Console/Commands/ListWalletsCommand.php +++ b/src/Console/Commands/ListWalletsCommand.php @@ -49,7 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $table = new Table($output); $table->setHeaders(['identifier', 'balance', '']); foreach ($wallets as $wallet) { - $isDefault = isset($config['default_wallet']) && $config['default_wallet'] == $wallet['identifier']; + $isDefault = isset($config[$this->getNetwork()]['default_wallet']) && $config[$this->getNetwork()]['default_wallet'] == $wallet['identifier']; $table->addRow([$wallet['identifier'], BlocktrailSDK::toBTCString($wallet['balance']), $isDefault ? 'IS_DEFAULT' : '']); } From 6cf568d9daeef82c865c0ebad4ba1abd1642ce06 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Fri, 13 Feb 2015 11:47:14 +0100 Subject: [PATCH 12/16] split UTXOs command --- src/Console/Application.php | 2 + src/Console/Commands/ListUTXOCommand.php | 2 +- src/Console/Commands/SplitUTXOsCommand.php | 84 ++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/Console/Commands/SplitUTXOsCommand.php diff --git a/src/Console/Application.php b/src/Console/Application.php index f9f8ebb..685476d 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -12,6 +12,7 @@ use Blocktrail\SDK\Console\Commands\ListUTXOCommand; use Blocktrail\SDK\Console\Commands\ListWalletsCommand; use Blocktrail\SDK\Console\Commands\PayCommand; +use Blocktrail\SDK\Console\Commands\SplitUTXOsCommand; use Blocktrail\SDK\Console\Commands\StoreWalletPassphraseCommand; use Blocktrail\SDK\Console\Commands\UseWalletCommand; use Symfony\Component\Console\Application as ConsoleApplication; @@ -39,6 +40,7 @@ protected function getDefaultCommands() { $commands[] = new BalanceCommand(); $commands[] = new DiscoveryCommand(); $commands[] = new ListUTXOCommand(); + $commands[] = new SplitUTXOsCommand(); return $commands; } diff --git a/src/Console/Commands/ListUTXOCommand.php b/src/Console/Commands/ListUTXOCommand.php index 2c5718e..0756362 100644 --- a/src/Console/Commands/ListUTXOCommand.php +++ b/src/Console/Commands/ListUTXOCommand.php @@ -24,7 +24,7 @@ protected function configure() { // ->setAliases(['utxos']) ->setDescription("List UTXO set for a wallet") ->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'pagination page', 1) - ->addOption('per-page', 'pp', InputOption::VALUE_REQUIRED, 'pagination limit', 50);; + ->addOption('per-page', 'pp', InputOption::VALUE_REQUIRED, 'pagination limit', 50); parent::configure(); } diff --git a/src/Console/Commands/SplitUTXOsCommand.php b/src/Console/Commands/SplitUTXOsCommand.php new file mode 100644 index 0000000..01c207b --- /dev/null +++ b/src/Console/Commands/SplitUTXOsCommand.php @@ -0,0 +1,84 @@ +setName('split_utxos') + ->setDescription("Split UTXOs") + ->addArgument("count", InputArgument::REQUIRED, "the amount of chunks") + ->addArgument("value", InputArgument::REQUIRED, "the value of each chunk") + ->addOption('value-is-total', null, InputOption::VALUE_NONE, "the value argument should be devided by the count") + ->addOption('silent', 's', InputOption::VALUE_NONE, "don't ask for confirmation"); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var Output $output */ + parent::execute($input, $output); + + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + $wallet = $this->getWallet($input, $output); + + list($confirmed, $unconfirmed) = $wallet->getBalance(); + + $count = $input->getArgument('count'); + $value = $input->getArgument('value'); + + if (strpos($value, ".") !== false || strpos($value, "," !== false)) { + $value = BlocktrailSDK::toSatoshi($value); + } else { + if (!$questionHelper->ask($input, $output, new ConfirmationQuestion("Did you specify this value in satoshi? [y/N] ", false))) { + $value = BlocktrailSDK::toSatoshi($value); + } + } + + if ($input->getOption('value-is-total')) { + $value = (int)floor($value / $count); + } + + if ($value * $count > $confirmed) { + $output->writeln("You do not have enough confirmed balance; " . BlocktrailSDK::toBTCString($value * $count) . " BTC > " . BlocktrailSDK::toBTCString($confirmed) . " BTC) "); + exit(1); + } + + $pay = []; + for ($i = 0; $i < $count; $i++) { + $pay[$wallet->getNewAddress()] = $value; + } + + if (!$input->getOption('silent')) { + $output->writeln("Sending payment from [{$wallet->getIdentifier()}] to:"); + foreach ($pay as $address => $value) { + $output->writeln("[{$address}] {$value} Satoshi = " . BlocktrailSDK::toBTCString($value) . " BTC"); + } + + if (!$questionHelper->ask($input, $output, new ConfirmationQuestion("Send? [Y/n] ", true))) { + exit(1); + } + } + + $txHash = $wallet->pay($pay); + + $output->writeln("TX {$txHash}"); + } +} From d6c5098a49b96a4a762f4d3675a811bbbbce3e9e Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Wed, 4 Mar 2015 15:00:49 +0100 Subject: [PATCH 13/16] list confirmations in UTXOs --- src/Console/Commands/ListUTXOCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/Commands/ListUTXOCommand.php b/src/Console/Commands/ListUTXOCommand.php index 0756362..8d84b7f 100644 --- a/src/Console/Commands/ListUTXOCommand.php +++ b/src/Console/Commands/ListUTXOCommand.php @@ -41,9 +41,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { $UTXOs = $wallet->utxos($page, $perpage)['data']; $table = new Table($output); - $table->setHeaders(['tx', 'idx', 'value', 'address']); + $table->setHeaders(['tx', 'idx', 'value', 'confirmations', 'address']); foreach ($UTXOs as $UTXO) { - $table->addRow([$UTXO['hash'], $UTXO['idx'], BlocktrailSDK::toBTCString($UTXO['value']), $UTXO['address']]); + $table->addRow([$UTXO['hash'], $UTXO['idx'], BlocktrailSDK::toBTCString($UTXO['value']), $UTXO['confirmations'], $UTXO['address']]); } $table->render(); From e969a76291382e6f0dd5e44630d1f304141207e5 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 5 Mar 2015 11:30:32 +0100 Subject: [PATCH 14/16] nice list UTXOs output --- src/Console/Commands/ListUTXOCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Console/Commands/ListUTXOCommand.php b/src/Console/Commands/ListUTXOCommand.php index 8d84b7f..9285eb1 100644 --- a/src/Console/Commands/ListUTXOCommand.php +++ b/src/Console/Commands/ListUTXOCommand.php @@ -41,15 +41,15 @@ protected function execute(InputInterface $input, OutputInterface $output) { $UTXOs = $wallet->utxos($page, $perpage)['data']; $table = new Table($output); - $table->setHeaders(['tx', 'idx', 'value', 'confirmations', 'address']); - foreach ($UTXOs as $UTXO) { - $table->addRow([$UTXO['hash'], $UTXO['idx'], BlocktrailSDK::toBTCString($UTXO['value']), $UTXO['confirmations'], $UTXO['address']]); + $table->setHeaders(['#', 'tx', 'idx', 'value', 'confirmations', 'address']); + foreach ($UTXOs as $i => $UTXO) { + $table->addRow([$i, $UTXO['hash'], $UTXO['idx'], BlocktrailSDK::toBTCString($UTXO['value']), $UTXO['confirmations'], $UTXO['address']]); } $table->render(); if (count($UTXOs) >= $perpage) { - $output->writeln("there are more wallets, use --page and --perpage to see all of them ..."); + $output->writeln("there are more UTXOs, use --page and --perpage to see all of them ..."); } } } From f8d047e5a89874bf74c3fb186c86dc5d8d680a0c Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 5 Mar 2015 12:14:22 +0100 Subject: [PATCH 15/16] add Monolog support --- composer.json | 3 ++- src/BlocktrailSDK.php | 24 ++++++++++++++++++ src/Connection/RestClient.php | 32 ++++++++++++++++++++++++ src/Console/Commands/AbstractCommand.php | 8 ++++++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eb8cd0c..74023f7 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,8 @@ "ramsey/array_column": "~1.1", "dompdf/dompdf" : "0.6.*", "endroid/qrcode": "1.5.*", - "symfony/console": "~2.6" + "symfony/console": "~2.6", + "monolog/monolog": "~1.13" }, "require-dev": { "phpunit/phpunit": "4.3.*", diff --git a/src/BlocktrailSDK.php b/src/BlocktrailSDK.php index 6882da9..859782c 100644 --- a/src/BlocktrailSDK.php +++ b/src/BlocktrailSDK.php @@ -8,6 +8,7 @@ use BitWasp\BitcoinLib\RawTransaction; use Blocktrail\SDK\Connection\RestClient; use Blocktrail\SDK\Exceptions\WalletChecksumException; +use Monolog\Logger; /** * Class BlocktrailSDK @@ -18,6 +19,11 @@ class BlocktrailSDK implements BlocktrailSDKInterface { */ protected $client; + /** + * @var Logger + */ + private $logger = null; + /** * @var string currently only supporting; bitcoin */ @@ -136,6 +142,24 @@ public function getRestClient() { return $this->client; } + /** + * @return Logger + */ + public function getLogger() { + return $this->logger; + } + + /** + * @param Logger $logger + * @param bool $setOnRestClient + */ + public function setLogger(Logger $logger, $setOnRestClient = true) { + $this->logger = $logger; + if ($setOnRestClient) { + $this->client->setLogger($logger); + } + } + /** * get a single address * @param string $address address hash diff --git a/src/Connection/RestClient.php b/src/Connection/RestClient.php index 276c8fc..2ebdcbe 100644 --- a/src/Connection/RestClient.php +++ b/src/Connection/RestClient.php @@ -4,6 +4,8 @@ use GuzzleHttp\Client as Guzzle; use GuzzleHttp\Message\RequestInterface; +use GuzzleHttp\Event\BeforeEvent; +use GuzzleHttp\Event\CompleteEvent; use GuzzleHttp\Message\ResponseInterface; use GuzzleHttp\Post\PostBodyInterface; use GuzzleHttp\Query; @@ -19,6 +21,7 @@ use Blocktrail\SDK\Connection\Exceptions\InvalidCredentials; use Blocktrail\SDK\Connection\Exceptions\MissingEndpoint; use Blocktrail\SDK\Connection\Exceptions\GenericHTTPError; +use Monolog\Logger; use Symfony\Component\HttpFoundation\Request; /** @@ -44,6 +47,13 @@ class RestClient { */ protected $verboseErrors = false; + private $timeIt = null; + + /** + * @var Logger|null + */ + private $logger = null; + public function __construct($apiEndpoint, $apiVersion, $apiKey, $apiSecret) { $this->guzzle = new Guzzle(array( 'base_url' => $apiEndpoint, @@ -64,6 +74,19 @@ public function __construct($apiEndpoint, $apiVersion, $apiKey, $apiSecret) { $this->apiKey = $apiKey; + $this->guzzle->getEmitter()->on('before', function (BeforeEvent $e) { + $this->timeIt = microtime(true); + if ($this->logger) { + $this->logger->debug("BEFORE {$e->getRequest()->getUrl()}"); + } + }); + + $this->guzzle->getEmitter()->on('complete', function (CompleteEvent $e) { + if ($this->logger) { + $this->logger->debug("COMPLETE {$e->getRequest()->getUrl()} " . (microtime(true) - $this->timeIt)); + } + }); + $this->guzzle->getEmitter()->attach(new RequestSubscriber( new Context([ 'keys' => [$apiKey => $apiSecret], @@ -89,6 +112,15 @@ public function setCurlDebugging($debug = true) { $this->guzzle->setDefaultOption('debug', $debug); } + /** + * set a logger to handle debug info + * + * @param Logger $logger + */ + public function setLogger(Logger $logger) { + $this->logger = $logger; + } + /** * enable verbose errors * diff --git a/src/Console/Commands/AbstractCommand.php b/src/Console/Commands/AbstractCommand.php index a7a099f..de27ce1 100644 --- a/src/Console/Commands/AbstractCommand.php +++ b/src/Console/Commands/AbstractCommand.php @@ -6,6 +6,8 @@ use Blocktrail\SDK\BlocktrailSDKInterface; use Blocktrail\SDK\Console\Application; use Blocktrail\SDK\WalletInterface; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputArgument; @@ -54,6 +56,12 @@ protected function execute(InputInterface $input, OutputInterface $output) { if (!$this->apiSecret) { throw new \RuntimeException('API_SECRET is required.'); } + + if ($output->isVeryVerbose()) { + $stderr = new StreamHandler('php://stderr', Logger::DEBUG); + $logger = new Logger("blocktrail-sdk", [$stderr]); + $this->getBlocktrailSDK()->getRestClient()->setLogger($logger); + } } /** From f7d856f368a9d3d67c7defac4964f8075043142c Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Fri, 12 Jun 2015 16:46:31 +0200 Subject: [PATCH 16/16] fixed value not being int --- src/Console/Commands/PayCommand.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Console/Commands/PayCommand.php b/src/Console/Commands/PayCommand.php index 2fab3f9..b10389e 100644 --- a/src/Console/Commands/PayCommand.php +++ b/src/Console/Commands/PayCommand.php @@ -74,6 +74,8 @@ protected function execute(InputInterface $input, OutputInterface $output) { } else { if (!$questionHelper->ask($input, $output, new ConfirmationQuestion("Did you specify this value in satoshi? [y/N] ", false))) { $value = BlocktrailSDK::toSatoshi($value); + } else { + $value = (int)$value; } }