Skip to content

Commit

Permalink
added BCHD backend support with SLP tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
Ekliptor authored and Ekliptor committed Aug 27, 2020
1 parent 0fd1133 commit 7caf245
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/BlockchainApi/AbstractBlockchainApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public static function getInstance(string $className, string $blockchainApiUrl =
case 'BitcoinComRestApi':
self::$instance = new BitcoinComRestApi($blockchainApiUrl);
return self::$instance;
case 'BchdProtoGatewayApi':
self::$instance = new BchdProtoGatewayApi($blockchainApiUrl);
return self::$instance;
case 'SlpDbApi':
self::$instance = new SlpDbApi($blockchainApiUrl);
return self::$instance;
Expand Down
9 changes: 9 additions & 0 deletions src/BlockchainApi/Http/AbstractHttpAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public function __construct(callable $loggerFn = null, array $options = array())
*/
public abstract function get(string $url, array $options = array());

/**
* Perform a HTTP POST request with Content-Type "application/json".
* @param string $url The full URL string (including possible query params).
* @param array $data The post data as string key-value pairs (not encoded).
* @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 post(string $url, array $data = array(), array $options = array());

protected function logError(string $subject, $error, $data = null): void {
if (static::$loggerFn !== null)
call_user_func(static::$loggerFn, $subject, $error, $data);
Expand Down
20 changes: 20 additions & 0 deletions src/BlockchainApi/Http/BasicHttpAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,25 @@ public function get(string $url, $options = array()) {
$contents = file_get_contents($url, 0, $ctx);
return $contents;
}

public function post(string $url, array $data = array(), array $options = array()) {
$ctx = stream_context_create(array('http' =>
array(
'method' => 'POST',
//'header' => 'Content-Type: application/x-www-form-urlencoded', // use \r\n to separate headers
//'content' => http_build_query($data),
'header' => implode("\r\n", array(
'accept: application/json',
'Content-Type: application/json'
)),
'content' => json_encode($data),
'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;
}
}
?>
39 changes: 39 additions & 0 deletions src/BlockchainApi/Http/CurlHttpAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,44 @@ public function get(string $url, $options = array()) {
curl_close($ch);
return $output;
}

public function post(string $url, array $data = array(), array $options = array()) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, isset($options['maxRedirects']) ? $options['timeout'] : $this->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);
}
*/
//curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"cache-control: no-cache",
//"content-type: application/x-www-form-urlencoded"
'Content-Type: application/json',
'accept: application/json'
));
$output = curl_exec($ch);
if (curl_errno($ch)) {
$output = false;
$error = "URL: $url Curl error: " . curl_error($ch);
$this->logError("cURL error posting to page", $error);
curl_close($ch);
return false;
}
curl_close($ch);
return $output;
}
}
?>
18 changes: 18 additions & 0 deletions src/BlockchainApi/Http/WordpressHttpAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ public function get(string $url, array $options = array()) {
return $body;
}

public function post(string $url, array $data = array(), array $options = array()) {
$wpOptions = $this->getHttpOptions($options);
$wpOptions['headers']['Content-Type'] = 'application/json';
$wpOptions['body'] = json_encode($data);
$response = wp_remote_post($url, $wpOptions);
if ($response instanceof \WP_Error) {
$this->logError("Error on HTTP POST $url", $response->get_error_messages());
return false;
}
$responseCode = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body($response);
if ($responseCode !== 200) {
$this->logError("Invalid HTTP response code $responseCode on GET: $url", $body);
return false;
}
return $body;
}

