From c66ed054063f1a51eb62049594f7b24310c67f08 Mon Sep 17 00:00:00 2001 From: Ekliptor Date: Sat, 29 Jun 2019 08:50:12 +0700 Subject: [PATCH] full code commit --- .gitignore | 5 + README.md | 120 +- autoload.php | 102 ++ cashp.php | 4 + composer.json | 41 + composer.lock | 1583 +++++++++++++++++ examples/pay-bch.php | 4 +- examples/pay-slp.php | 7 +- map.php | 7 + mapFiles.php | 10 + src/BlockchainApi/AbstractBlockchainApi.php | 129 ++ src/BlockchainApi/BitcoinComRestApi.php | 107 ++ src/BlockchainApi/Http/AbstractHttpAgent.php | 52 + src/BlockchainApi/Http/BasicHttpAgent.php | 25 + src/BlockchainApi/Http/CurlHttpAgent.php | 45 + src/BlockchainApi/Http/WordpressHttpAgent.php | 34 + src/BlockchainApi/Structs/BchAddress.php | 64 + src/BlockchainApi/Structs/SlpToken.php | 38 + src/BlockchainApi/Structs/SlpTokenAddress.php | 47 + src/CashP.php | 112 ++ src/CashpOptions.php | 61 + src/ExchangeRate.php | 56 + src/Utils/macros.php | 19 + tests/RestBackendTest.php | 32 + 24 files changed, 2700 insertions(+), 4 deletions(-) create mode 100644 autoload.php create mode 100644 cashp.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 map.php create mode 100644 mapFiles.php create mode 100644 src/BlockchainApi/AbstractBlockchainApi.php create mode 100644 src/BlockchainApi/BitcoinComRestApi.php create mode 100644 src/BlockchainApi/Http/AbstractHttpAgent.php create mode 100644 src/BlockchainApi/Http/BasicHttpAgent.php create mode 100644 src/BlockchainApi/Http/CurlHttpAgent.php create mode 100644 src/BlockchainApi/Http/WordpressHttpAgent.php create mode 100644 src/BlockchainApi/Structs/BchAddress.php create mode 100644 src/BlockchainApi/Structs/SlpToken.php create mode 100644 src/BlockchainApi/Structs/SlpTokenAddress.php create mode 100644 src/CashP.php create mode 100644 src/CashpOptions.php create mode 100644 src/ExchangeRate.php create mode 100644 src/Utils/macros.php create mode 100644 tests/RestBackendTest.php diff --git a/.gitignore b/.gitignore index 5cba820..a992255 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ Homestead.json .svn .project .buildpath +.settings # Logs logs @@ -52,4 +53,8 @@ pids .yarn-integrity +# PHP packages +phpqrcode + # private files +examples/example-qr.png \ No newline at end of file diff --git a/README.md b/README.md index ab8c215..823106c 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,16 @@ This is a PHP library to enable [BitCoin Cash (BCH)](https://www.bitcoincash.org You can easily use this with WordPress, Laravel and other PHP frameworks. #### Installation +With composer (recommended): ``` composer require "ekliptor/cashp" ``` +Manual installation: + +1. Download the source code and include `cashp.php` from the root directory of this library. +2. Download the source of this [PHP QR-code package](https://github.com/Ekliptor/qr-code) and extract it to `src/phpqrcode` + #### Requirements ``` PHP >= 7.1 @@ -19,11 +25,123 @@ PHP >= 7.1 * generate QR codes with BCH and SLP payment URIs ## Docs -* ... +Take a look at [code examples](https://github.com/Ekliptor/cashp/tree/master/examples). + +#### CashP class +##### __construct(CashpOptions $options = null) +Create the main API class. +* `CashpOptions $options` - (optional) API options (see below) + +##### getRate(): ExchangeRate +Return the exchange rate API. + +##### getBlockchain(): AbstractBlockchainApi +Return the Blockchain API to generate addresses, check balances, transactions,... + +##### generateQrCodeForAddress(string $fileLocal, string $address, float $amountBCH, float $amountToken = 0.0, string $tokenID = ""): bool +Generate a QR code for a payment. +* `string $fileLocal` - A path on your local filesystem to store the QR code file. This should be accessible from the web if you want to display the QR code to users. +If the given file already exists it will NOT be overwritten (QR codes are meant to be generated & cached in your web temp directory). +* `string $address` - The (1-time) BCH (or SLP) address created for this payment. +* `float $amountBCH` - The amount in BCH. Can be 0 if the user pays the full amount in SLP tokens. +* `float $amountToken` - (optional) The amount of the optional token to be received. +* `string $tokenID` - (optional) The hex ID of the SLP token. Required if $amountToken > 0. +--- +returns `bool` - true on success, false otherwise + +##### createPaymentURI(string $address, float $amountBCH, float $amountToken = 0.0, string $tokenID = ""): string +Return a payment URI (starting with "bitcoincash:" or "simpleledger:" if $amountToken > 0) for the given $address. +* `string $address` - The receiving BCH (or SLP) address. +* `float $amountBCH` - The amount in BCH to receive. +* `float $amountToken` - (optional) The amount of SLP tokens to receive. +* `string $tokenID` - (optional) The hex ID of the SLP token. Required if $amountToken > 0. +--- +returns `string` + + +#### CashpOptions class +A set of advanced config properties. +* `$httpAgent = null` - The HTTP implementation used to make HTTP requests. +Values: BasicHttpAgent|CurlHttpAgent|WordpressHttpAgent +Defaults to BasicHttpAgent, but you should use a better one according to your PHP setup. +* `$exchangeRateExpirationMin = 60` - How long the crawled exchange rate shall stay in cache. Currently rates are not saved beyond script execution. +* `$httpTimeoutSec = 10` - The timeout for HTTP requests to the REST API backend. +* `$blockchainApiImplementation = "BitcoinComRestApi"` - The REST API backend implementation to use. Allowed values: BitcoinComRestApi + + +#### ExchangeRate class +An API to get BCH exchanges rates to fiat currencies. + +##### getRate(string $currency = "USD"): float +Get the current exchange rate for BCH. +* `string $currency` - A fiat currency such as USD|EUR|JPY +--- +returns `float` + + +#### BlockchainApi class +The Blockchain API to generate addresses, check balances, transactions,... + +##### static setLogger(callable $loggerFn): void +Set a logger function for errors and debug output. Use this to write to a logfile or database. +If no function is provided everything will be printed using 'echo'. +* `callable $loggerFn(string $subject, mixed $error, mixed $data = null)` - parameters of the PHP callable + +##### setHttpAgent(AbstractHttpAgent $agent): void +Set a a HTTP implementation for requests (cURL, Wordpress HTTP API,...) +* `AbstractHttpAgent $agent` - + +##### getConfirmationCount(string $transactionID): int +Return the number of confirmation for the given blockchain transaction ID. +* `string $transactionID` +--- +returns `int` - The number of confirmations or -1 if the $transactionID doesn't exist. + +##### createNewAddress(string $xPub, int $addressCount, string $hdPathFormat = '0/%d'): ?BchAddress +Creates a new address from the xPub. It will automatically increment the counter in the hdPath parameter to derive the next deterministic address. +* `string $xPub` - The extended public key. Called 'Master Public Key' in Electron Cash. +* `int $addressCount` - The number of the next address to generate a unique address. Usually this should be an incrementing integer. +* `string $hdPathFormat` - (optional) The HD path to be used for creating address children. +--- +returns `BchAddress` - the address or `null` on failure + +##### getTokenInfo(string $tokenID): ?SlpToken +Get general (network-wide) info about a SLP token. +* `string $tokenID` - +--- +returns `SlpToken` - The token or `null` on failure + +##### getAddressBalance(string $address): float +Return the BCH balance of the given address (including unconfirmed transactions). +* `string $address` - The BCH address in CashAddress format. +--- +returns `float` - The balance or -1 if the address doesn't exist. + +##### getAddressTokenBalance(string $address, string $tokenID): float +Return the token balance of the given SLP address (including unconfirmed transactions). +* `string $address` - +* `string $tokenID` - +--- +returns `float` - The balance or -1 if the address doesn't exist. + +##### getAddressDetails(string $address): ?BchAddress +Return the BCH Address with all its properties such as balance, TXIDs,... +* `string $address` - The BCH address in CashAddress format. +--- +returns `BchAddress` - the address or `null` on failure + +##### getSlpAddressDetails(string $address, string $tokenID): ?SlpTokenAddress +Return the SLP token details of a given address include balance, TXIDs,... +* `string $address` - +* `string $tokenID` - +--- +returns `SlpTokenAddress` - The token or `null` on failure + ## ToDo * add SLP address verification * add BadgerWallet HTML & JavaScript output +* implement more functions of the REST API ## Contact diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..761bd68 --- /dev/null +++ b/autoload.php @@ -0,0 +1,102 @@ +getNamespaceMap(); + foreach ($namespaceMap as $namespace => $paths) + { + $namespacePath = str_replace('\\', '/', $namespace); + $length = strlen($namespacePath); + if (substr($classPath, 0, $length) !== $namespacePath) + continue; + foreach ($paths as $path) + { + $file = $path . substr($classPath, $length) . '.php'; + if ($includeFile($file)) + return; + } + } + + // try the root src dir as fallback (for 'use' keyword or not registered/global namespace) + $fallbackDirs = $appAutoload->getFallbackDirs(); + foreach ($fallbackDirs as $dir) + { + $file = $dir . $classPath . '.php'; + if ($includeFile($file)) + return; + } + if (static::DEBUG) // happens on production when we upload a new file with OPCache enabled + notifyError("Class Autoload failed", "File does not exist: $file"); + }); + + // files to always include (config, global functions,...) + $includeFiles = require __DIR__ . '/mapFiles.php'; + foreach ($includeFiles as $fileIdentifier => $file) + $appAutoload->requireFile($fileIdentifier, $file); + + static::$loader = $appAutoload; + return static::$loader; + } + + public function __construct() { + $this->namespaceMap = require __DIR__ . '/map.php'; + // last resort for loading classes are the paths specified here (the root /src/ dir) + // we have to merge all namespace paths into this, in case $class in the callback does not hold the namespace + $this->fallbackDirs = array(__DIR__ . '/src/'); + foreach ($this->namespaceMap as $namespace => $paths) + $this->fallbackDirs = array_merge($paths, $this->fallbackDirs); // try namespace paths first + } + + public function getNamespaceMap() { + return $this->namespaceMap; + } + + public function getFallbackDirs() { + return $this->fallbackDirs; + } + + public function requireFile($fileIdentifier, $file) { + // use check via globals instead of require_once because it's faster, no context change (see Laravel autoloader) + if (empty($GLOBALS['__ekliptor_autoload_files'][$fileIdentifier])) { + require $file; + $GLOBALS['__ekliptor_autoload_files'][$fileIdentifier] = true; + } + } +} +AppAutoload::getAppAutoload(); +?> \ No newline at end of file diff --git a/cashp.php b/cashp.php new file mode 100644 index 0000000..fc3ec8c --- /dev/null +++ b/cashp.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9ffa7b7 --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "name" : "ekliptor/cashp", + "description" : "PHP library for Bitcoin Cash (BCH) and SLP token payments", + "keywords" : [ + "bitcoin", + "cryptocurrency", + "billing", + "ecommerce", + "payment" + ], + "type" : "library", + "license" : "MIT", + "support" : { + "issues" : "https://github.com/Ekliptor/cashp/issues", + "source" : "https://github.com/Ekliptor/cashp" + }, + "authors" : [{ + "name" : "Ekliptor", + "email" : "Ekliptor1@yandex.com" + } + ], + "require": { + "ekliptor/qr-code": "^1" + }, + "require-dev" : { + "phpunit/phpunit" : "^8" + }, + "autoload" : { + "psr-4" : { + "Ekliptor\\CashP\\" : "src/" + }, + "files" : [] + }, + "autoload-dev" : { + "psr-4" : { + "Ekliptor\\CashP\\Tests\\" : "tests/" + } + }, + "minimum-stability" : "stable", + "prefer-stable" : true +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f90b800 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1583 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "03c1afad50f02f8f424d1a2abbcc59e3", + "packages": [ + { + "name": "ekliptor/qr-code", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Ekliptor/qr-code.git", + "reference": "dcad60721bcccab89fda989fa499e0edc5169b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ekliptor/qr-code/zipball/dcad60721bcccab89fda989fa499e0edc5169b5e", + "reference": "dcad60721bcccab89fda989fa499e0edc5169b5e", + "shasum": "" + }, + "require-dev": { + "khanamiryan/qrcode-detector-decoder": "^1.0", + "phpunit/phpunit": "^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers/constants.php", + "src/helpers/functions.php" + ], + "psr-4": { + "QR_Code\\": "src/QR_Code/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ekliptor", + "email": "Ekliptor1@yandex.com" + } + ], + "description": "QR Code Generator for PHP", + "keywords": [ + "qrcode" + ], + "time": "2019-06-29T00:07:35+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-04-07T13:18:21+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-04-30T17:48:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-06-13T12:50:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "aed67b57d459dcab93e84a5c9703d3deb5025dff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aed67b57d459dcab93e84a5c9703d3deb5025dff", + "reference": "aed67b57d459dcab93e84a5c9703d3deb5025dff", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.1", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-xdebug": "^2.6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2019-06-06T12:28:18+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-06-07T04:22:29+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2018-10-30T05:52:18+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "f67ca36860ebca7224d4573f107f79bd8ed0ba03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f67ca36860ebca7224d4573f107f79bd8ed0ba03", + "reference": "f67ca36860ebca7224d4573f107f79bd8ed0ba03", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.2", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.5", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.0", + "sebastian/global-state": "^3.0.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.0", + "sebastian/version": "^2.0.1" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-06-19T12:03:56+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2019-05-05T09:05:15+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "shasum": "" + }, + "require": { + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2019-02-01T05:30:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "251ca774d58181fe1d3eda68843264eaae7e07ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/251ca774d58181fe1d3eda68843264eaae7e07ef", + "reference": "251ca774d58181fe1d3eda68843264eaae7e07ef", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-06-19T06:39:12+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-12-25T11:19:39+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/examples/pay-bch.php b/examples/pay-bch.php index 265bab8..b52f05b 100644 --- a/examples/pay-bch.php +++ b/examples/pay-bch.php @@ -14,6 +14,7 @@ $xPub = "xpub6CphSGwqZvKFU9zMfC3qLxxhskBFjNAC9imbSMGXCNVD4DRynJGJCYR63DZe5T4bePEkyRoi9wtZQkmxsNiZfR9D6X3jBxyacHdtRpETDvV"; $requestAmountBCH = 0.002; $addressCounter = 1; // increment this and store it (in database) to generate unique addresses +$qrCodeFile = "./example-qr.png"; // setup library @@ -31,7 +32,8 @@ $address = $cashp->getBlockchain()->createNewAddress($xPub, $addressCounter); print_r($address); -print_r($cashp->generateQrCodeForAddress("./example-qr.png", $address->cashAddress, $requestAmountBCH)); +@unlink($qrCodeFile); // ensure it doesn't exist +print_r($cashp->generateQrCodeForAddress($qrCodeFile, $address->cashAddress, $requestAmountBCH)); echo 'qr-code' . "\n"; // check the address balance (inlcuding TX) diff --git a/examples/pay-slp.php b/examples/pay-slp.php index f054641..8908e42 100644 --- a/examples/pay-slp.php +++ b/examples/pay-slp.php @@ -1,5 +1,6 @@ getBlockchain()->createNewAddress($xPub, $addressCounter); print_r($address); -print_r($cashp->generateQrCodeForAddress("./example-qr.png", $address->slpAddress, $requestAmountBCH, $requestAmountSLP)); +@unlink($qrCodeFile); // ensure it doesn't exist +print_r($cashp->generateQrCodeForAddress($qrCodeFile, $address->slpAddress, $requestAmountBCH, $requestAmountSLP, $tokenID)); echo 'qr-code' . "\n"; // check the BCH address balance (inlcuding TX) diff --git a/map.php b/map.php new file mode 100644 index 0000000..5d372f9 --- /dev/null +++ b/map.php @@ -0,0 +1,7 @@ + array($srcDir), + 'QR_Code\\' => array($srcDir . 'phpqrcode/src/QR_Code/') +); diff --git a/mapFiles.php b/mapFiles.php new file mode 100644 index 0000000..3c55022 --- /dev/null +++ b/mapFiles.php @@ -0,0 +1,10 @@ + $srcDir . 'phpqrcode/src/helpers/constants.php', + 'phpqrcode/src/helpers/functions.php' => $srcDir . 'phpqrcode/src/helpers/functions.php' +); diff --git a/src/BlockchainApi/AbstractBlockchainApi.php b/src/BlockchainApi/AbstractBlockchainApi.php new file mode 100644 index 0000000..7572664 --- /dev/null +++ b/src/BlockchainApi/AbstractBlockchainApi.php @@ -0,0 +1,129 @@ +blockchainApiUrl = $blockchainApiUrl; + } + + /** + * Create an API class and cache it for further usage. + * (You can only create 1 instance per script - even with multiple implementations.) + * @param string $className The BCH chain API implementation to use. Valid options: BitcoinComRestApi + * @param string $blockchainApiUrl The API URL to use. Defaults to rest.bitcoin.com + * @throws \Error + * @return \Ekliptor\CashP\BlockchainApi\AbstractBlockchainApi + */ + public static function getInstance(string $className, string $blockchainApiUrl = '') { + if (self::$instance !== null) + return self::$instance; + switch ($className) { + case 'BitcoinComRestApi': + self::$instance = new BitcoinComRestApi($blockchainApiUrl); + return self::$instance; + } + throw new \Error("Unable to load bloackchain API class (not existing?): " . $className); + } + + /** + * Set a logger function for errors and debug output. Use this to write to a logfile or database. + * If no function is provided everything will be printed using 'echo'. + * @param callable $loggerFn(string $subject, mixed $error, mixed $data = null) + */ + public static function setLogger(callable $loggerFn): void { + static::$loggerFn = $loggerFn; + } + + /** + * Set a a HTTP implementation for requests (cURL, Wordpress HTTP API,...) + * @param AbstractHttpAgent $agent + */ + public function setHttpAgent(AbstractHttpAgent $agent): void { + $this->httpAgent = $agent; + } + + /** + * Return the number of confirmation for the given blockchain transaction ID. + * @param string $transactionID + * @return int The number of confirmations or -1 if the $transactionID doesn't exist. + */ + public abstract function getConfirmationCount(string $transactionID): int; + + /** + * Creates a new address from the xPub. It will automatically increment the counter in the hdPath parameter to + * derive the next deterministic address. + * @param string $xPub The extended public key. Called 'Master Public Key' in Electron Cash. + * @param int $addressCount The number of the next address to generate a unique address. Usually this should be an incrementing integer. + * @param string $hdPathFormat (optional) The HD path to be used for creating address children. + * @return BchAddress the address or null on failure + */ + public abstract function createNewAddress(string $xPub, int $addressCount, string $hdPathFormat = '0/%d'): ?BchAddress; + + /** + * Get general (network-wide) info about a SLP token. + * @param string $tokenID + * @return SlpToken or null on failure + */ + public abstract function getTokenInfo(string $tokenID): ?SlpToken; + + /** + * Return the BCH balance of the given address (including unconfirmed transactions). + * @param string $address The BCH address in CashAddress format. + * @return float The balance or -1 if the address doesn't exist. + */ + public abstract function getAddressBalance(string $address): float; + + /** + * Return the token balance of the given SLP address (including unconfirmed transactions). + * @param string $address + * @param string $tokenID + * @return float The balance or -1 if the address doesn't exist. + */ + public abstract function getAddressTokenBalance(string $address, string $tokenID): float; + + /** + * Return the BCH Address with all its properties such as balance, TXIDs,... + * @param string $address The BCH address in CashAddress format. + * @return BchAddress|NULL + */ + public abstract function getAddressDetails(string $address): ?BchAddress; + + /** + * Return the SLP token details of a given address include balance, TXIDs,... + * @param string $address The SLP address + * @param string $tokenID + * @return SlpTokenAddress|NULL + */ + public abstract function getSlpAddressDetails(string $address, string $tokenID): ?SlpTokenAddress; + + protected function logError(string $subject, $error, $data = null): void { + if (static::$loggerFn !== null) + call_user_func(static::$loggerFn, $subject, $error, $data); + else { + $output = "$subject
\n
" . print_r($error, true) . "

