t3ran13 / php-graphene-node-client
PHP client for connection to Graphene node (steem/golos)
Installs: 465
Dependents: 1
Suggesters: 0
Security: 0
Stars: 12
Watchers: 4
Forks: 12
Open Issues: 2
Requires
- php: ^7.0
- simplito/elliptic-php: ^1.0
- stephenhill/base58: ^1.1
- t3ran13/bytebuffer: ^1.0
- textalk/websocket: ^1.2
Requires (Dev)
- dev-master
- v6.0.4
- v6.0.3
- v6.0.2
- v6.0.1
- v6.0.0
- v5.2.2
- v5.2.1
- v5.2.0
- v5.1.3
- v5.1.2
- v5.1.1
- v5.1.0
- v5.0.4
- v5.0.3
- v5.0.1
- v4.1.2
- v4.1.1
- v4.1.0
- v4.0.12
- v4.0.11
- v4.0.10
- v4.0.9
- v4.0.8
- v4.0.7
- v4.0.6
- v4.0.5
- v4.0.4
- v4.0.3
- v4.0.2
- v4.0.1
- v4.0.0
- v3.1.1
- v3.1.0
- v3.0.2
- v3.0.1
- v3.0.0
- v2.3.5
- v2.3.4
- v2.3.3
- v2.3.2
- v2.2.2
- v2.2.1
- v2.2.0
- v2.1.0
- v2.0.1
- v2.0.0
- v1.0.1
- v1.0.0
- 0.18.0.x-dev
- dev-revert-12-0.17.0
- dev-develop
- dev-development
This package is auto-updated.
Last update: 2024-09-29 04:31:16 UTC
README
PHP client for connection to VIZ/STEEM/GOLOS/WHALESHARES node
Install Via Composer
For readonly, without broadcast
composer require t3ran13/php-graphene-node-client
with broadcast (sending transactions to blockchain)
actual dockerfile and requests examples see in branch "debug"
install components
- libgmp-dev
install extensions
- gmp
Basic Usage
<?php use GrapheneNodeClient\Commands\CommandQueryData; use GrapheneNodeClient\Commands\Commands; use GrapheneNodeClient\Commands\Single\GetDiscussionsByCreatedCommand; use GrapheneNodeClient\Connectors\WebSocket\GolosWSConnector; use GrapheneNodeClient\Connectors\WebSocket\SteemitWSConnector; //Set params for query $commandQuery = new CommandQueryData(); $data = [ [ 'limit' => $limit, 'select_tags' => ['golos'], // for GOLOS 'tag' => 'steemit', // for STEEMIT ] ]; $commandQuery->setParams($data); //OR $commandQuery = new CommandQueryData(); $commandQuery->setParamByKey('0:limit', $limit); $commandQuery->setParamByKey('0:select_tags', [$tag]); $commandQuery->setParamByKey('0:tag', $tag); //and use single command $command = new GetDiscussionsByCreatedCommand(new GolosWSConnector()); $golosPosts = $command->execute( $commandQuery ); //or commands aggregator class $commands = new Commands(new GolosWSConnector()); $golosPosts = $commands->get_discussions_by_created() ->execute( $commandQuery ); // will return // [ // "id" => 1, // "result" => [ // [ // "id": 466628, // "author": "piranya", // "permlink": "devyatyi-krug", // ... // ], // ... // ] // ] //single command $command = new GetDiscussionsByCreatedCommand(new SteemitWSConnector()); $steemitPosts = $command->execute( $commandQuery, 'result', SteemitWSConnector::ANSWER_FORMAT_ARRAY // or SteemitWSConnector::ANSWER_FORMAT_OBJECT ); //or commands aggregator class $commands = new Commands(new GolosWSConnector()); $golosPosts = $commands->get_discussions_by_created() ->execute( $commandQuery, 'result', SteemitWSConnector::ANSWER_FORMAT_ARRAY // or SteemitWSConnector::ANSWER_FORMAT_OBJECT ); // will return // [ // [ // "id": 466628, // "author": "piranya", // "permlink": "devyatyi-krug", // ... // ], // ... // ]
Implemented Commands List
Single Commands
- BroadcastTransactionCommand
- BroadcastTransactionSynchronousCommand
- GetAccountCountCommand
- GetAccountHistoryCommand
- GetAccountsCommand
- GetAccountVotesCommand
- GetActiveWitnessesCommand
- GetApiByNameCommand //ONLY STEEM/whaleshares
- GetBlockCommand
- GetBlockHeaderCommand
- GetConfigCommand
- GetContentCommand
- GetContentRepliesCommand
- GetCurrentMedianHistoryPriceCommand //STEEM/GOLOS
- GetDiscussionsByAuthorBeforeDateCommand
- GetDiscussionsByBlogCommand
- GetDiscussionsByCreatedCommand
- GetDiscussionsByFeedCommand
- GetDiscussionsByTrendingCommand
- GetDynamicGlobalPropertiesCommand
- GetFollowersCommand
- GetOpsInBlock
- GetTransactionHexCommand
- GetTrendingCategoriesCommand //only steem/whaleshares
- GetVersionCommand
- GetWitnessesByVoteCommand
- LoginCommand //ONLY for STEEM/whaleshares
All single commands can be called through Commands Class as methods (example: (new Commands)->get_block()->execute(...) )
broadcast operations templates
namespace GrapheneNodeClient\Tools\ChainOperations
- vote
- transfer
- comment // steem or golos
- content // only viz
- witness_update
<?php use GrapheneNodeClient\Tools\ChainOperations\OpVote; use GrapheneNodeClient\Connectors\Http\SteemitHttpConnector; use GrapheneNodeClient\Connectors\WebSocket\GolosWSConnector; $connector = new SteemitHttpConnector(); //$connector = new GolosWSConnector(); //$connector = new VizWSConnector(); $answer = OpVote::doSynchronous( $connector, 'guest123', '5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg', 'firepower', 'steemit-veni-vidi-vici-steemfest-2016-together-we-made-it-happen-thank-you-steemians', 10000 ); // example of answer //Array //( // [id] => 5 // [result] => Array // ( // [id] => a2c52988ea870e446480782ff046994de2666e0d // [block_num] => 17852337 // [trx_num] => 1 // [expired] => // ) // //)
Implemented Connectors List
namespace: GrapheneNodeClient\Connectors\WebSocket OR GrapheneNodeClient\Connectors\Http;
- VizWSConnector
- VizHttpJsonRpcConnector
- GolosWSConnector
- GolosHttpJsonRpcConnector
- SteemitWSConnector
- SteemitHttpJsonRpcConnector
List of available STEEM nodes are here
Switching between connectors
<?php use GrapheneNodeClient\Commands\CommandQueryData; use GrapheneNodeClient\Commands\Single\GetContentCommand; use GrapheneNodeClient\Connectors\InitConnector; $command = new GetContentCommand(InitConnector::getConnector(InitConnector::PLATFORM_STEEMIT)); $commandQuery = new CommandQueryData(); $commandQuery->setParamByKey('0', 'author'); $commandQuery->setParamByKey('1', 'permlink'); //OR $commandQuery = new CommandQueryData(); $commandQuery->setParams( [ 0 => "author", 1 => "permlink" ] ); $content = $command->execute( $commandQuery ); // will return // [ // "id" => 1, // "result" => [ // ... // ] // ]
Creating Own Connector
<?php namespace My\App\Connectors; use GrapheneNodeClient\Connectors\ConnectorInterface; class MyConnector implements ConnectorInterface { public function setConnectionTimeoutSeconds($timeoutSeconds) { // TODO: Implement setConnectionTimeoutSeconds() method. } public function setMaxNumberOfTriesToReconnect($triesN) { // TODO: Implement setMaxNumberOfTriesToReconnect() method. } /** * platform name for witch connector is. steemit or golos. */ public function getPlatform() { // TODO: Implement getPlatform() method. } /** * @param string $apiName calling api name - follow_api, database_api and ect. * @param array $data options and data for request * @param string $answerFormat * * @return array|object return answer data */ public function doRequest($apiName, array $data, $answerFormat = self::ANSWER_FORMAT_ARRAY) { // TODO: Implement doRequest() method. } }
Or use GrapheneNodeClient\Connectors\WebSocket\WSConnectorAbstract for extending
<?php namespace My\App\Commands; use GrapheneNodeClient\Commands\Single\CommandAbstract; use GrapheneNodeClient\Connectors\ConnectorInterface; class GolosWSConnector extends WSConnectorAbstract { /** * @var string */ protected $platform = self::PLATFORM_GOLOS; /** * waiting answer from Node during $wsTimeoutSeconds seconds * * @var int */ protected $wsTimeoutSeconds = 5; /** * max number of tries to get answer from the node * * @var int */ protected $maxNumberOfTriesToCallApi = 3; /** * wss or ws servers, can be list. First node is default, other are reserve. * After $maxNumberOfTriesToCallApi tries connects to default it is connected to reserve node. * * @var string|array */ protected $nodeURL = ['wss://ws.golos.io', 'wss://api.golos.cf']; }
Creating Own Command
You have to update $map properties in GolosApiMethods/SteemitApiMethods/VizApiMethods classes as shown below
<?php namespace GrapheneNodeClient\Commands; class SteemitApiMethods { //... //protected $projectApi = [ 'method_name' => [ 'apiName' => 'api_name', 'fields'=>['массив с полями из команды']]]; protected static $map = [ //... 'broadcast_transaction' => [ 'apiName' => 'network_broadcast_api', 'fields' => [ '0:ref_block_num' => ['integer'], '0:ref_block_prefix' => ['integer'], '0:expiration' => ['string'], '0:operations:*:0' => ['string'], '0:operations:*:1' => ['array'], '0:extensions' => ['array'], '0:signatures' => ['array'] ] ], //... 'broadcast_transaction' => [ 'apiName' => 'your_method', 'fields' => [ //your fields ] ] ]; }
Tools
Transliterator
<?php use GrapheneNodeClient\Tools\Transliterator; //Encode tags $tag = Transliterator::encode('пол', Transliterator::LANG_RU); // return 'pol'; //Decode tags $tag = Transliterator::encode('ru--pol', Transliterator::LANG_RU); // return 'пол';
Reputation viewer
<?php use GrapheneNodeClient\Tools\Reputation; $rep = Reputation::calculate($account['reputation']);
Bandwidth
Sometimes you can't send transaction to blockchain because your account has not enough bandwidth. Now you can check this before sending transaction to blockchain as shown below
<?php use GrapheneNodeClient\Connectors\Http\SteemitHttpConnector; use GrapheneNodeClient\Commands\CommandQueryData; use GrapheneNodeClient\Tools\Bandwidth; use GrapheneNodeClient\Tools\Transaction; $connector = new SteemitHttpConnector(); /** @var CommandQueryData $tx */ $tx = Transaction::init($connector, 'PT4M'); $tx->setParamByKey( '0:operations:0', [ 'vote', [ 'voter' => $voter, 'author' => $author, 'permlink' => $permlink, 'weight' => $weight ] ] ); $command = new BroadcastTransactionSynchronousCommand($connector); Transaction::sign($chainName, $tx, ['posting' => $publicWif]); $trxString = mb_strlen(json_encode($tx->getParams()), '8bit'); if (Bandwidth::isEnough($connector, $voter, 'market', $trxString)) { $answer = $command->execute( $tx ); } //or other way $bandwidth = Bandwidth::getBandwidthByAccountName($voter, 'market', $connector); //Array //( // [used] => 3120016 // [available] => 148362781 //) if ($trxString * 10 + $bandwidth['used'] < $bandwidth['available']) { $answer = $command->execute( $tx ); } //
Transaction for blockchain (broadcast)
<?php use GrapheneNodeClient\Tools\Transaction; use GrapheneNodeClient\Connectors\Http\SteemitHttpConnector; use GrapheneNodeClient\Connectors\WebSocket\GolosWSConnector; $connector = new SteemitHttpConnector(); //$connector = new GolosWSConnector(); /** @var CommandQueryData $tx */ $tx = Transaction::init($connector, 'PT4M'); $tx->setParamByKey( '0:operations:0', [ 'vote', [ 'voter' => $voter, 'author' => $author, 'permlink' => $permlink, 'weight' => $weight ] ] ); $command = new BroadcastTransactionSynchronousCommand($connector); Transaction::sign($chainName, $tx, ['posting' => $publicWif]); $answer = $command->execute( $tx );
** WARNING**
Transactions are signing with spec256k1-php with function secp256k1_ecdsa_sign_recoverable($context, $signatureRec, $msg32, $privateKey) and if it is not canonical from first time, you have to make transaction for other block. For searching canonical sign function have to implement two more parameters, but spec256k1-php library does not have it. It is was solved with php-hack in Transaction::sign()
... //becouse spec256k1-php canonical sign trouble will use php hack. //If sign is not canonical, we have to chang msg (we will add 1 sec to tx expiration time) and try to sign again $nTries = 0; while (true) { $nTries++; $msg = self::getTxMsg($chainName, $trxData); echo '<pre>' . print_r($trxData->getParams(), true) . '<pre>'; //FIXME delete it try { foreach ($privateWIFs as $keyName => $privateWif) { $index = count($trxData->getParams()[0]['signatures']); /** @var CommandQueryData $trxData */ $trxData->setParamByKey('0:signatures:' . $index, self::signOperation($msg, $privateWif)); } break; } catch (TransactionSignException $e) { if ($nTries > 200) { //stop tries to find canonical sign throw $e; break; } else { /** @var CommandQueryData $trxData */ $params = $trxData->getParams(); foreach ($params as $key => $tx) { $tx['expiration'] = (new \DateTime($tx['expiration'])) ->add(new \DateInterval('PT0M1S')) ->format('Y-m-d\TH:i:s\.000'); $params[$key] = $tx; } $trxData->setParams($params); } } ...
Tests
You need to install PhpUnit in your system (https://phpunit.de/manual/3.7/en/installation.html)
cd Tests
phpunit CommandsTest.php
phpunit CommandsTest.php --filter=testGetBlock // test only one command