Skip to content

Commit

Permalink
- added GetTransaction() function with all inputs/outputs
Browse files Browse the repository at this point in the history
- added helper function to get return address
  • Loading branch information
Ekliptor committed Dec 14, 2020
1 parent dec731e commit a94ccdd
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 2 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ returns `bool` - True if the address is valid, false otherwise.

---

##### getReturnAddress(Transaction $tx): string
Gets the return BCH address (belonging to the sender senders address) defined
as the last address in transaction outputs.
* `Transaction $tx` - The transaction from getTransaction() call.

returns `string` - the address

---

##### getReturnSlpAddress(Transaction $tx): string
Gets the return SLP address (belonging to the sender senders address) defined
as the last address in transaction outputs.
* `Transaction $tx` - The transaction from getTransaction() call.

returns `string` - the address

---


#### CashpOptions class
A set of advanced config properties.
Expand Down Expand Up @@ -189,6 +207,14 @@ returns `SlpTokenAddress` - The token or `null` on failure

---

##### getTransaction(string $transactionID): ?Transaction
Returns a transaction with all inputs and outputs including SLP data.
* `string $transactionID` -

returns `Transaction` - The transaction or `null` on failure

---


## Testing
To run unit tests type the following command in the project root directory (requires PHPUnit, installed automatically with Composer):
Expand Down
8 changes: 8 additions & 0 deletions src/BlockchainApi/AbstractBlockchainApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Ekliptor\CashP\BlockchainApi\Structs\SlpToken;
use Ekliptor\CashP\BlockchainApi\Structs\SlpTokenAddress;
use Ekliptor\CashP\BlockchainApi\Http\AbstractHttpAgent;
use Ekliptor\CashP\BlockchainApi\Structs\Transaction;