\n"; + if ($data !== null) + $output .= "
" . print_r($data, true) . "

\n"; + echo $output; + } + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/BitcoinComRestApi.php b/src/BlockchainApi/BitcoinComRestApi.php new file mode 100644 index 0000000..af8e36b --- /dev/null +++ b/src/BlockchainApi/BitcoinComRestApi.php @@ -0,0 +1,107 @@ +getTransactionDetails($transactionID); + if (!$txDetails || !isset($txDetails->confirmations)) + return -1; // not found + return (int)$txDetails->confirmations; + } + + public function createNewAddress(string $xPub, int $addressCount, string $hdPathFormat = '0/%d'): ?BchAddress { + $nextHdPath = sprintf($hdPathFormat, $addressCount); // since update on 2019-06-05 only works with plain numbers + $url = sprintf($this->blockchainApiUrl . 'address/fromXPub/%s?hdPath=%s', $xPub, $nextHdPath); + $response = $this->httpAgent->get($url); + if ($response === false) + return null; + $jsonRes = json_decode($response); + if (!$jsonRes) + return null; + return new BchAddress($jsonRes->cashAddress, $jsonRes->legacyAddress, $jsonRes->slpAddress); + } + + public function getTokenInfo(string $tokenID): ?SlpToken { + $url = sprintf($this->blockchainApiUrl . 'slp/list/%s', $tokenID); + $response = $this->httpAgent->get($url); + if ($response === false) + return null; + $jsonRes = json_decode($response); + if (!$jsonRes) + return null; + return SlpToken::fromJson($jsonRes); + } + + public function getAddressBalance(string $address): float { + $bchAddress = $this->getAddressDetails($address); + if ($bchAddress === null) + return -1.0; + return $bchAddress->balance; + } + + public function getAddressTokenBalance(string $address, string $tokenID): float { + //$url = sprintf($this->blockchainApiUrl . 'slp/balancesForAddress/%s', $address); // address can have multiple tokens + $url = sprintf($this->blockchainApiUrl . 'slp/balance/%s/%s', $address, $tokenID); + $response = $this->httpAgent->get($url); + if ($response === false) + return -1.0; + $jsonRes = json_decode($response); + if (!$jsonRes) + return -1.0; + return $jsonRes->balance; + } + + public function getAddressDetails(string $address): ?BchAddress { + $url = sprintf($this->blockchainApiUrl . 'address/details/%s', $address); + $response = $this->httpAgent->get($url); + if ($response === false) + return null; + $jsonRes = json_decode($response); + if (!$jsonRes) + return null; + else if (isset($jsonRes->error) && $jsonRes->error) { + $this->logError("Error on receiving BCH address details", $jsonRes->error); + return null; + } + $bchAddress = new BchAddress($jsonRes->cashAddress, $jsonRes->legacyAddress, $jsonRes->slpAddress); + $bchAddress->addProperties($jsonRes); + return $bchAddress; + } + + public function getSlpAddressDetails(string $address, string $tokenID): ?SlpTokenAddress { + $url = sprintf($this->blockchainApiUrl . 'slp/transactions/%s/%s', $tokenID, $address); + $response = $this->httpAgent->get($url); + if ($response === false) + return null; + $jsonRes = json_decode($response); + //if (!$jsonRes) // can be empty array + if ($jsonRes === null) + return null; + $slpToken = SlpTokenAddress::fromAddressJson($jsonRes, new SlpTokenAddress($address), $tokenID); + return $slpToken; + } + + protected function getTransactionDetails(string $transactionID): ?\stdClass { + if (isset($this->transactionCache[$transactionID])) + return $this->transactionCache[$transactionID]; + + $url = sprintf($this->blockchainApiUrl . 'transaction/details/%s', $transactionID); // we could also use /slp/txDetails/{txid} + $response = $this->httpAgent->get($url); + if ($response === false) + return null; + $jsonRes = json_decode($response); + if ($jsonRes) + $this->transactionCache[$transactionID] = $jsonRes; + return $jsonRes; + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/Http/AbstractHttpAgent.php b/src/BlockchainApi/Http/AbstractHttpAgent.php new file mode 100644 index 0000000..64cf8f5 --- /dev/null +++ b/src/BlockchainApi/Http/AbstractHttpAgent.php @@ -0,0 +1,52 @@ +timeoutSec = $options['timeout']; + if (isset($options['maxRedirects'])) + $this->maxRedirects = $options['maxRedirects']; + if (isset($options['userAgent'])) + $this->userAgent = $options['userAgent']; + } + + /** + * Perform a HTTP GET request. + * @param string $url The full URL string (including possible query params). + * @param array $options additional options (depending on the specific HTTP implementation) valid: timeout|userAgent|maxRedirects + * @return string|bool The response body or false on failure. + */ + public abstract function get(string $url, array $options = array()); + + protected function logError(string $subject, $error, $data = null): void { + if (static::$loggerFn !== null) + call_user_func(static::$loggerFn, $subject, $error, $data); + else { + $output = "$subject
\n
" . print_r($error, true) . "