protected function getHttpOptions(array $options = array()) {
return array(
'timeout' => isset($options['timeout']) ? $options['timeout'] : $this->timeoutSec, //seconds
Expand Down
2 changes: 2 additions & 0 deletions src/BlockchainApi/Structs/BchAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class BchAddress {
// TODO add paging using currentPage and pagesTotal for big addresses

public function __construct(string $cashAddress, string $legacyAddress = '', string $slpAddress = '') {
if (substr($cashAddress, 0, 12) !== 'bitcoincash:')
$cashAddress = 'bitcoincash:' . $cashAddress;
$this->cashAddress = $cashAddress;
$this->legacyAddress = $legacyAddress;
$this->slpAddress = $slpAddress;
Expand Down
14 changes: 14 additions & 0 deletions src/BlockchainApi/Structs/SlpTokenAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
class SlpTokenAddress extends SlpToken {
/** @var string */
public $slpAddress = '';
/** @var float */
public $balance = 0.0;
/** @var int */
public $balanceSat = 0;

/**
* An indexed array with strings of BCH TXIDs
Expand All @@ -17,9 +21,19 @@ class SlpTokenAddress extends SlpToken {

public function __construct(string $slpAddress = '') {
parent::__construct();
if (substr($slpAddress, 0, 13) !== 'simpleledger:')
$slpAddress = 'simpleledger:' . $slpAddress;
$this->slpAddress = $slpAddress;
}

public static function withToken(SlpToken $token, string $slpAddress = ''): SlpTokenAddress {
$address = new static($slpAddress);
foreach ($token as $prop => $value) {
$address->$prop = $value;
}
return $address;
}

public static function fromAddressJson(array $jsonArr, SlpTokenAddress $instance = null, string $tokenID = ""): SlpTokenAddress {
if ($instance === null)
$instance = new SlpTokenAddress();
Expand Down
36 changes: 34 additions & 2 deletions src/CashP.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ public function getBlockchain(): AbstractBlockchainApi {
return $this->blockchainApi;
}

public function toSatoshis(float $bch): float {
public static function toSatoshis(float $bch): float {
return floor($bch * 100000000);
}

public static function fromSatoshis(float $sats): float {
return $sats / 100000000.0;
}

/**
* 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
Expand Down Expand Up @@ -145,7 +149,7 @@ public function getBadgerButton(array $btnConf, string $address, float $amountBC
'address' => $address,
'amountBCH' => $amountBCH,
'tokenAmount' => $amountToken,
'sats' => $this->toSatoshis($amountBCH),
'sats' => static::toSatoshis($amountBCH),
'tokenID' => $tokenID,
'useTokenPayments' => $useTokenPayments,
'buttonLibSrc' => CashP::BADGER_LIB_URL,
Expand Down Expand Up @@ -199,5 +203,33 @@ public function isValidSlpAddress(string $slpAddress): bool {
return false;
return preg_match("/^[a-z0-9]+$/", $addressParts[1]) === 1;
}

/**
* Convert a hex string to base64.
* @param string $hex
* @return string
*/
public static function hexToBase64(string $hex): string {
$return = '';
$split = str_split($hex, 2);
foreach($split as $pair) {
$return .= chr(hexdec($pair));
}
return base64_encode($return);
}

/**
* Reverse bytes in a hex string (to deal with endian-ness).
* @param string $hex
* @return string
*/
public static function reverseBytes(string $hex): string {
$return = '';
$split = str_split($hex, 2);
for($i = count($split)-1; $i >= 0; $i--) {
$return .= $split[$i];
}
return $return;
}
}
?>
2 changes: 1 addition & 1 deletion src/CashpOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class CashpOptions {
//public $hdPathFormat = "0/%d";

/**
* The REST API backend implementation to use. Allowed values: BitcoinComRestApi|SlpDbApi
* The REST API backend implementation to use. Allowed values: BitcoinComRestApi|BchdProtoGatewayApi|SlpDbApi
* @var string
*/
public $blockchainApiImplementation = "BitcoinComRestApi";
Expand Down
13 changes: 10 additions & 3 deletions tests/RestBackendTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,35 @@
use PHPUnit\Framework\TestCase;
use Ekliptor\CashP\CashP;
use Ekliptor\CashP\BlockchainApi\Structs\BchAddress;
use Ekliptor\CashP\CashpOptions;

final class RestBackendTest extends TestCase {
public function testCurrencyRateFetch(): void {
$cashp = new CashP();
$cashp = $this->getCashpForTesting();
$usdRate = $cashp->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();
$cashp = $this->getCashpForTesting();
$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();
$cashp = $this->getCashpForTesting();
$xPub = "xpub6CphSGwqZvKFU9zMfC3qLxxhskBFjNAC9imbSMGXCNVD4DRynJGJCYR63DZe5T4bePEkyRoi9wtZQkmxsNiZfR9D6X3jBxyacHdtRpETDvV";
$address = $cashp->getBlockchain()->createNewAddress($xPub, 3);
$this->assertInstanceOf(BchAddress::class, $address, "BCH address creation failed");
}

protected function getCashpForTesting(): CashP {
$opts = new CashpOptions();
$opts->blockchainApiImplementation = 'BitcoinComRestApi';
return new CashP($opts);
}
}
?>
2 changes: 1 addition & 1 deletion tests/SlpDbTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Ekliptor\CashP\BlockchainApi\Structs\BchAddress;
use Ekliptor\CashP\CashpOptions;

final class RestBackendTest extends TestCase {
final class SlpDbTestTest extends TestCase {
public function testGetTokenInfo(): void {
$cashp = $this->getSlpDbCashP();
$tokenInfo = $cashp->getBlockchain()->getTokenInfo('c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479');
Expand Down

0 comments on commit 7caf245

Please sign in to comment.