class BlockchainException extends \Exception {
public function __construct($message = null, $code = null, $previous = null) {
Expand Down Expand Up @@ -133,6 +134,13 @@ public abstract function getAddressDetails(string $address): ?BchAddress;
*/
public abstract function getSlpAddressDetails(string $address, string $tokenID): ?SlpTokenAddress;

/**
* Returns a transaction with all inputs and outputs including SLP data.
* @param string $transactionID
* @return Transaction|NULL
*/
public abstract function getTransaction(string $transactionID): ?Transaction;

/**
* Creates a new addresses from the xPub repeatedly by incrementing $addressCount in $hdPathFormat until it finds an address with an empty balance (not used by another wallet).
* @param string $xPub The extended public key. Called 'Master Public Key' in Electron Cash.
Expand Down
51 changes: 51 additions & 0 deletions src/BlockchainApi/BchdProtoGatewayApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
use Ekliptor\CashP\BlockchainApi\Structs\BchAddress;
use Ekliptor\CashP\BlockchainApi\Structs\SlpToken;
use Ekliptor\CashP\BlockchainApi\Structs\SlpTokenAddress;
use Ekliptor\CashP\BlockchainApi\Structs\Transaction;
use Ekliptor\CashP\BlockchainApi\Structs\TransactionInput;
use Ekliptor\CashP\BlockchainApi\Structs\TransactionBaseData;
use Ekliptor\CashP\BlockchainApi\Structs\TransactionOutput;
use Ekliptor\CashP\BlockchainApi\Structs\SlpTransactionData;

class BchdProtoGatewayApi extends AbstractBlockchainApi {

Expand Down Expand Up @@ -157,6 +162,39 @@ public function getSlpAddressDetails(string $address, string $tokenID): ?SlpToke
return $slpAddress;
}

public function getTransaction(string $transactionID): ?Transaction {
$tx = $this->getTransactionDetails($transactionID);
if (empty($tx) || !isset($tx->transaction) || (isset($tx->error) && !empty($tx->error)))
return null;

$txID = bin2hex(base64_decode($tx->transaction->hash));
$transaction = new Transaction($txID);
foreach ($tx->transaction->inputs as $in) {
$input = new TransactionInput();
if (isset($in->index)) // not present on index == 0
$input->index = $in->index;
$input->value = intval($in->value);
$input->address = 'bitcoincash:' . $in->address;
if (isset($in->slp_token))
$this->addSlpTransactionData($input, $in->slp_token);
$transaction->inputs[] = $input;
}
foreach ($tx->transaction->outputs as $out) {
$output = new TransactionOutput();
if (isset($out->index)) // not present on index == 0
$output->index = $out->index;
if (isset($out->value)) // missing on OP_RETURN outputs (value 0)
$output->value = intval($out->value);
if (isset($out->address)) // missing on OP_RETURN outputs (value 0)
$output->address = 'bitcoincash:' . $out->address;
if (isset($out->slp_token))
$this->addSlpTransactionData($output, $out->slp_token);
$transaction->outputs[] = $output;
}

return $transaction;
}

protected function getTransactionDetails(string $transactionID): ?\stdClass {
$transactionID = static::ensureBase64Encoding($transactionID);
if (isset($this->transactionCache[$transactionID]))
Expand Down Expand Up @@ -244,6 +282,19 @@ protected function addTokenMetadata(SlpToken $token, array $bchdTokenMetadata):
$this->logError("Unable to find desired token metadata", $bchdTokenMetadata);
}

protected function addSlpTransactionData(TransactionBaseData $tx, \stdClass $rawSlpData): void {
if (empty($rawSlpData))
return;

$tx->slp = new SlpTransactionData();
$tx->slp->tokenID = bin2hex(base64_decode($rawSlpData->token_id));
$tx->slp->amount = intval($rawSlpData->amount);
if (isset($rawSlpData->address)) // not present on inputs
$tx->slp->address = 'simpleledger:' . $rawSlpData->address;
if (isset($rawSlpData->decimals)) // not present on inputs
$tx->slp->decimals = intval($rawSlpData->decimals);
}

protected static function ensureBase64Encoding(string $hash, bool $reverseBytes = true): string {
if (preg_match("/^[0-9a-f]+$/i", $hash) !== 1)
return $hash;
Expand Down
5 changes: 5 additions & 0 deletions src/BlockchainApi/BitcoinComRestApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Ekliptor\CashP\BlockchainApi\Structs\BchAddress;
use Ekliptor\CashP\BlockchainApi\Structs\SlpToken;
use Ekliptor\CashP\BlockchainApi\Structs\SlpTokenAddress;
use Ekliptor\CashP\BlockchainApi\Structs\Transaction;

class BitcoinComRestApi extends AbstractBlockchainApi {

Expand Down Expand Up @@ -103,6 +104,10 @@ public function getSlpAddressDetails(string $address, string $tokenID): ?SlpToke
return $slpToken;
}

public function getTransaction(string $transactionID): ?Transaction {
throw new \Exception("getTransaction() is not yet implemented on " . get_class($this)); // TODO
}

protected function getTransactionDetails(string $transactionID): ?\stdClass {
if (isset($this->transactionCache[$transactionID]))
return $this->transactionCache[$transactionID];
Expand Down
7 changes: 6 additions & 1 deletion src/BlockchainApi/Http/BasicHttpAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ public function get(string $url, $options = array()) {
$ctx = stream_context_create(array('http' =>
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
'max_redirects' => isset($options['maxRedirects']) ? $options['maxRedirects'] : $this->maxRedirects,
'header' => implode("\r\n", array(
'accept: application/json',
'Content-Type: application/json',
'Cache-Control: no-cache,max-age=0'
)),
))
);
$contents = file_get_contents($url, 0, $ctx);
Expand Down
6 changes: 6 additions & 0 deletions src/BlockchainApi/Http/CurlHttpAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public function get(string $url, $options = array()) {
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirects);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
//"content-type: application/x-www-form-urlencoded"
'Content-Type: application/json',
'Accept: application/json',
'Cache-Control: no-cache,max-age=0'
));
/*
if ($skip_certificate_check) {
curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, false);
Expand Down
1 change: 0 additions & 1 deletion src/BlockchainApi/Http/WordpressHttpAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public function get(string $url, array $options = array()) {

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) {
Expand Down
5 changes: 5 additions & 0 deletions src/BlockchainApi/SlpDbApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Ekliptor\CashP\BlockchainApi\Structs\BchAddress;
use Ekliptor\CashP\BlockchainApi\Structs\SlpToken;
use Ekliptor\CashP\BlockchainApi\Structs\SlpTokenAddress;
use Ekliptor\CashP\BlockchainApi\Structs\Transaction;

class SlpDbApi extends AbstractBlockchainApi {
/** @var array */
Expand Down Expand Up @@ -100,6 +101,10 @@ public function getSlpAddressDetails(string $address, string $tokenID): ?SlpToke
return $slpToken;
}

public function getTransaction(string $transactionID): ?Transaction {
throw new \Exception("getTransaction() is not yet implemented on " . get_class($this)); // TODO
}

protected function getTokenTransactionIDs(string $address, string $tokenID): array {
$transactions = array();
$response = $this->executeQuery('{
Expand Down
35 changes: 35 additions & 0 deletions src/CashP.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Ekliptor\CashP;

use Ekliptor\CashP\BlockchainApi\Http\BasicHttpAgent;
use Ekliptor\CashP\BlockchainApi\Structs\Transaction;
use Ekliptor\CashP\BlockchainApi\AbstractBlockchainApi;


Expand Down Expand Up @@ -204,6 +205,40 @@ public function isValidSlpAddress(string $slpAddress): bool {
return preg_match("/^[a-z0-9]+$/", $addressParts[1]) === 1;
}

/**
* Gets the return BCH address (belonging to the sender senders address) defined
* as the last address in transaction outputs.
* @param Transaction $tx
* @return string
*/
public function getReturnAddress(Transaction $tx): string {
$len = count($tx->outputs);
for ($i = $len-1; $i >= 0; $i--) {
$out = $tx->outputs[$i];
if (!empty($out->address))
return $out->address;
}
return '';
}

/**
* Gets the return SLP address (belonging to the sender senders address) defined
* as the last address in transaction outputs.
* @param Transaction $tx
* @return string
*/
public function getReturnSlpAddress(Transaction $tx): string {
$len = count($tx->outputs);
for ($i = $len-1; $i >= 0; $i--) {
$out = $tx->outputs[$i];
if (empty($out->slp))
continue;
if (!empty($out->slp->address))
return $out->slp->address;
}
return '';
}

/**
* Convert a hex string to base64.
* @param string $hex
Expand Down
24 changes: 24 additions & 0 deletions tests/BchdBackendTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,30 @@ public function testAddressCreation(): void {
$this->assertInstanceOf(BchAddress::class, $address, "BCH address creation failed");
}

public function testGetTransaction(): void {
$cashp = $this->getCashpForTesting();
$txHash = 'ca87043999ad7c441193ced336577b4ba50fc7a45fbaf6c0bbda825cc42d7fc5';
$tx = $cashp->getBlockchain()->getTransaction($txHash);
if (count($tx->outputs) !== 2)
$this->fail("expected 2 outputs in TX $txHash");
$this->assertEquals(5027, $tx->outputs[1]->value, "TX $txHash output has wrong value");

$returnAddress = $cashp->getReturnAddress($tx);
$this->assertEquals('bitcoincash:qpwk4x4pz7xd5mxg7w40v95vhjk2qcuawsmusryd7y', $returnAddress, 'wrong BCH return address');
}

public function testGetSlpTransaction(): void {
$cashp = $this->getCashpForTesting();
$txHash = '1407222af22676f9706847b629d26350eb118c8763875a656abbc3f5df786d18';
$tx = $cashp->getBlockchain()->getTransaction($txHash);
if (count($tx->outputs) !== 4)
$this->fail("expected 4 outputs in TX $txHash");
$this->assertEquals(806745, $tx->outputs[3]->value, "TX $txHash output has wrong value");

$returnAddress = $cashp->getReturnSlpAddress($tx);
$this->assertEquals('simpleledger:qzapwgc088xj9hf8pcsrzsey8j7svcqysyp9ygxmq8', $returnAddress, 'wrong SLP return address');
}

protected function getCashpForTesting(): CashP {
$opts = new CashpOptions();
$opts->blockchainApiImplementation = 'BchdProtoGatewayApi';
Expand Down

0 comments on commit a94ccdd

Please sign in to comment.