\n"; + if ($data !== null) + $output .= "
" . print_r($data, true) . "

\n"; + echo $output; + } + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/Http/BasicHttpAgent.php b/src/BlockchainApi/Http/BasicHttpAgent.php new file mode 100644 index 0000000..3f7c721 --- /dev/null +++ b/src/BlockchainApi/Http/BasicHttpAgent.php @@ -0,0 +1,25 @@ + + array('timeout' => isset($options['timeout']) ? $options['timeout'] : $this->timeoutSec, + 'user_agent' => isset($options['userAgent']) ? $options['userAgent'] : $this->userAgent, + 'max_redirects' => isset($options['maxRedirects']) ? $options['maxRedirects'] : $this->maxRedirects + )) + ); + $contents = file_get_contents($url, 0, $ctx); + return $contents; + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/Http/CurlHttpAgent.php b/src/BlockchainApi/Http/CurlHttpAgent.php new file mode 100644 index 0000000..337ef06 --- /dev/null +++ b/src/BlockchainApi/Http/CurlHttpAgent.php @@ -0,0 +1,45 @@ +timeoutSec); + curl_setopt($ch, CURLOPT_USERAGENT, isset($options['userAgent']) ? $options['userAgent'] : $this->userAgent); + $maxRedirects = isset($options['maxRedirects']) ? $options['maxRedirects'] : $this->maxRedirects; + if ($maxRedirects > 0) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirects); + } + /* + if ($skip_certificate_check) { + curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, false); + } + */ + $output = curl_exec($ch); + if (curl_errno($ch)) { + $output = false; + $error = "URL: $url Curl error: " . curl_error($ch); + $this->logError("cURL error getting page", $error); + curl_close($ch); + return false; + } + curl_close($ch); + return $output; + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/Http/WordpressHttpAgent.php b/src/BlockchainApi/Http/WordpressHttpAgent.php new file mode 100644 index 0000000..228067e --- /dev/null +++ b/src/BlockchainApi/Http/WordpressHttpAgent.php @@ -0,0 +1,34 @@ +getHttpOptions($options)); + if ($response instanceof \WP_Error) { + $this->logError("Error on HTTP GET", $response->get_error_messages()); + return false; + } + $body = wp_remote_retrieve_body($response); + return $body; + } + + protected function getHttpOptions(array $options = array()) { + return array( + 'timeout' => isset($options['timeout']) ? $options['timeout'] : $this->timeoutSec, //seconds + 'user-agent' => isset($options['userAgent']) ? $options['userAgent'] : $this->userAgent, + 'redirection' => isset($options['maxRedirects']) ? $options['maxRedirects'] : $this->maxRedirects, + 'headers' => array( + 'Accept' => 'application/json', + ), + ); + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/Structs/BchAddress.php b/src/BlockchainApi/Structs/BchAddress.php new file mode 100644 index 0000000..4f37e2e --- /dev/null +++ b/src/BlockchainApi/Structs/BchAddress.php @@ -0,0 +1,64 @@ +cashAddress = $cashAddress; + $this->legacyAddress = $legacyAddress; + $this->slpAddress = $slpAddress; + } + + /** + * Adds known properties of a BchAddress from the supplied JSON. + * If a property already exists, it will be overwritten. + * @param \stdClass $json + */ + public function addProperties(\stdClass $json): void { + if (isset($json->cashAddress) && $json->cashAddress !== $this->cashAddress) + throw new \Exception("Can not change the BCH CashAddr supplied in addProperties() - supplied value: " . $json->cashAddress); + + $keys = array("balance", "balanceSat", "totalReceived", "totalReceivedSat", "totalSent", "totalSentSat", "unconfirmedBalance", + "unconfirmedBalanceSat", "unconfirmedTxApperances", "txApperances", "transactions", + //"legacyAddress", "cashAddress", "slpAddress" // values from consteuctor + ); + foreach ($keys as $key) { + if (isset($json->$key)) + $this->$key = $json->$key; + } + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/Structs/SlpToken.php b/src/BlockchainApi/Structs/SlpToken.php new file mode 100644 index 0000000..e8525b7 --- /dev/null +++ b/src/BlockchainApi/Structs/SlpToken.php @@ -0,0 +1,38 @@ + $value) { + if (isset($json->$key)) + $instance->$key = $json->$key; + } + return $instance; + } +} +?> \ No newline at end of file diff --git a/src/BlockchainApi/Structs/SlpTokenAddress.php b/src/BlockchainApi/Structs/SlpTokenAddress.php new file mode 100644 index 0000000..ce5987a --- /dev/null +++ b/src/BlockchainApi/Structs/SlpTokenAddress.php @@ -0,0 +1,47 @@ +slpAddress = $slpAddress; + } + + public static function fromAddressJson(array $jsonArr, SlpTokenAddress $instance = null, string $tokenID = ""): SlpTokenAddress { + if ($instance === null) + $instance = new SlpTokenAddress(); + if (!empty($tokenID)) + $instance->id = $tokenID; + //$instance = SlpToken::fromJson($json, $instance); // on the REST API response for TX most properties exist on each TX + foreach ($jsonArr as $tx) { + if (!isset($tx->tokenDetails) || $tx->tokenDetails->valid !== true) + continue; + $txDetail = $tx->tokenDetails->detail; + if (empty ($instance->name)) { // get token info from the first TX + $instance->id = $txDetail->tokenIdHex; + $instance->name = $txDetail->name; + $instance->symbol = $txDetail->symbol; + $instance->documentHash = $txDetail->documentSha256Hex; + $instance->documentUri = $txDetail->documentUri; + $instance->decimals = $txDetail->decimals; + //$instance->timestamp_unix = ; // the time when the token has been created is not present here + } + $instance->transactions[] = $tx->txid; + } + return $instance; + } +} +?> \ No newline at end of file diff --git a/src/CashP.php b/src/CashP.php new file mode 100644 index 0000000..c3ba4ed --- /dev/null +++ b/src/CashP.php @@ -0,0 +1,112 @@ +options = $options; + if ($this->options->httpAgent === null) + $this->options->httpAgent = new BasicHttpAgent(null, array('timeout' => $this->options->httpTimeoutSec)); + $this->rate = new ExchangeRate($this->options->httpAgent, $options->exchangeRateExpirationMin); + $this->blockchainApi = AbstractBlockchainApi::getInstance($options->blockchainApiImplementation); + $this->blockchainApi->setHttpAgent($this->options->httpAgent); + } + + /** + * Return the exchange rate API. + * @return ExchangeRate + */ + public function getRate(): ExchangeRate { + return $this->rate; + } + + /** + * Return the Blockchain API to generate addresses, check balances, transactions,... + * @return AbstractBlockchainApi + */ + public function getBlockchain(): AbstractBlockchainApi { + return $this->blockchainApi; + } + + /** + * Generate a QR code for a payment. + * @param string $fileLocal A path on your local filesystem to store the QR code file. This should be accessible from the web if you want + * to display the QR code to users. + * If the given file already exists it will NOT be overwritten (QR codes are meant to be generated & cached in your web temp directory). + * @param string $address The (1-time) BCH (or SLP) address created for this payment. + * @param float The amount in BCH. Can be 0 if the user pays the full amount in SLP tokens. + * @param float $amountToken (optional) The amount of the optional token to be received. + * @param string $tokenID (optional) The hex ID of the SLP token. Required if $amountToken > 0. + * @return bool true on success, false otherwise + */ + public function generateQrCodeForAddress(string $fileLocal, string $address, float $amountBCH, float $amountToken = 0.0, string $tokenID = ""): bool { + if (substr($fileLocal, -4) !== '.png') + $fileLocal .= '.png'; + if (file_exists($fileLocal) === true) + return true; // use it from cache + $codeContents = $this->createPaymentURI($address, $amountBCH, $amountToken, $tokenID); + \QR_Code\QR_Code::png($codeContents, $fileLocal); + return true; + } + + /** + * Return a payment URI (starting with "bitcoincash:" or "simpleledger:" if $amountToken > 0) for the given $address. + * @param string $address The receiving BCH (or SLP) address. + * @param float $amountBCH The amount in BCH to receive. + * @param float $amountToken (optional) The amount of SLP tokens to receive. + * @param string $tokenID (optional) The hex ID of the SLP token. Required if $amountToken > 0. + * @return string + */ + public function createPaymentURI(string $address, float $amountBCH, float $amountToken = 0.0, string $tokenID = ""): string { + $tokenDigits = 8; // TODO add parameter for this? + $address = preg_replace("/.+:/i", "", $address); + // we use the bitcoincash URI if tokens are disabled because bitcoin.com and other wallets only support this as of June 2019 + //$uri = "simpleledger:$address?amount=$amountBCH"; + $uri = sprintf("bitcoincash:%s?amount=%s", $address, number_format($amountBCH, $tokenDigits)); + if ($amountToken > 0.0) { + if (empty($tokenID)) + throw new \Error("A payment URI with SLP tokens must use the $tokenID parameter."); + $uri = sprintf("simpleledger:%s?amount=%s", $address, number_format($amountBCH, $tokenDigits)); + $uri .= sprintf("&amount1=%s-%s", number_format($amountToken, $tokenDigits), $tokenID); + } + return $uri; + } +} +?> \ No newline at end of file diff --git a/src/CashpOptions.php b/src/CashpOptions.php new file mode 100644 index 0000000..6218ba9 --- /dev/null +++ b/src/CashpOptions.php @@ -0,0 +1,61 @@ + Information -> Master Public Key + * @var string + */ + //public $xPub = ""; + + /** + * The HTTP implementation used to make HTTP requests. + * Values: BasicHttpAgent|CurlHttpAgent|WordpressHttpAgent + * Defaults to BasicHttpAgent, but you should use a better one according to your PHP setup. + * @var AbstractHttpAgent + */ + public $httpAgent = null; + + + // ===== ADVANCED OPTIONS ===== + /** + * How long the crawled exchange rate shall stay in cache. + * Currently rates are not saved beyond script execution. // TODO add support for backend caching using redis, memcached, mysql (Laravel cache abstraction),... + * @var int + */ + public $exchangeRateExpirationMin = 60; + + /** + * The timeout for HTTP requests to the REST API backend. + * @var int + */ + public $httpTimeoutSec = 10; + + /** The format to create new wallet addreses using xPub. This must contain %d for the incrementing address + * counter starting with $addressStart. + * @var string + */ + //public $hdPathFormat = "0/%d"; + + /** + * The REST API backend implementation to use. Allowed values: BitcoinComRestApi + * @var string + */ + public $blockchainApiImplementation = "BitcoinComRestApi"; + + /** + * The first value to pass into $hdPathFormat when creating a new address. + * @var integer + */ + //public $addressStart = 0; +} +?> \ No newline at end of file diff --git a/src/ExchangeRate.php b/src/ExchangeRate.php new file mode 100644 index 0000000..dca4735 --- /dev/null +++ b/src/ExchangeRate.php @@ -0,0 +1,56 @@ +rate = $rate; + $this->timestamp = time(); + } +} + +/** + * This class crawls and caches currency exchange rates. + * + */ +class ExchangeRate { + /** @var int */ + protected $exchangeRateExpirationMin; + /** @var AbstractHttpAgent */ + protected $httpAgent; + /** @var array */ + protected $cache = array(); // (currency string, CurrencyRate) + + public function __construct(AbstractHttpAgent $httpAgent, int $exchangeRateExpirationMin) { + $this->httpAgent = $httpAgent; + $this->exchangeRateExpirationMin = $exchangeRateExpirationMin; + } + + /** + * Get the current exchange rate for BCH. + * @param string $currency A fiat currency such as USD|EUR|JPY + * @return float + */ + public function getRate(string $currency = "USD"): float { + if (isset($this->cache[$currency]) && $this->cache[$currency]->timestamp + $this->exchangeRateExpirationMin*60 > time()) + return $this->cache[$currency]->rate; + + $res = $this->httpAgent->get('https://index-api.bitcoin.com/api/v0/cash/price/usd'); + if ($res === false) { + return 0.0; + } + $json = json_decode($res); + if ($json === null || empty($json->price) || $json->price <= 0.0) { + return 0.0; + } + $this->cache[$currency] = new CurrencyRate($json->price); + return $this->cache[$currency]->rate; + } +} +?> \ No newline at end of file diff --git a/src/Utils/macros.php b/src/Utils/macros.php new file mode 100644 index 0000000..a1e987d --- /dev/null +++ b/src/Utils/macros.php @@ -0,0 +1,19 @@ +" . PHP_EOL); + +define( 'MINUTE_IN_SECONDS', 60 ); +define( 'HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS ); +define( 'DAY_IN_SECONDS', 24 * HOUR_IN_SECONDS ); +define( 'WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS ); +define( 'MONTH_IN_SECONDS', 30 * DAY_IN_SECONDS ); +define( 'YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS ); + +define( 'KB_IN_BYTES', 1024 ); +define( 'MB_IN_BYTES', 1024 * KB_IN_BYTES ); +define( 'GB_IN_BYTES', 1024 * MB_IN_BYTES ); +define( 'TB_IN_BYTES', 1024 * GB_IN_BYTES ); +*/ +?> \ No newline at end of file diff --git a/tests/RestBackendTest.php b/tests/RestBackendTest.php new file mode 100644 index 0000000..eac0945 --- /dev/null +++ b/tests/RestBackendTest.php @@ -0,0 +1,32 @@ +getRate()->getRate("USD"); + $this->assertIsFloat($usdRate, "Returned currency rate is not of type float."); + $this->assertGreaterThan(0.0, $usdRate, "Currency rate can not be negative"); + } + + public function testTokenBalance(): void { + $cashp = new CashP(); + $tokenID = "7278363093d3b899e0e1286ff681bf50d7ddc3c2a68565df743d0efc54c0e7fd"; + $address = "simpleledger:qrg3pzge6lhy90p4semx2a60w6624nudagqzycecg4"; + $tokenBalance = $cashp->getBlockchain()->getAddressTokenBalance($address, $tokenID); + $this->assertGreaterThan(0.0, $tokenBalance, "Test token balance is empty"); + } + + public function testAddressCreation(): void { + $cashp = new CashP(); + $xPub = "xpub6CphSGwqZvKFU9zMfC3qLxxhskBFjNAC9imbSMGXCNVD4DRynJGJCYR63DZe5T4bePEkyRoi9wtZQkmxsNiZfR9D6X3jBxyacHdtRpETDvV"; + $address = $cashp->getBlockchain()->createNewAddress($xPub, 3); + $this->assertInstanceOf(BchAddress::class, $address, "BCH address creation failed"); + } +} +?> \ No newline at end of file