├── .gitignore ├── .travis.yml ├── Tools ├── TransactionSignExceptionInterface.php ├── TransactionSignException.php ├── Reputation.php ├── ChainOperations │ ├── ChainOperationsViz.php │ ├── ChainOperations.php │ ├── OpTransfer.php │ ├── OpVote.php │ ├── ChainOperationsWhaleshares.php │ ├── ChainOperationsHive.php │ ├── ChainOperationsSteem.php │ ├── OpComment.php │ ├── ChainOperationsGolos.php │ └── OpContent.php ├── Auth.php ├── Transliterator.php ├── Bandwidth.php └── Transaction.php ├── Commands ├── Single │ ├── GetVersionCommand.php │ ├── GetOpsInBlock.php │ ├── GetTrendingTagsCommand.php │ ├── GetBlockCommand.php │ ├── GetTransaction.php │ ├── GetConfigCommand.php │ ├── GetContentCommand.php │ ├── getTickerCommand.php │ ├── GetAccountsCommand.php │ ├── GetFollowersCommand.php │ ├── GetFollowingCommand.php │ ├── GetTrendingCategoriesCommand.php │ ├── GetWitnessesByVoteCommand.php │ ├── GetActiveWitnessesCommand.php │ ├── GetWitnessByAccountCommand.php │ ├── getRewardFundCommand.php │ ├── GetAccountCountCommand.php │ ├── GetAccountVotesCommand.php │ ├── GetActiveVotesCommand.php │ ├── GetBlockHeaderCommand.php │ ├── GetDiscussionsByBlogCommand.php │ ├── GetDiscussionsByFeedCommand.php │ ├── GetFeedHistoryCommand.php │ ├── GetFollowCountCommand.php │ ├── GetApiByNameCommand.php │ ├── GetAccountHistoryCommand.php │ ├── GetChainPropertiesCommand.php │ ├── GetContentRepliesCommand.php │ ├── GetDiscussionsByCreatedCommand.php │ ├── GetDiscussionsByTrendingCommand.php │ ├── GetVestingDelegationsCommand.php │ ├── getDiscussionsByCommentsCommand.php │ ├── getDiscussionsByContentsCommand.php │ ├── GetDiscussionsByAuthorBeforeDateCommand.php │ ├── GetDynamicGlobalPropertiesCommand.php │ ├── GetCurrentMedianHistoryPriceCommand.php │ ├── LoginCommand.php │ ├── GetTransactionHexCommand.php │ ├── BroadcastTransactionCommand.php │ ├── BroadcastTransactionSynchronousCommand.php │ └── CommandAbstract.php ├── CommandQueryDataException.php ├── CommandInterface.php ├── CommandQueryDataInterface.php ├── Commands.php ├── CommandQueryData.php ├── WhalesharesApiMethods.php ├── HiveApiMethods.php ├── SteemitApiMethods.php ├── VizApiMethods.php └── GolosApiMethods.php ├── Connectors ├── WebSocket │ ├── VizWSConnector.php │ ├── GolosWSConnector.php │ ├── WhalesharesWSConnector.php │ ├── SteemitWSConnector.php │ └── WSConnectorAbstract.php ├── Http │ ├── VizHttpJsonRpcConnector.php │ ├── WhalesharesHttpJsonRpcConnector.php │ ├── SteemitHttpJsonRpcConnector.php │ ├── GolosHttpJsonRpcConnector.php │ ├── HiveHttpJsonRpcConnector.php │ └── HttpJsonRpcConnectorAbstract.php ├── ConnectorInterface.php └── InitConnector.php ├── composer.json ├── LICENSE ├── examples └── Broadcast │ ├── GetTransactionHex.php │ ├── WitnessUpdate.php │ └── Donate.php ├── Tests └── CommandsTest.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | -------------------------------------------------------------------------------- /Tools/TransactionSignExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 0 ? 1 : -1) + $addNumber; 21 | } 22 | 23 | 24 | return round($rating, $precision); 25 | } 26 | } -------------------------------------------------------------------------------- /Connectors/WebSocket/SteemitWSConnector.php: -------------------------------------------------------------------------------- 1 | $params['key'][1]['one_more_key'] 23 | * $setKey example: 'key:a1' => $params['key']['a1'] 24 | * 25 | * @param string $setKey 26 | * @param mixed $setVal 27 | */ 28 | public function setParamByKey($setKey, $setVal); 29 | 30 | /** 31 | * @param array $map 32 | * @return array 33 | */ 34 | public function prepareData($map); 35 | } 36 | -------------------------------------------------------------------------------- /Connectors/Http/GolosHttpJsonRpcConnector.php: -------------------------------------------------------------------------------- 1 | connector = $connector; 22 | } 23 | 24 | /** 25 | * @param CommandQueryDataInterface $commandQueryData 26 | * @param string $answerFormat 27 | * @param string $getElementWithKey If you want to get only certain element from answer. 28 | * Example: 'key:123:qwe' => $array['key']['123']['qwe'] or $object->key->123->qwe 29 | * @return array|object 30 | */ 31 | public function execute(CommandQueryDataInterface $commandQueryData, $getElementWithKey = null, $answerFormat = ConnectorInterface::ANSWER_FORMAT_ARRAY) 32 | { 33 | $commands = new Commands($this->connector); 34 | return $commands->{$this->method}()->execute($commandQueryData, $getElementWithKey, $answerFormat); 35 | } 36 | } -------------------------------------------------------------------------------- /Tools/ChainOperations/ChainOperationsViz.php: -------------------------------------------------------------------------------- 1 | 2, 12 | ChainOperations::OPERATION_CUSTOM => 10, 13 | ChainOperations::OPERATION_WITNESS_UPDATE => 6, 14 | ]; 15 | 16 | const FIELDS_TYPES = [ 17 | ChainOperations::OPERATION_TRANSFER => [ 18 | 'from' => OperationSerializer::TYPE_STRING, 19 | 'to' => OperationSerializer::TYPE_STRING, 20 | 'amount' => OperationSerializer::TYPE_ASSET, 21 | 'memo' => OperationSerializer::TYPE_STRING 22 | ], 23 | ChainOperations::OPERATION_CUSTOM => [ 24 | 'required_auths' => OperationSerializer::TYPE_SET_STRING, 25 | 'required_posting_auths' => OperationSerializer::TYPE_SET_STRING, 26 | 'id' => OperationSerializer::TYPE_STRING, 27 | 'json' => OperationSerializer::TYPE_STRING 28 | ], 29 | ChainOperations::OPERATION_WITNESS_UPDATE => [ 30 | 'owner' => OperationSerializer::TYPE_STRING, 31 | 'url' => OperationSerializer::TYPE_STRING, 32 | 'block_signing_key' => OperationSerializer::TYPE_PUBLIC_KEY 33 | ] 34 | ]; 35 | } -------------------------------------------------------------------------------- /Connectors/InitConnector.php: -------------------------------------------------------------------------------- 1 | getPlatform(); 39 | /** @var CommandQueryData $tx */ 40 | $tx = Transaction::init($connector); 41 | $tx->setParamByKey( 42 | '0:operations:0', 43 | [ 44 | 'witness_update', 45 | [ 46 | 'owner' => 't3ran13-miner', 47 | 'url' => 'https://golos.io/@t3ran13-miner', 48 | 'block_signing_key' => 'GLS7eExwRw2Waqrq7DcC1553revU7MWvjHMqK8sbWGScaBfsThnzN', 49 | 'props' => 50 | [ 51 | 'account_creation_fee' => '0.001 GOLOS', 52 | 'maximum_block_size' => 131072, 53 | 'sbd_interest_rate' => 1000 54 | ], 55 | 'fee' => '0.000 GOLOS' 56 | ] 57 | ] 58 | ); 59 | 60 | $command = new GetTransactionHexCommand($connector); 61 | $answer = $command->execute( 62 | $tx 63 | ); 64 | 65 | 66 | 67 | echo PHP_EOL . '
' . print_r($answer, true) . '
';
68 | die; //FIXME delete it
69 | 


--------------------------------------------------------------------------------
/examples/Broadcast/WitnessUpdate.php:
--------------------------------------------------------------------------------
 1 | getPlatform();
39 | /** @var CommandQueryData $tx */
40 | $tx = Transaction::init($connector);
41 | $tx->setParamByKey(
42 |     '0:operations:0',
43 |     [
44 |         'witness_update',
45 |         [
46 |             'owner'             => 'guest123',
47 |             'url'               => 'https://golos.io/@guest123',
48 |             'block_signing_key' => 'GLS7eExwRw2Waqrq7DcC1553revU7MWvjHMqK8sbWGScguest123',
49 |             'props'             =>
50 |                 [
51 |                     'account_creation_fee' => '0.001 GOLOS',
52 |                     'maximum_block_size'   => 131072,
53 |                     'sbd_interest_rate'    => 1000
54 |                 ],
55 |             'fee'               => '0.000 GOLOS'
56 |         ]
57 |     ]
58 | );
59 | Transaction::sign($chainName, $tx, ['active' => '5_active_private_key']);
60 | 
61 | $command = new BroadcastTransactionSynchronousCommand($connector);
62 | $answer = $command->execute(
63 |     $tx
64 | );
65 | 
66 | 
67 | 
68 | echo PHP_EOL . '
' . print_r($answer, true) . '
';
69 | die; //FIXME delete it
70 | 


--------------------------------------------------------------------------------
/examples/Broadcast/Donate.php:
--------------------------------------------------------------------------------
 1 | getPlatform();
39 | /** @var CommandQueryData $tx */
40 | $tx = Transaction::init($connector);
41 | $tx->setParamByKey(
42 |     '0:operations:0',
43 |     [
44 |         'donate',
45 |         [
46 |             'from'       => 't3ran13',
47 |             'to'         => 'redhat',
48 |             'amount'     => '10.000 GOLOS',
49 |             'memo'       =>
50 |                 [
51 |                     'app'     => 'golos-id',
52 |                     'version' => 1,
53 |                     'target'  => [
54 |                         'author'   => 'redhat',
55 |                         'permlink' => 'vozmeshenii-ubytkov-prichinennykh-nekachestvennoi-uslugoi'
56 |                     ],
57 |                     'comment'  => 'test php--graphene-node-client'
58 |                 ],
59 |             'extensions' => []
60 |         ]
61 |     ]
62 | );
63 | Transaction::sign($chainName, $tx, ['active' => '5_active_private_key']);
64 | 
65 | $command = new BroadcastTransactionSynchronousCommand($connector);
66 | $answer = $command->execute(
67 |     $tx
68 | );
69 | 
70 | 
71 | 
72 | echo PHP_EOL . '
' . print_r($answer, true) . '
';
73 | die; //FIXME delete it
74 | 


--------------------------------------------------------------------------------
/Tools/Auth.php:
--------------------------------------------------------------------------------
 1 | write($base58->decode($privateWif));
36 |         $version = $wifBuffer->readInt8(0);
37 |         if ($version !== 128) {
38 |             //        assert.equal(0x80, version, `Expected version ${0x80}, instead got ${version}`);
39 |             throw new \Exception('Expected version 128, instead got ' . $version);
40 |         }
41 | 
42 |         //checking WIF checksum
43 |         $private_key = $wifBuffer->read(0, $wifBuffer->length() - 4);
44 |         $checksum = $wifBuffer->read($wifBuffer->length() - 4, 4);
45 |         $new_checksum = hash('sha256', $private_key, true);
46 |         $new_checksum = hash('sha256', $new_checksum, true);
47 |         $new_checksum = substr($new_checksum, 0, 4);
48 |         if ($new_checksum !== $checksum) {
49 |             throw new \Exception('Invalid WIF key (checksum miss-match)');
50 |         }
51 | 
52 |         //getting private_key
53 | //        $private_key = substr(strrev($wifBuffer->read(0, $wifBuffer->length())), 1, -4);
54 |         $private_key = substr($private_key, 1);
55 | //        if (32 !== buf.length) {
56 | //            console.log(`WARN: Expecting 32 bytes, instead got ${buf.length}, stack trace:`, new Error().stack);
57 | //        }
58 |         $length = strlen($private_key);
59 |         if ($length !== 32) {
60 |             throw new \Exception('Expecting 32 bytes for private_key, instead got ' . $length);
61 |         }
62 | 
63 |         return $private_key;
64 |     }
65 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/OpTransfer.php:
--------------------------------------------------------------------------------
 1 | getPlatform();
27 |         /** @var CommandQueryData $tx */
28 |         $tx = Transaction::init($connector);
29 |         $tx->setParamByKey(
30 |             '0:operations:0',
31 |             [
32 |                 'transfer',
33 |                 [
34 |                     'from'   => $from,
35 |                     'to'     => $to,
36 |                     'amount' => $amountWithAsset,
37 |                     'memo'   => $memo
38 |                 ]
39 |             ]
40 |         );
41 | 
42 |         $command = new BroadcastTransactionCommand($connector);
43 |         Transaction::sign($chainName, $tx, ['active' => $privateActiveWif]);
44 | 
45 |         $answer = $command->execute(
46 |             $tx
47 |         );
48 | 
49 |         return $answer;
50 |     }
51 | 
52 |     /**
53 |      * @param ConnectorInterface $connector
54 |      * @param string             $from
55 |      * @param string             $privateActiveWif
56 |      * @param string             $to
57 |      * @param string             $amountWithAsset
58 |      * @param string             $memo
59 |      *
60 |      * @return mixed
61 |      * @throws \Exception
62 |      */
63 |     public static function doSynchronous(ConnectorInterface $connector, $privateActiveWif, $from, $to, $amountWithAsset, $memo)
64 |     {
65 |         $chainName = $connector->getPlatform();
66 |         /** @var CommandQueryData $tx */
67 |         $tx = Transaction::init($connector);
68 |         $tx->setParamByKey(
69 |             '0:operations:0',
70 |             [
71 |                 'transfer',
72 |                 [
73 |                     'from'   => $from,
74 |                     'to'     => $to,
75 |                     'amount' => $amountWithAsset,
76 |                     'memo'   => $memo
77 |                 ]
78 |             ]
79 |         );
80 | 
81 |         $command = new BroadcastTransactionSynchronousCommand($connector);
82 |         Transaction::sign($chainName, $tx, ['active' => $privateActiveWif]);
83 | 
84 |         $answer = $command->execute(
85 |             $tx
86 |         );
87 | 
88 |         return $answer;
89 |     }
90 | 
91 | 
92 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/OpVote.php:
--------------------------------------------------------------------------------
 1 | getPlatform();
28 |         /** @var CommandQueryData $tx */
29 |         $tx = Transaction::init($connector);
30 |         $tx->setParamByKey(
31 |             '0:operations:0',
32 |             [
33 |                 'vote',
34 |                 [
35 |                     'voter'    => $voter,
36 |                     'author'   => $author,
37 |                     'permlink' => $permlink,
38 |                     'weight'   => $weight
39 |                 ]
40 |             ]
41 |         );
42 | 
43 |         $command = new BroadcastTransactionCommand($connector);////        echo '
' . var_dump($commandQueryData->getParams(), $properties2) . '
'; die; //FIXME delete it
44 |         Transaction::sign($chainName, $tx, ['posting' => $publicWif]);
45 | 
46 |         $answer = $command->execute(
47 |             $tx
48 |         );
49 | 
50 |         return $answer;
51 |     }
52 | 
53 |     /**
54 |      * @param ConnectorInterface $connector
55 |      * @param string             $voter
56 |      * @param string             $publicWif
57 |      * @param string             $author
58 |      * @param string             $permlink
59 |      * @param integer            $weight
60 |      *
61 |      * @return array|object
62 |      * @throws \Exception
63 |      */
64 |     public static function doSynchronous(ConnectorInterface $connector, $voter, $publicWif, $author, $permlink, $weight)
65 |     {
66 |         $chainName = $connector->getPlatform();
67 |         /** @var CommandQueryData $tx */
68 |         $tx = Transaction::init($connector);
69 |         $tx->setParamByKey(
70 |             '0:operations:0',
71 |             [
72 |                 'vote',
73 |                 [
74 |                     'voter'    => $voter,
75 |                     'author'   => $author,
76 |                     'permlink' => $permlink,
77 |                     'weight'   => $weight
78 |                 ]
79 |             ]
80 |         );
81 | 
82 |         $command = new BroadcastTransactionSynchronousCommand($connector);////        echo '
' . var_dump($commandQueryData->getParams(), $properties2) . '
'; die; //FIXME delete it
83 |         Transaction::sign($chainName, $tx, ['posting' => $publicWif]);
84 | 
85 |         $answer = $command->execute(
86 |             $tx
87 |         );
88 | 
89 |         return $answer;
90 |     }
91 | 
92 | 
93 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/ChainOperationsWhaleshares.php:
--------------------------------------------------------------------------------
 1 |  0,
12 |         ChainOperations::OPERATION_COMMENT         => 1,
13 |         ChainOperations::OPERATION_COMMENT_OPTIONS => 19,
14 |         ChainOperations::OPERATION_TRANSFER        => 2,
15 |         ChainOperations::OPERATION_CUSTOM_JSON     => 18,
16 |         ChainOperations::OPERATION_WITNESS_UPDATE  => 9,
17 |     ];
18 | 
19 |     const FIELDS_TYPES = [
20 |         ChainOperations::OPERATION_VOTE            => [
21 |             'voter'    => OperationSerializer::TYPE_STRING,
22 |             'author'   => OperationSerializer::TYPE_STRING,
23 |             'permlink' => OperationSerializer::TYPE_STRING,
24 |             'weight'   => OperationSerializer::TYPE_INT16
25 |         ],
26 |         ChainOperations::OPERATION_COMMENT         => [
27 |             'parent_author'   => OperationSerializer::TYPE_STRING,
28 |             'parent_permlink' => OperationSerializer::TYPE_STRING,
29 |             'author'          => OperationSerializer::TYPE_STRING,
30 |             'permlink'        => OperationSerializer::TYPE_STRING,
31 |             'title'           => OperationSerializer::TYPE_STRING,
32 |             'body'            => OperationSerializer::TYPE_STRING,
33 |             'json_metadata'   => OperationSerializer::TYPE_STRING
34 |         ],
35 |         ChainOperations::OPERATION_COMMENT_OPTIONS => [
36 |             'author'                 => OperationSerializer::TYPE_STRING,
37 |             'permlink'               => OperationSerializer::TYPE_STRING,
38 |             'max_accepted_payout'    => OperationSerializer::TYPE_ASSET,
39 |             'percent_steem_dollars'  => OperationSerializer::TYPE_INT16,
40 |             'allow_votes'            => OperationSerializer::TYPE_BOOL,
41 |             'allow_curation_rewards' => OperationSerializer::TYPE_BOOL,
42 |             'extensions'             => OperationSerializer::TYPE_SET_EXTENSIONS
43 |         ],
44 |         ChainOperations::OPERATION_TRANSFER        => [
45 |             'from'   => OperationSerializer::TYPE_STRING,
46 |             'to'     => OperationSerializer::TYPE_STRING,
47 |             'amount' => OperationSerializer::TYPE_ASSET,
48 |             'memo'   => OperationSerializer::TYPE_STRING
49 |         ],
50 |         ChainOperations::OPERATION_CUSTOM_JSON     => [
51 |             'required_auths'         => OperationSerializer::TYPE_SET_STRING,
52 |             'required_posting_auths' => OperationSerializer::TYPE_SET_STRING,
53 |             'id'                     => OperationSerializer::TYPE_STRING,
54 |             'json'                   => OperationSerializer::TYPE_STRING
55 |         ],
56 |         ChainOperations::OPERATION_WITNESS_UPDATE     => [
57 |             'owner'             => OperationSerializer::TYPE_STRING,
58 |             'url'               => OperationSerializer::TYPE_STRING,
59 |             'block_signing_key' => OperationSerializer::TYPE_PUBLIC_KEY,
60 |             'props'             => OperationSerializer::TYPE_CHAIN_PROPERTIES,
61 |             'fee'               => OperationSerializer::TYPE_ASSET
62 |         ],
63 |         OperationSerializer::TYPE_CHAIN_PROPERTIES => [
64 |             'account_creation_fee' => OperationSerializer::TYPE_ASSET,
65 |             'maximum_block_size'   => OperationSerializer::TYPE_INT32
66 |         ]
67 |     ];
68 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/ChainOperationsHive.php:
--------------------------------------------------------------------------------
 1 |  0,
12 |         ChainOperations::OPERATION_COMMENT         => 1,
13 |         ChainOperations::OPERATION_COMMENT_OPTIONS => 19,
14 |         ChainOperations::OPERATION_TRANSFER        => 2,
15 |         ChainOperations::OPERATION_CUSTOM_JSON     => 18,
16 |         ChainOperations::OPERATION_WITNESS_UPDATE  => 11,
17 |     ];
18 | 
19 |     const FIELDS_TYPES = [
20 |         ChainOperations::OPERATION_VOTE            => [
21 |             'voter'    => OperationSerializer::TYPE_STRING,
22 |             'author'   => OperationSerializer::TYPE_STRING,
23 |             'permlink' => OperationSerializer::TYPE_STRING,
24 |             'weight'   => OperationSerializer::TYPE_INT16
25 |         ],
26 |         ChainOperations::OPERATION_COMMENT         => [
27 |             'parent_author'   => OperationSerializer::TYPE_STRING,
28 |             'parent_permlink' => OperationSerializer::TYPE_STRING,
29 |             'author'          => OperationSerializer::TYPE_STRING,
30 |             'permlink'        => OperationSerializer::TYPE_STRING,
31 |             'title'           => OperationSerializer::TYPE_STRING,
32 |             'body'            => OperationSerializer::TYPE_STRING,
33 |             'json_metadata'   => OperationSerializer::TYPE_STRING
34 |         ],
35 |         ChainOperations::OPERATION_COMMENT_OPTIONS => [
36 |             'author'                 => OperationSerializer::TYPE_STRING,
37 |             'permlink'               => OperationSerializer::TYPE_STRING,
38 |             'max_accepted_payout'    => OperationSerializer::TYPE_ASSET,
39 |             'percent_steem_dollars'  => OperationSerializer::TYPE_INT16,
40 |             'allow_votes'            => OperationSerializer::TYPE_BOOL,
41 |             'allow_curation_rewards' => OperationSerializer::TYPE_BOOL,
42 |             'extensions'             => OperationSerializer::TYPE_SET_EXTENSIONS
43 |         ],
44 |         ChainOperations::OPERATION_TRANSFER        => [
45 |             'from'   => OperationSerializer::TYPE_STRING,
46 |             'to'     => OperationSerializer::TYPE_STRING,
47 |             'amount' => OperationSerializer::TYPE_ASSET,
48 |             'memo'   => OperationSerializer::TYPE_STRING
49 |         ],
50 |         ChainOperations::OPERATION_CUSTOM_JSON     => [
51 |             'required_auths'         => OperationSerializer::TYPE_SET_STRING,
52 |             'required_posting_auths' => OperationSerializer::TYPE_SET_STRING,
53 |             'id'                     => OperationSerializer::TYPE_STRING,
54 |             'json'                   => OperationSerializer::TYPE_STRING
55 |         ],
56 |         ChainOperations::OPERATION_WITNESS_UPDATE     => [
57 |             'owner'             => OperationSerializer::TYPE_STRING,
58 |             'url'               => OperationSerializer::TYPE_STRING,
59 |             'block_signing_key' => OperationSerializer::TYPE_PUBLIC_KEY,
60 |             'props'             => OperationSerializer::TYPE_CHAIN_PROPERTIES,
61 |             'fee'               => OperationSerializer::TYPE_ASSET
62 |         ],
63 |         OperationSerializer::TYPE_CHAIN_PROPERTIES => [
64 |             'account_creation_fee' => OperationSerializer::TYPE_ASSET,
65 |             'maximum_block_size'   => OperationSerializer::TYPE_INT32,
66 |             'sbd_interest_rate'    => OperationSerializer::TYPE_INT16
67 |         ]
68 |     ];
69 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/ChainOperationsSteem.php:
--------------------------------------------------------------------------------
 1 |  0,
12 |         ChainOperations::OPERATION_COMMENT         => 1,
13 |         ChainOperations::OPERATION_COMMENT_OPTIONS => 19,
14 |         ChainOperations::OPERATION_TRANSFER        => 2,
15 |         ChainOperations::OPERATION_CUSTOM_JSON     => 18,
16 |         ChainOperations::OPERATION_WITNESS_UPDATE  => 11,
17 |     ];
18 | 
19 |     const FIELDS_TYPES = [
20 |         ChainOperations::OPERATION_VOTE            => [
21 |             'voter'    => OperationSerializer::TYPE_STRING,
22 |             'author'   => OperationSerializer::TYPE_STRING,
23 |             'permlink' => OperationSerializer::TYPE_STRING,
24 |             'weight'   => OperationSerializer::TYPE_INT16
25 |         ],
26 |         ChainOperations::OPERATION_COMMENT         => [
27 |             'parent_author'   => OperationSerializer::TYPE_STRING,
28 |             'parent_permlink' => OperationSerializer::TYPE_STRING,
29 |             'author'          => OperationSerializer::TYPE_STRING,
30 |             'permlink'        => OperationSerializer::TYPE_STRING,
31 |             'title'           => OperationSerializer::TYPE_STRING,
32 |             'body'            => OperationSerializer::TYPE_STRING,
33 |             'json_metadata'   => OperationSerializer::TYPE_STRING
34 |         ],
35 |         ChainOperations::OPERATION_COMMENT_OPTIONS => [
36 |             'author'                 => OperationSerializer::TYPE_STRING,
37 |             'permlink'               => OperationSerializer::TYPE_STRING,
38 |             'max_accepted_payout'    => OperationSerializer::TYPE_ASSET,
39 |             'percent_steem_dollars'  => OperationSerializer::TYPE_INT16,
40 |             'allow_votes'            => OperationSerializer::TYPE_BOOL,
41 |             'allow_curation_rewards' => OperationSerializer::TYPE_BOOL,
42 |             'extensions'             => OperationSerializer::TYPE_SET_EXTENSIONS
43 |         ],
44 |         ChainOperations::OPERATION_TRANSFER        => [
45 |             'from'   => OperationSerializer::TYPE_STRING,
46 |             'to'     => OperationSerializer::TYPE_STRING,
47 |             'amount' => OperationSerializer::TYPE_ASSET,
48 |             'memo'   => OperationSerializer::TYPE_STRING
49 |         ],
50 |         ChainOperations::OPERATION_CUSTOM_JSON     => [
51 |             'required_auths'         => OperationSerializer::TYPE_SET_STRING,
52 |             'required_posting_auths' => OperationSerializer::TYPE_SET_STRING,
53 |             'id'                     => OperationSerializer::TYPE_STRING,
54 |             'json'                   => OperationSerializer::TYPE_STRING
55 |         ],
56 |         ChainOperations::OPERATION_WITNESS_UPDATE     => [
57 |             'owner'             => OperationSerializer::TYPE_STRING,
58 |             'url'               => OperationSerializer::TYPE_STRING,
59 |             'block_signing_key' => OperationSerializer::TYPE_PUBLIC_KEY,
60 |             'props'             => OperationSerializer::TYPE_CHAIN_PROPERTIES,
61 |             'fee'               => OperationSerializer::TYPE_ASSET
62 |         ],
63 |         OperationSerializer::TYPE_CHAIN_PROPERTIES => [
64 |             'account_creation_fee' => OperationSerializer::TYPE_ASSET,
65 |             'maximum_block_size'   => OperationSerializer::TYPE_INT32,
66 |             'sbd_interest_rate'    => OperationSerializer::TYPE_INT16
67 |         ]
68 |     ];
69 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/OpComment.php:
--------------------------------------------------------------------------------
  1 | getPlatform();
 31 |         /** @var CommandQueryData $tx */
 32 |         $tx = Transaction::init($connector);
 33 |         $tx->setParamByKey(
 34 |             '0:operations:0',
 35 |             [
 36 |                 'comment',
 37 |                 [
 38 |                     'parent_author'   => $parentAuthor,
 39 |                     'parent_permlink' => $parentPermlink,
 40 |                     'author'          => $author,
 41 |                     'permlink'        => $permlink,
 42 |                     'title'           => $title,
 43 |                     'body'            => $body,
 44 |                     'json_metadata'   => $jsonMetadata
 45 |                 ]
 46 |             ]
 47 |         );
 48 | 
 49 |         $command = new BroadcastTransactionCommand($connector);
 50 |         Transaction::sign($chainName, $tx, ['posting' => $privatePostingWif]);
 51 | 
 52 |         $answer = $command->execute(
 53 |             $tx
 54 |         );
 55 | 
 56 |         return $answer;
 57 |     }
 58 | 
 59 |     /**
 60 |      * @param ConnectorInterface $connector
 61 |      * @param string             $privatePostingWif
 62 |      * @param string             $author
 63 |      * @param string             $permlink
 64 |      * @param string             $title
 65 |      * @param string             $body
 66 |      * @param string             $jsonMetadata
 67 |      * @param string             $parentPermlink
 68 |      * @param string             $parentAuthor
 69 |      *
 70 |      * @return mixed
 71 |      * @throws \Exception
 72 |      */
 73 |     public static function doSynchronous(ConnectorInterface $connector, $privatePostingWif, $author, $permlink, $title, $body, $jsonMetadata, $parentPermlink, $parentAuthor = '')
 74 |     {
 75 |         $chainName = $connector->getPlatform();
 76 |         /** @var CommandQueryData $tx */
 77 |         $tx = Transaction::init($connector);
 78 |         $tx->setParamByKey(
 79 |             '0:operations:0',
 80 |             [
 81 |                 'comment',
 82 |                 [
 83 |                     'parent_author'   => $parentAuthor,
 84 |                     'parent_permlink' => $parentPermlink,
 85 |                     'author'          => $author,
 86 |                     'permlink'        => $permlink,
 87 |                     'title'           => $title,
 88 |                     'body'            => $body,
 89 |                     'json_metadata'   => $jsonMetadata
 90 |                 ]
 91 |             ]
 92 |         );
 93 | 
 94 |         $command = new BroadcastTransactionSynchronousCommand($connector);
 95 |         Transaction::sign($chainName, $tx, ['posting' => $privatePostingWif]);
 96 | 
 97 |         $answer = $command->execute(
 98 |             $tx
 99 |         );
100 | 
101 |         return $answer;
102 |     }
103 | 
104 | 
105 | }


--------------------------------------------------------------------------------
/Tools/Transliterator.php:
--------------------------------------------------------------------------------
  1 |  [
 14 |             'ые' => 'yie',
 15 |             'щ' => 'shch',
 16 |             'ш' => 'sh',
 17 |             'ч' => 'ch',
 18 |             'ц' => 'cz',
 19 |             'й' => 'ij',
 20 |             'ё' => 'yo',
 21 |             'э' => 'ye',
 22 |             'ю' => 'yu',
 23 |             'я' => 'ya',
 24 |             'х' => 'kh',
 25 |             'ж' => 'zh',
 26 |             'а' => 'a',
 27 |             'б' => 'b',
 28 |             'в' => 'v',
 29 |             'г' => 'g',
 30 |             'д' => 'd',
 31 |             'е' => 'e',
 32 |             'з' => 'z',
 33 |             'и' => 'i',
 34 |             'к' => 'k',
 35 |             'л' => 'l',
 36 |             'м' => 'm',
 37 |             'н' => 'n',
 38 |             'о' => 'o',
 39 |             'п' => 'p',
 40 |             'р' => 'r',
 41 |             'с' => 's',
 42 |             'т' => 't',
 43 |             'у' => 'u',
 44 |             'ф' => 'f',
 45 |             'ъ' => 'xx',
 46 |             'ы' => 'y',
 47 |             'ь' => 'x',
 48 |             'ґ' => 'g',
 49 |             'є' => 'e',
 50 |             'і' => 'i',
 51 |             'ї' => 'i'
 52 |         ]
 53 |     ];
 54 | 
 55 |     /**
 56 |      * @param string $str
 57 |      * @param string $lang
 58 |      * @param bool $reverse
 59 |      * @return string
 60 |      */
 61 |     private static function transliterate($str, $lang, $reverse = false)
 62 |     {
 63 |         foreach (self::$langs[$lang] as $lFrom => $lTo) {
 64 |             if ($reverse) {
 65 |                 $from = $lTo;
 66 |                 $to = $lFrom;
 67 |             } else {
 68 |                 $from = $lFrom;
 69 |                 $to = $lTo;
 70 |             }
 71 |             $str = str_replace($from, $to, $str);
 72 |             $str = str_replace(mb_strtoupper($from, 'utf-8'), mb_strtoupper($to, 'utf-8'), $str);
 73 |         }
 74 | 
 75 |         return $str;
 76 |     }
 77 | 
 78 |     /**
 79 |      * @param string $str
 80 |      * @param string $pattern
 81 |      * @param array $original
 82 |      * @return string
 83 |      */
 84 |     private static function restoreTechnicalData($str, $pattern, $original)
 85 |     {
 86 |         preg_match_all($pattern, $str, $damaged, PREG_PATTERN_ORDER);
 87 | 
 88 |         foreach ($damaged[0] as $key => $el) {
 89 |             $str = str_replace($el, $original[0][$key], $str);
 90 |         }
 91 | 
 92 |         return $str;
 93 |     }
 94 | 
 95 |     /**
 96 |      * @param string $str
 97 |      * @param string $fromLang
 98 |      * @return string
 99 |      */
100 |     public static function encode($str, $fromLang)
101 |     {
102 |         if (!$str) {
103 |             return $str;
104 |         }
105 | 
106 |         $s = '/[^[\]]+(?=])/';
107 |         $t = '/<(.|\n)*?>/';
108 |         preg_match_all($s, $str, $orig, PREG_PATTERN_ORDER);
109 |         preg_match_all($t, $str, $tags, PREG_PATTERN_ORDER);
110 | 
111 |         $str = self::transliterate($str, $fromLang);
112 | 
113 |         if (!empty($orig[0])) {
114 |             self::restoreTechnicalData($str, $s, $orig);
115 |         }
116 | 
117 |         if (!empty($tags[0])) {
118 |             self::restoreTechnicalData($str, $t, $tags);
119 |         }
120 | 
121 |         return $str;
122 |     }
123 | 
124 |     /**
125 |      * @param string $str
126 |      * @param string $toLang
127 |      * @return string
128 |      */
129 |     public static function decode($str, $toLang)
130 |     {
131 |         if (!$str || substr($str, 0, 4) !== ($toLang . '--')) {
132 |             return $str;
133 |         }
134 |         $str = substr($str, 4);
135 | 
136 |         $s = '/[^[\]]+(?=])/';
137 |         $t = '/<(.|\n)*?>/';
138 |         preg_match_all($s, $str, $orig, PREG_PATTERN_ORDER);
139 |         preg_match_all($t, $str, $tags, PREG_PATTERN_ORDER);
140 | 
141 |         $str = self::transliterate($str, $toLang, true);
142 | 
143 |         if (!empty($orig[0])) {
144 |             self::restoreTechnicalData($str, $s, $orig);
145 |         }
146 | 
147 |         if (!empty($tags[0])) {
148 |             self::restoreTechnicalData($str, $t, $tags);
149 |         }
150 | 
151 |         return $str;
152 |     }
153 | }


--------------------------------------------------------------------------------
/Tools/Bandwidth.php:
--------------------------------------------------------------------------------
  1 |  int, 'available' => int] in bytes. For upvotes and comments 1:1, for transfers 10:1
 70 |      * @throws \Exception
 71 |      */
 72 |     public static function getBandwidthByAccountName($accountName, $type, ConnectorInterface $connector)
 73 |     {
 74 |         $commands = new Commands($connector);
 75 |         $commands->get_dynamic_global_properties();
 76 |         $commandQueryData = new CommandQueryData();
 77 |         $answer = $commands->execute(
 78 |             $commandQueryData
 79 |         );
 80 |         if (!isset($answer['result'])) {
 81 |             throw new \Exception('wrong api answer for get_dynamic_global_properties');
 82 |         }
 83 |         $globalProp = $answer['result'];
 84 |         $maxVirtualBandwidth = $globalProp['max_virtual_bandwidth'];
 85 |         $totalVestingShares = str_replace(' VESTS', '', $globalProp['total_vesting_shares']);
 86 |         $totalVestingShares = substr($totalVestingShares, 0, strpos($totalVestingShares, '.'));
 87 | 
 88 |         $commands->get_accounts();
 89 |         $commandQueryData->setParams(
 90 |             [
 91 |                 0 => [$accountName]
 92 |             ]
 93 |         );
 94 |         $answer = $commands->execute(
 95 |             $commandQueryData
 96 |         );
 97 |         if (!isset($answer['result'])) {
 98 |             throw new \Exception('wrong api answer for get_accounts');
 99 |         }
100 |         $account = $answer['result'][0];
101 |         $accountVShares = str_replace(' VESTS', '', $account['vesting_shares']);
102 |         $accountVShares = substr($accountVShares, 0, strpos($accountVShares, '.'));
103 | 
104 |         $paramName = 'average_bandwidth';
105 |         if ($type === 'market') {
106 |             $paramName = 'average_market_bandwidth';
107 |         }
108 |         $accountAverageBandwidth = $account[$paramName];
109 | 
110 |         return [
111 |             'used'      => (int)self::getAccountUsedBandwidth($accountAverageBandwidth),
112 |             'available' => (int)self::getAccountAvailableBandwidth($accountVShares, $totalVestingShares, $maxVirtualBandwidth)
113 |         ];
114 |     }
115 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/ChainOperationsGolos.php:
--------------------------------------------------------------------------------
 1 |  0,
12 |         ChainOperations::OPERATION_COMMENT         => 1,//STEEM/GOLOS/whaleshares
13 |         ChainOperations::OPERATION_COMMENT_OPTIONS => 19,
14 |         ChainOperations::OPERATION_TRANSFER        => 2,
15 |         ChainOperations::OPERATION_CUSTOM_JSON     => 18,
16 |         ChainOperations::OPERATION_WITNESS_UPDATE  => 11,
17 |         ChainOperations::OPERATION_DONATE          => 54,
18 |     ];
19 | 
20 |     const FIELDS_TYPES = [
21 |         ChainOperations::OPERATION_VOTE            => [
22 |             'voter'    => OperationSerializer::TYPE_STRING,
23 |             'author'   => OperationSerializer::TYPE_STRING,
24 |             'permlink' => OperationSerializer::TYPE_STRING,
25 |             'weight'   => OperationSerializer::TYPE_INT16
26 |         ],
27 |         ChainOperations::OPERATION_COMMENT         => [
28 |             'parent_author'   => OperationSerializer::TYPE_STRING,
29 |             'parent_permlink' => OperationSerializer::TYPE_STRING,
30 |             'author'          => OperationSerializer::TYPE_STRING,
31 |             'permlink'        => OperationSerializer::TYPE_STRING,
32 |             'title'           => OperationSerializer::TYPE_STRING,
33 |             'body'            => OperationSerializer::TYPE_STRING,
34 |             'json_metadata'   => OperationSerializer::TYPE_STRING
35 |         ],
36 |         ChainOperations::OPERATION_COMMENT_OPTIONS => [
37 |             'author'                 => OperationSerializer::TYPE_STRING,
38 |             'permlink'               => OperationSerializer::TYPE_STRING,
39 |             'max_accepted_payout'    => OperationSerializer::TYPE_ASSET,
40 |             'percent_steem_dollars'  => OperationSerializer::TYPE_INT16,
41 |             'allow_votes'            => OperationSerializer::TYPE_BOOL,
42 |             'allow_curation_rewards' => OperationSerializer::TYPE_BOOL,
43 |             'extensions'             => OperationSerializer::TYPE_SET_EXTENSIONS
44 |         ],
45 |         ChainOperations::OPERATION_TRANSFER        => [
46 |             'from'   => OperationSerializer::TYPE_STRING,
47 |             'to'     => OperationSerializer::TYPE_STRING,
48 |             'amount' => OperationSerializer::TYPE_ASSET,
49 |             'memo'   => OperationSerializer::TYPE_STRING
50 |         ],
51 |         ChainOperations::OPERATION_CUSTOM_JSON     => [
52 |             'required_auths'         => OperationSerializer::TYPE_SET_STRING,
53 |             'required_posting_auths' => OperationSerializer::TYPE_SET_STRING,
54 |             'id'                     => OperationSerializer::TYPE_STRING,
55 |             'json'                   => OperationSerializer::TYPE_STRING
56 |         ],
57 |         ChainOperations::OPERATION_WITNESS_UPDATE  => [
58 |             'owner'             => OperationSerializer::TYPE_STRING,
59 |             'url'               => OperationSerializer::TYPE_STRING,
60 |             'block_signing_key' => OperationSerializer::TYPE_PUBLIC_KEY,
61 |             'props'             => OperationSerializer::TYPE_CHAIN_PROPERTIES,
62 |             'fee'               => OperationSerializer::TYPE_ASSET
63 |         ],
64 |         OperationSerializer::TYPE_CHAIN_PROPERTIES => [
65 |             'account_creation_fee' => OperationSerializer::TYPE_ASSET,
66 |             'maximum_block_size'   => OperationSerializer::TYPE_INT32,
67 |             'sbd_interest_rate'    => OperationSerializer::TYPE_INT16
68 |         ],
69 |         ChainOperations::OPERATION_DONATE  => [
70 |             'from'             => OperationSerializer::TYPE_STRING,
71 |             'to'               => OperationSerializer::TYPE_STRING,
72 |             'amount'           => OperationSerializer::TYPE_ASSET,
73 |             'memo'             => OperationSerializer::TYPE_DONATE_MEMO,
74 |             'extensions'       => OperationSerializer::TYPE_SET_FUTURE_EXTENSIONS
75 |         ],
76 |         OperationSerializer::TYPE_DONATE_MEMO => [
77 |             'app'     => OperationSerializer::TYPE_STRING,
78 |             'version' => OperationSerializer::TYPE_INT16,
79 |             'target'  => OperationSerializer::TYPE_VARIANT_OBJECT,
80 |             'comment' => OperationSerializer::TYPE_OPTIONAL_STRING
81 |         ],
82 |         OperationSerializer::TYPE_FUTURE_EXTENSIONS => [
83 |         ]
84 |     ];
85 | }


--------------------------------------------------------------------------------
/Tools/ChainOperations/OpContent.php:
--------------------------------------------------------------------------------
  1 |  'denis-skripnik', 'weight' => 10000]], weight is max 10000 as 100%
 24 |      * @param string             $parentPermlink
 25 |      * @param string             $parentAuthor
 26 |      *
 27 |      * @return mixed
 28 |      * @throws \Exception
 29 |      */
 30 |     public static function do(ConnectorInterface $connector, $privatePostingWif, $author, $permlink, $title, $body, $jsonMetadata, $curationPercent = 0, $beneficiaries = [], $parentPermlink, $parentAuthor = '')
 31 |     {
 32 |         $chainName = $connector->getPlatform();
 33 | 
 34 |         /** @var CommandQueryData $tx */
 35 |         $tx = Transaction::init($connector);
 36 |         $tx->setParamByKey(
 37 |             '0:operations:0',
 38 |             [
 39 |                 ChainOperations::OPERATION_CONTENT,
 40 |                 [
 41 |                     'parent_author'   => $parentAuthor,
 42 |                     'parent_permlink' => $parentPermlink,
 43 |                     'author'          => $author,
 44 |                     'permlink'        => $permlink,
 45 |                     'title'           => $title,
 46 |                     'body'            => $body,
 47 |                     'curation_percent'=> $curationPercent,
 48 |                     'json_metadata'   => $jsonMetadata,
 49 |                     'extensions'      => empty($beneficiaries) ? [] : [[0, ["beneficiaries" => $beneficiaries]]]
 50 |                 ]
 51 |             ]
 52 |         );
 53 | 
 54 |         $command = new BroadcastTransactionCommand($connector);
 55 |         Transaction::sign($chainName, $tx, ['posting' => $privatePostingWif]);
 56 | 
 57 |         $answer = $command->execute(
 58 |             $tx
 59 |         );
 60 | 
 61 |         return $answer;
 62 |     }
 63 | 
 64 |     /**
 65 |      * @param ConnectorInterface $connector
 66 |      * @param string             $privatePostingWif
 67 |      * @param string             $author
 68 |      * @param string             $permlink
 69 |      * @param string             $title
 70 |      * @param string             $body
 71 |      * @param string             $jsonMetadata
 72 |      * @param integer            $curationPercent 10000 as 100%
 73 |      * @param array[]            $beneficiaries as array [['account' => 'denis-skripnik', 'weight' => 10000]], weight is max 10000 as 100%
 74 |      * @param string             $parentPermlink
 75 |      * @param string             $parentAuthor
 76 |      *
 77 |      * @return mixed
 78 |      * @throws \Exception
 79 |      */
 80 |     public static function doSynchronous(ConnectorInterface $connector, $privatePostingWif, $author, $permlink, $title, $body, $jsonMetadata, $curationPercent = 0, $beneficiaries = [], $parentPermlink, $parentAuthor = '')
 81 |     {
 82 |         $chainName = $connector->getPlatform();
 83 |         /** @var CommandQueryData $tx */
 84 |         $tx = Transaction::init($connector);
 85 |         $tx->setParamByKey(
 86 |             '0:operations:0',
 87 |             [
 88 |                 ChainOperations::OPERATION_CONTENT,
 89 |                 [
 90 |                     'parent_author'   => $parentAuthor,
 91 |                     'parent_permlink' => $parentPermlink,
 92 |                     'author'          => $author,
 93 |                     'permlink'        => $permlink,
 94 |                     'title'           => $title,
 95 |                     'body'            => $body,
 96 |                     'curation_percent'=> $curationPercent,
 97 |                     'json_metadata'   => $jsonMetadata,
 98 |                     'extensions'      => empty($beneficiaries) ? [] : [[0, ["beneficiaries" => $beneficiaries]]]
 99 |                 ]
100 |             ]
101 |         );
102 | 
103 |         $command = new BroadcastTransactionSynchronousCommand($connector);
104 |         Transaction::sign($chainName, $tx, ['posting' => $privatePostingWif]);
105 | 
106 |         $answer = $command->execute(
107 |             $tx
108 |         );
109 | 
110 |         return $answer;
111 |     }
112 | 
113 | 
114 | }


--------------------------------------------------------------------------------
/Commands/Commands.php:
--------------------------------------------------------------------------------
  1 | connector = $connector;
 52 |         return $this;
 53 |     }
 54 | 
 55 |     /**
 56 |      * @param CommandQueryDataInterface $commandQueryData
 57 |      * @param string                    $answerFormat
 58 |      * @param string                    $getElementWithKey If you want to get only certain element from answer.
 59 |      *                                                     Example: 'key:123:qwe' => $array['key']['123']['qwe'] or
 60 |      *                                                     $object->key->123->qwe
 61 |      *
 62 |      * @return array|object
 63 |      */
 64 |     public function execute(
 65 |         CommandQueryDataInterface $commandQueryData,
 66 |         $getElementWithKey = null,
 67 |         $answerFormat = ConnectorInterface::ANSWER_FORMAT_ARRAY
 68 |     )
 69 |     {
 70 |         /** @var CommandQueryData $commandQueryData */
 71 |         $params = $commandQueryData->prepareData($this->getQueryDataMap());
 72 | 
 73 |         $answer = $this->doRequest($params, $answerFormat);
 74 | 
 75 |         $defaultValue = $answerFormat === ConnectorInterface::ANSWER_FORMAT_ARRAY ? [] : ((object)[]);
 76 | 
 77 |         return $this->getElementByKey($answer, $getElementWithKey, $defaultValue);
 78 |     }
 79 | 
 80 | 
 81 |     /**
 82 |      * @return array|mixed
 83 |      */
 84 |     public function getQueryDataMap()
 85 |     {
 86 |         return self::$queryDataMap[$this->connector->getPlatform()][$this->method]['fields'];
 87 |     }
 88 | 
 89 |     /**
 90 |      * @param array  $params
 91 |      * @param string $answerFormat
 92 |      *
 93 |      * @return array|object
 94 |      */
 95 |     protected function doRequest($params, $answerFormat = ConnectorInterface::ANSWER_FORMAT_ARRAY)
 96 |     {
 97 |         $data = [
 98 |             'method' => $this->method,
 99 |             'params' => $params
100 |         ];
101 | 
102 |         return $this->connector->doRequest($this->apiName, $data, $answerFormat);
103 |     }
104 | 
105 | 
106 |     /**
107 |      * get all values or vulue by key
108 |      *
109 |      * Example: 'key:123:qwe' => $array['key']['123']['qwe'] or $object->key->123->qwe
110 |      *
111 |      * @param null|string  $getKey
112 |      * @param null|mixed   $default
113 |      * @param array|object $array
114 |      *
115 |      * @return mixed
116 |      */
117 |     protected function getElementByKey($array, $getKey = null, $default = null)
118 |     {
119 |         $data = $array;
120 |         if ($getKey) {
121 |             $keyParts = explode(':', $getKey);
122 |             foreach ($keyParts as $key) {
123 |                 if (is_array($data) && isset($data[$key])) {
124 |                     $data = $data[$key];
125 |                 } elseif (is_object($data) && isset($data->$key)) {
126 |                     $data = $data->$key;
127 |                 } else {
128 |                     $data = null;
129 |                     break;
130 |                 }
131 |             }
132 |         }
133 | 
134 |         if ($data === null) {
135 |             $data = $default;
136 |         }
137 | 
138 |         return $data;
139 |     }
140 | 
141 |     /**
142 |      * @param $name
143 |      * @param $params
144 |      *
145 |      * @return $this
146 |      * @throws \Exception
147 |      */
148 |     public function __call($name, $params)
149 |     {
150 |         $platform = $this->connector->getPlatform();
151 | 
152 |         if (!isset(self::$queryDataMap[$platform])) {
153 |             if ($platform === ConnectorInterface::PLATFORM_GOLOS) {
154 |                 $api = GolosApiMethods::$map;
155 |             } elseif ($platform === ConnectorInterface::PLATFORM_STEEMIT) {
156 |                 $api = SteemitApiMethods::$map;
157 |             } elseif ($platform === ConnectorInterface::PLATFORM_HIVE) {
158 |                 $api = HiveApiMethods::$map;
159 |             } elseif ($platform === ConnectorInterface::PLATFORM_VIZ) {
160 |                 $api = VizApiMethods::$map;
161 |             } elseif ($platform === ConnectorInterface::PLATFORM_WHALESHARES) {
162 |                 $api = WhalesharesApiMethods::$map;
163 |             } else {
164 |                 throw new \Exception('There is no api');
165 |             }
166 |             self::$queryDataMap[$platform] = $api;
167 |         }
168 | 
169 |         if (!isset(self::$queryDataMap[$platform][$name])) {
170 |             throw new \Exception('There is no information about command:' . $name . '. Please create your own class for that command');
171 |         }
172 | 
173 |         $this->method = $name;
174 |         $this->apiName = self::$queryDataMap[$platform][$this->method]['apiName'];
175 | 
176 |         return $this;
177 |     }
178 | }


--------------------------------------------------------------------------------
/Commands/CommandQueryData.php:
--------------------------------------------------------------------------------
  1 | params;
 18 |     }
 19 | 
 20 |     /**
 21 |      * @param $params
 22 |      */
 23 |     public function setParams($params)
 24 |     {
 25 |         $this->params = $params;
 26 |     }
 27 | 
 28 |     /**
 29 |      * set value in params by key
 30 |      *
 31 |      * $setKey example: 'key:1:one_more_key' => $params['key'][1]['one_more_key']
 32 |      * $setKey example: 'key:a1' => $params['key']['a1']
 33 |      *
 34 |      * @param string $setKey
 35 |      * @param mixed $setVal
 36 |      */
 37 |     public function setParamByKey($setKey, $setVal)
 38 |     {
 39 |         $this->params = $this->setArrayElementByKey($this->params, $setKey, $setVal);
 40 |     }
 41 | 
 42 |     /**
 43 |      * @param array $map
 44 |      * @return array
 45 |      */
 46 |     public function prepareData($map)
 47 |     {
 48 |         $queryData = [];
 49 |         foreach ($map as $route => $rules) {
 50 |             $queryData = $this->prepareQueryDataForRoute($queryData, $route, $rules);
 51 |         }
 52 | 
 53 |         return $queryData;
 54 |     }
 55 | 
 56 | 
 57 |     /**
 58 |      * @param array $data
 59 |      * @param string $fullRoute
 60 |      * @param array $rules
 61 |      *
 62 |      * @return mixed
 63 |      *
 64 |      * @throws CommandQueryDataException
 65 |      */
 66 |     protected function prepareQueryDataForRoute($data, $fullRoute, $rules = [])
 67 |     {
 68 |         $errors = [];
 69 |         $routeParts = explode(':', $fullRoute);
 70 |         $values = $this->getParamsListByKey($this->params, $routeParts);
 71 | 
 72 |         foreach ($values as $route => $value) {
 73 |             foreach ($rules as $rule) {
 74 |                 if (!$this->validate($value, $rule)) {
 75 |                     $errors[] = 'Validation rule \'' . $rule . '\' was failed for route \'' . $route . '\' with value ' . $value . ';';
 76 |                 }
 77 |             }
 78 |             if ($value !== null) {
 79 |                 $data = $this->setArrayElementByKey($data, $route, $value);
 80 |             }
 81 |         }
 82 | 
 83 |         if (!empty($errors)) {
 84 |             throw new CommandQueryDataException(implode(PHP_EOL, $errors));
 85 |         }
 86 | 
 87 |         return $data;
 88 |     }
 89 | 
 90 | 
 91 |     /**
 92 |      * @param mixed $value
 93 |      * @param string $rule
 94 |      * @return bool
 95 |      */
 96 |     protected function validate($value, $rule)
 97 |     {
 98 |         if ($rule === 'required') {
 99 |             return $value === null ? false : true;
100 |         } elseif ($rule === 'bool') {
101 |             return  $value !== null && is_bool($value);
102 |         } elseif ($rule === 'array') {
103 |             return $value !== null && is_array($value);
104 |         } elseif ($rule === 'string') {
105 |             return $value !== null && is_string($value);
106 |         } elseif ($rule === 'integer') {
107 |             return $value !== null && is_int($value);
108 |         } elseif ($rule === 'nullOrBool') {
109 |             return $value === null || is_bool($value);
110 |         } elseif ($rule === 'nullOrArray') {
111 |             return $value === null || is_array($value);
112 |         } elseif ($rule === 'nullOrString') {
113 |             return $value === null || is_string($value);
114 |         } elseif ($rule === 'nullOrInteger') {
115 |             return $value === null || is_int($value);
116 |         }
117 |     }
118 | 
119 | 
120 |     /**
121 |      * @param array $params
122 |      * @param array $routeParts
123 |      * @return array
124 |      */
125 |     protected function getParamsListByKey($params, $routeParts = [])
126 |     {
127 |         $values = [];
128 |         if (empty($routeParts)) {
129 |             $values = $params;
130 |         } else {
131 |             $currentKeyPart = array_shift($routeParts);
132 |             if (
133 |                 is_numeric($currentKeyPart)
134 |                 && (string)((integer)$currentKeyPart) === $currentKeyPart
135 |             ) {
136 |                 $currentKeyPart = (integer)$currentKeyPart;
137 |             }
138 |             if (isset($params[$currentKeyPart])) {
139 |                 $tmp = $this->getParamsListByKey($params[$currentKeyPart], $routeParts);
140 |                 if (is_array($tmp) && !empty($routeParts)) {
141 |                     foreach ($tmp as $valueKey => $value) {
142 |                         $values[$currentKeyPart . ':' . $valueKey] = $value;
143 |                     }
144 |                 } else {
145 |                     $values[$currentKeyPart] = $tmp;
146 |                 }
147 |             } elseif (is_array($params) && $currentKeyPart === '*') {
148 |                 foreach ($params as $paramKey => $param) {
149 |                     $tmp = $this->getParamsListByKey($param, $routeParts);
150 |                     if (is_array($tmp)) {
151 |                         foreach ($this->getParamsListByKey($param, $routeParts) as $valueKey => $value) {
152 |                             $values[$paramKey . ':' . $valueKey] = $value;
153 |                         }
154 |                     } else {
155 |                         $values[$paramKey] = $tmp;
156 |                     }
157 |                 }
158 |             } else {
159 |                 $values[implode(':', array_merge([$currentKeyPart], $routeParts))] = null;
160 |             }
161 |         }
162 | 
163 |         return $values;
164 |     }
165 | 
166 | 
167 |     /**
168 |      * set value in array by key
169 |      *
170 |      * $setKey example: 'key:123:array' => $_SESSION['key'][123]['array']
171 |      *
172 |      * @param array $array
173 |      * @param string $setKey
174 |      * @param mixed  $setVal
175 |      *
176 |      * @return array
177 |      */
178 |     protected function setArrayElementByKey($array, $setKey, $setVal)
179 |     {
180 |         $link = &$array;
181 |         $keyParts = explode(':', $setKey);
182 |         foreach ($keyParts as $key) {
183 |             if (
184 |                 is_numeric($key)
185 |                 && (string)((integer)$key) === $key
186 |             ) {
187 |                 $key = (integer)$key;
188 |             }
189 |             if (!isset($link[$key])) {
190 |                 $link[$key] = [];
191 |             }
192 |             $link = &$link[$key];
193 |         }
194 |         $link = $setVal;
195 | 
196 |         return $array;
197 |     }
198 | }


--------------------------------------------------------------------------------
/Commands/WhalesharesApiMethods.php:
--------------------------------------------------------------------------------
  1 |  [ 'apiName' => 'api_name', 'fields'=>['массив с полями из команды']]];
 11 |      *
 12 |      * @var array
 13 |      */
 14 |     public static $map = [
 15 |         'get_block'                             => [
 16 |             'apiName' => 'database_api',
 17 |             'fields'  => [
 18 |                 '0' => ['integer'], //block_id
 19 |             ]
 20 |         ],
 21 |         'get_accounts'                          => [
 22 |             'apiName' => 'database_api',
 23 |             'fields'  => [
 24 |                 '0' => ['array'], //authors
 25 |             ]
 26 |         ],
 27 |         'get_account_count'                     => [
 28 |             'apiName' => 'database_api',
 29 |             'fields'  => []
 30 |         ],
 31 |         'get_account_history'                   => [
 32 |             'apiName' => 'database_api',
 33 |             'fields'  => [
 34 |                 '0' => ['string'], //author
 35 |                 '1' => ['integer'], //from
 36 |                 '2' => ['integer'], //limit max 2000
 37 |             ]
 38 |         ],
 39 |         'get_account_votes'                     => [
 40 |             'apiName' => 'database_api',
 41 |             'fields'  => [
 42 |                 '0' => ['string'] //account name
 43 |             ]
 44 |         ],
 45 |         'get_active_votes'                      => [
 46 |             'apiName' => 'database_api',
 47 |             'fields'  => [
 48 |                 '0' => ['string'], //author
 49 |                 '1' => ['string'] //permlink
 50 |             ]
 51 |         ],
 52 |         'get_active_witnesses'                  => [
 53 |             'apiName' => 'database_api',
 54 |             'fields'  => [
 55 |             ]
 56 |         ],
 57 |         'get_block_header'                      => [
 58 |             'apiName' => 'database_api',
 59 |             'fields'  => [
 60 |                 '0' => ['integer'], //block_id
 61 |             ]
 62 |         ],
 63 |         'get_chain_properties'         => [
 64 |             'apiName' => 'database_api',
 65 |             'fields'  => [
 66 |             ]
 67 |         ],
 68 |         'get_config'                   => [
 69 |             'apiName' => 'database_api',
 70 |             'fields'  => [
 71 |             ]
 72 |         ],
 73 |         'get_content'                           => [
 74 |             'apiName' => 'database_api',
 75 |             'fields'  => [
 76 |                 '0' => ['string'], //author
 77 |                 '1' => ['string'] //permlink
 78 |             ]
 79 |         ],
 80 |         'get_content_replies'                   => [
 81 |             'apiName' => 'database_api',
 82 |             'fields'  => [
 83 |                 '0' => ['string'], //author
 84 |                 '1' => ['string'] //permlink
 85 |             ]
 86 |         ],
 87 |         'get_discussions_by_author_before_date' => [
 88 |             'apiName' => 'database_api',
 89 |             'fields'  => [
 90 |                 '0' => ['string'], //'author',
 91 |                 '1' => ['string'], //'start_permlink' for pagination,
 92 |                 '2' => ['string'], //'before_date'
 93 |                 '3' => ['integer'] //'limit'
 94 |             ]
 95 |         ],
 96 |         'get_discussions_by_blog'               => [
 97 |             'apiName' => 'database_api',
 98 |             'fields'  => [
 99 |                 '*:tag'            => ['string'], //'author',
100 |                 '*:limit'          => ['integer'], //'limit'
101 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
102 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
103 |             ]
104 |         ],
105 |         'get_discussions_by_comments'               => [
106 |             'apiName' => 'database_api',
107 |             'fields'  => [
108 |                 '*:limit'          => ['integer'], //'limit'
109 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
110 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
111 |             ]
112 |         ],
113 |         'get_discussions_by_created'            => [
114 |             'apiName' => 'database_api',
115 |             'fields'  => [
116 |                 '*:tag'            => ['nullOrString'], //'author',
117 |                 '*:limit'          => ['integer'], //'limit'
118 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
119 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
120 |             ],
121 |         ],
122 |         'get_discussions_by_feed'               => [
123 |             'apiName' => 'database_api',
124 |             'fields'  => [
125 |                 '*:tag'            => ['string'], //'author',
126 |                 '*:limit'          => ['integer'], //'limit'
127 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
128 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
129 |             ]
130 |         ],
131 |         'get_discussions_by_trending'           => [
132 |             'apiName' => 'database_api',
133 |             'fields'  => [
134 |                 '*:tag'            => ['nullOrString'], //'author',
135 |                 '*:limit'          => ['integer'], //'limit'
136 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
137 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
138 |             ]
139 |         ],
140 |         'get_dynamic_global_properties'         => [
141 |             'apiName' => 'database_api',
142 |             'fields'  => [
143 |             ]
144 |         ],
145 |         'get_follow_count'                         => [
146 |             'apiName' => 'follow_api',
147 |             'fields'  => [
148 |                 '0' => ['string'], //author
149 |             ]
150 |         ],
151 |         'get_ops_in_block'                      => [
152 |             'apiName' => 'database_api',
153 |             'fields'  => [
154 |                 '0' => ['integer'], //blockNum
155 |                 '1' => ['bool'], //onlyVirtual
156 |             ]
157 |         ],
158 |         'get_transaction'                      => [
159 |             'apiName' => 'database_api',
160 |             'fields'  => [
161 |                 '0' => ['string'], //trxId
162 |             ]
163 |         ],
164 |         'get_reward_fund'                          => [
165 |             'apiName' => 'database_api',
166 |             'fields'  => [
167 |                 '0' => ['string'], //post or comments
168 |             ]
169 |         ],
170 |         'get_trending_categories'               => [
171 |             'apiName' => 'database_api',
172 |             'fields'  => [
173 |                 '0' => ['nullOrString'], //after
174 |                 '1' => ['integer'], //permlink
175 |             ]
176 |         ],
177 |         'get_trending_tags'               => [
178 |             'apiName' => 'database_api',
179 |             'fields'  => [
180 |                 '0' => ['nullOrString'], //after
181 |                 '1' => ['integer'], //permlink
182 |             ]
183 |         ],
184 |         'get_witnesses_by_vote'                 => [
185 |             'apiName' => 'database_api',
186 |             'fields'  => [
187 |                 '0' => ['string'], //from accountName, can be empty string ''
188 |                 '1' => ['integer'] //limit
189 |             ]
190 |         ],
191 |         'get_witness_by_account'                 => [
192 |             'apiName' => 'witness_api',
193 |             'fields'  => [
194 |                 '0' => ['string'] //account
195 |             ]
196 |         ],
197 |         'get_followers'                         => [
198 |             'apiName' => 'follow_api',
199 |             'fields'  => [
200 |                 '0' => ['string'], //author
201 |                 '1' => ['nullOrString'], //startFollower
202 |                 '2' => ['string'], //followType //blog, ignore
203 |                 '3' => ['integer'], //limit
204 |             ]
205 |         ],
206 |         'login'                                 => [
207 |             'apiName' => 'login_api',
208 |             'fields'  => [
209 |                 0 => ['string'],
210 |                 1 => ['string']
211 |             ]
212 |         ],
213 |         'get_version'                           => [
214 |             'apiName' => 'login_api',
215 |             'fields'  => [
216 |             ]
217 |         ],
218 |         'get_api_by_name'                       => [
219 |             'apiName' => 'login_api',
220 |             'fields'  => [
221 |                 '0' => ['string'], //'api_name',for example follow_api, database_api, login_api and ect.
222 |             ]
223 |         ],
224 |         'broadcast_transaction'                 => [
225 |             'apiName' => 'network_broadcast_api',
226 |             'fields'  => [
227 |                 '0:ref_block_num'    => ['integer'],
228 |                 '0:ref_block_prefix' => ['integer'],
229 |                 '0:expiration'       => ['string'],
230 |                 '0:operations:*:0'   => ['string'],
231 |                 '0:operations:*:1'   => ['array'],
232 |                 '0:extensions'       => ['array'],
233 |                 '0:signatures'       => ['array']
234 |             ]
235 |         ],
236 |         'broadcast_transaction_synchronous'     => [
237 |             'apiName' => 'network_broadcast_api',
238 |             'fields'  => [
239 |                 '0:ref_block_num'    => ['integer'],
240 |                 '0:ref_block_prefix' => ['integer'],
241 |                 '0:expiration'       => ['string'],
242 |                 '0:operations:*:0'   => ['string'],
243 |                 '0:operations:*:1'   => ['array'],
244 |                 '0:extensions'       => ['array'],
245 |                 '0:signatures'       => ['array']
246 |             ]
247 |         ],
248 |         'get_transaction_hex'                 => [
249 |             'apiName' => 'database_api',
250 |             'fields'  => [
251 |                 '0:ref_block_num'    => ['integer'],
252 |                 '0:ref_block_prefix' => ['integer'],
253 |                 '0:expiration'       => ['string'],
254 |                 '0:operations:*:0'   => ['string'],
255 |                 '0:operations:*:1'   => ['array'],
256 |                 '0:extensions'       => ['array'],
257 |                 '0:signatures'       => ['array']
258 |             ]
259 |         ],
260 |     ];
261 | }


--------------------------------------------------------------------------------
/Connectors/WebSocket/WSConnectorAbstract.php:
--------------------------------------------------------------------------------
  1 |  0 && is_array(static::$nodeURL) && count(static::$nodeURL) > 1) {
 74 |             $this->orderNodesByTimeout($orderNodesByTimeout);
 75 |         }
 76 |     }
 77 | 
 78 |     /**
 79 |      * @param int $timeoutSeconds
 80 |      */
 81 |     public function setConnectionTimeoutSeconds($timeoutSeconds)
 82 |     {
 83 |         $this->wsTimeoutSeconds = $timeoutSeconds;
 84 |     }
 85 | 
 86 |     /**
 87 |      * Number of tries to reconnect (get correct answer) to api
 88 |      *
 89 |      * @param int $triesN
 90 |      */
 91 |     public function setMaxNumberOfTriesToReconnect($triesN)
 92 |     {
 93 |         $this->maxNumberOfTriesToCallApi = $triesN;
 94 |     }
 95 | 
 96 | 
 97 |     /**
 98 |      * @param integer $orderNodesByTimeout Only if you set few nodes. do not set it is too low, if node do not answer it go out from list
 99 |      *
100 |      *
101 |      * @throws \WebSocket\BadOpcodeException
102 |      */
103 |     public function orderNodesByTimeout($orderNodesByTimeout)
104 |     {
105 |         $requestId = $this->getNextId();
106 |         $limits = [4, 7];
107 |         $requestData = [
108 |             'jsonrpc' => '2.0',
109 |             'id'      => $requestId,
110 |             'method'  => 'call',
111 |             'params'  => [
112 |                 'database_api',
113 |                 'get_discussions_by_created',
114 |                 [['limit' => 7]]
115 |             ]
116 |         ];
117 |         $wsTimeoutSeconds = $this->wsTimeoutSeconds;
118 |         $this->wsTimeoutSeconds = $orderNodesByTimeout;
119 |         $timeouts = [];
120 |         foreach (static::$nodeURL as $currentNodeURL) {
121 |             try {
122 |                 $connection = $this->newConnection($currentNodeURL);
123 | 
124 |                 $startMTime = microtime(true);
125 |                 foreach ($limits as $limit) {
126 |                     $requestData['params'][2] = [['limit' => $limit]];
127 | 
128 |                     $connection->send(json_encode($requestData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
129 |                     $answerRaw = $connection->receive();
130 | 
131 |                     $answer = json_decode($answerRaw, self::ANSWER_FORMAT_ARRAY);
132 | 
133 |                     if (isset($answer['error'])) {
134 |                         throw new ConnectionException('got error in answer: ' . $answer['error']['code'] . ' ' . $answer['error']['message']);
135 |                     }
136 |                 }
137 |                 $timeout = $requestTimeout = microtime(true) - $startMTime;
138 | 
139 |                 if ($connection->isConnected()) {
140 |                     $connection->close();
141 |                 }
142 |                 $timeouts[$currentNodeURL] = round($timeout, 4);
143 |             } catch (ConnectionException $e) {
144 |             }
145 |         }
146 |         static::$connection = null;
147 |         $this->wsTimeoutSeconds = $wsTimeoutSeconds;
148 |         asort($timeouts);
149 |         static::$nodeURL = array_keys($timeouts);
150 |     }
151 | 
152 |     /**
153 |      * @return Client|null
154 |      */
155 |     public function getConnection()
156 |     {
157 |         if (
158 |             !isset(self::$connection[static::class])
159 |             || self::$connection[static::class] === null
160 |         ) {
161 |             $this->newConnection($this->getCurrentUrl());
162 |         }
163 | 
164 |         return self::$connection[static::class];
165 |     }
166 | 
167 |     /**
168 |      * @param string $nodeUrl
169 |      *
170 |      * @return Client
171 |      */
172 |     public function newConnection($nodeUrl)
173 |     {
174 |         self::$connection[static::class] = new Client($nodeUrl, ['timeout' => $this->wsTimeoutSeconds]);
175 | 
176 |         return self::$connection[static::class];
177 |     }
178 | 
179 |     public function getCurrentUrl()
180 |     {
181 |         if (
182 |             !isset(static::$currentNodeURL[static::class])
183 |             || static::$currentNodeURL[static::class] === null
184 |             || !in_array(static::$currentNodeURL[static::class], static::$nodeURL)
185 |         ) {
186 |             if (is_array(static::$nodeURL)) {
187 |                 $this->reserveNodeUrlList = static::$nodeURL;
188 |                 $url = array_shift($this->reserveNodeUrlList);
189 |             } else {
190 |                 $url = static::$nodeURL;
191 |             }
192 | 
193 |             static::$currentNodeURL[static::class] = $url;
194 |         }
195 | 
196 |         return static::$currentNodeURL[static::class];
197 |     }
198 | 
199 |     public function isExistReserveNodeUrl()
200 |     {
201 |         return !empty($this->reserveNodeUrlList);
202 |     }
203 | 
204 |     protected function setReserveNodeUrlToCurrentUrl()
205 |     {
206 |         static::$currentNodeURL[static::class] = array_shift($this->reserveNodeUrlList);
207 |     }
208 | 
209 |     public function connectToReserveNode()
210 |     {
211 |         $connection = $this->getConnection();
212 |         if ($connection->isConnected()) {
213 |             $connection->close();
214 |         }
215 |         $this->setReserveNodeUrlToCurrentUrl();
216 |         return $this->newConnection($this->getCurrentUrl());
217 |     }
218 | 
219 |     public function getCurrentId()
220 |     {
221 |         if (
222 |             !isset(self::$currentId[static::class])
223 |             || self::$currentId[static::class] === null
224 |         ) {
225 |             self::$currentId[static::class] = 1;
226 |         }
227 | 
228 |         return self::$currentId[static::class];
229 |     }
230 | 
231 |     public function getPlatform()
232 |     {
233 |         return $this->platform;
234 |     }
235 | 
236 |     public function setCurrentId($id)
237 |     {
238 |         self::$currentId[static::class] = $id;
239 |     }
240 | 
241 |     public function getNextId()
242 |     {
243 |         $next = $this->getCurrentId() + 1;
244 |         $this->setCurrentId($next);
245 | 
246 |         return $next;
247 |     }
248 | 
249 |     /**
250 |      * @param string $apiName
251 |      * @param array  $data
252 |      * @param string $answerFormat
253 |      * @param int    $try_number Try number of getting answer from api
254 |      *
255 |      * @return array|object
256 |      * @throws ConnectionException
257 |      * @throws \WebSocket\BadOpcodeException
258 |      */
259 |     public function doRequest($apiName, array $data, $answerFormat = self::ANSWER_FORMAT_ARRAY, $try_number = 1)
260 |     {
261 |         $requestId = $this->getNextId();
262 |         $requestData = [
263 |             'jsonrpc' => '2.0',
264 |             'id'     => $requestId,
265 |             'method' => 'call',
266 |             'params' => [
267 |                 $apiName,
268 |                 $data['method'],
269 |                 $data['params']
270 |             ]
271 |         ];
272 |         try {
273 |             $connection = $this->getConnection();
274 |             $connection->send(json_encode($requestData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
275 |             $answerRaw = $connection->receive();
276 |             $answer = json_decode($answerRaw, self::ANSWER_FORMAT_ARRAY === $answerFormat);
277 | 
278 |             if (
279 |                 (self::ANSWER_FORMAT_ARRAY === $answerFormat && isset($answer['error']))
280 |                 || (self::ANSWER_FORMAT_OBJECT === $answerFormat && isset($answer->error))
281 |             ) {
282 |                 throw new ConnectionException('got error in answer: ' . $answer['error']['code'] . ' ' . $answer['error']['message']);
283 |             }
284 |             //check that answer has the same id or id from previous tries, else it is answer from other request
285 |             if (self::ANSWER_FORMAT_ARRAY === $answerFormat) {
286 |                 $answerId = $answer['id'];
287 |             } elseif (self::ANSWER_FORMAT_OBJECT === $answerFormat) {
288 |                 $answerId = $answer->id;
289 |             }
290 |             if ($requestId - $answerId > ($try_number - 1)) {
291 |                 throw new ConnectionException('got answer from old request');
292 |             }
293 | 
294 | 
295 |         } catch (ConnectionException $e) {
296 | 
297 |             if ($try_number < $this->maxNumberOfTriesToCallApi) {
298 |                 //if got WS Exception, try to get answer again
299 |                 $answer = $this->doRequest($apiName, $data, $answerFormat, $try_number + 1);
300 |             } elseif ($this->isExistReserveNodeUrl()) {
301 |                 //if got WS Exception after few ties, connect to reserve node
302 |                 $this->connectToReserveNode();
303 |                 $answer = $this->doRequest($apiName, $data, $answerFormat);
304 |             } else {
305 |                 //if nothing helps
306 |                 throw $e;
307 |             }
308 |         }
309 | 
310 |         return $answer;
311 |     }
312 | }


--------------------------------------------------------------------------------
/Commands/HiveApiMethods.php:
--------------------------------------------------------------------------------
  1 |  [ 'apiName' => 'api_name', 'fields'=>['массив с полями из команды']]];
 11 |      *
 12 |      * @var array
 13 |      */
 14 |     public static $map = [
 15 |         'get_block'                             => [
 16 |             'apiName' => 'database_api',
 17 |             'fields'  => [
 18 |                 '0' => ['integer'], //block_id
 19 |             ]
 20 |         ],
 21 |         'get_accounts'                          => [
 22 |             'apiName' => 'database_api',
 23 |             'fields'  => [
 24 |                 '0' => ['array'], //authors
 25 |             ]
 26 |         ],
 27 |         'get_account_count'                     => [
 28 |             'apiName' => 'database_api',
 29 |             'fields'  => []
 30 |         ],
 31 |         'get_account_history'                   => [
 32 |             'apiName' => 'database_api',
 33 |             'fields'  => [
 34 |                 '0' => ['string'], //authors
 35 |                 '1' => ['integer'], //from
 36 |                 '2' => ['integer'], //limit max 2000
 37 |             ]
 38 |         ],
 39 |         'get_account_votes'                     => [
 40 |             'apiName' => 'database_api',
 41 |             'fields'  => [
 42 |                 '0' => ['string'], //account name
 43 |             ]
 44 |         ],
 45 |         'get_active_votes'                      => [
 46 |             'apiName' => 'database_api',
 47 |             'fields'  => [
 48 |                 '0' => ['string'], //author
 49 |                 '1' => ['string'], //permlink
 50 |             ]
 51 |         ],
 52 |         'get_active_witnesses'                  => [
 53 |             'apiName' => 'database_api',
 54 |             'fields'  => [
 55 |             ]
 56 |         ],
 57 |         'get_content'                           => [
 58 |             'apiName' => 'database_api',
 59 |             'fields'  => [
 60 |                 '0' => ['string'], //author
 61 |                 '1' => ['string'], //permlink
 62 |             ]
 63 |         ],
 64 |         'get_block_header'                      => [
 65 |             'apiName' => 'database_api',
 66 |             'fields'  => [
 67 |                 '0' => ['integer'], //block_id
 68 |             ]
 69 |         ],
 70 |         'get_chain_properties'         => [
 71 |             'apiName' => 'database_api',
 72 |             'fields'  => [
 73 |             ]
 74 |         ],
 75 |         'get_config'                   => [
 76 |             'apiName' => 'database_api',
 77 |             'fields'  => [
 78 |             ]
 79 |         ],
 80 |         'get_content_replies'                   => [
 81 |             'apiName' => 'database_api',
 82 |             'fields'  => [
 83 |                 '0' => ['string'], //author
 84 |                 '1' => ['string'], //permlink
 85 |             ]
 86 |         ],
 87 |         'get_current_median_history_price'      => [
 88 |             'apiName' => 'database_api',
 89 |             'fields'  => [
 90 |             ]
 91 |         ],
 92 |         'get_discussions_by_author_before_date' => [
 93 |             'apiName' => 'database_api',
 94 |             'fields'  => [
 95 |                 '0' => ['string'], //'author',
 96 |                 '1' => ['string'], //'start_permlink' for pagination,
 97 |                 '2' => ['string'], //'before_date'
 98 |                 '3' => ['integer'], //'limit'
 99 |             ]
100 |         ],
101 |         'get_discussions_by_blog'               => [
102 |             'apiName' => 'database_api',
103 |             'fields'  => [
104 |                 '*:tag'            => ['string'], //'author',
105 |                 '*:limit'          => ['integer'], //'limit'
106 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
107 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
108 |             ]
109 |         ],
110 |         'get_discussions_by_comments'               => [
111 |             'apiName' => 'database_api',
112 |             'fields'  => [
113 |                 '*:limit'          => ['integer'], //'limit'
114 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
115 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
116 |             ]
117 |         ],
118 |         'get_discussions_by_created'            => [
119 |             'apiName' => 'database_api',
120 |             'fields'  => [
121 |                 '*:tag'            => ['nullOrString'], //'author',
122 |                 '*:limit'          => ['integer'], //'limit'
123 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
124 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
125 |             ]
126 |         ],
127 |         'get_discussions_by_feed'               => [
128 |             'apiName' => 'database_api',
129 |             'fields'  => [
130 |                 '*:tag'            => ['string'], //'author',
131 |                 '*:limit'          => ['integer'], //'limit'
132 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
133 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
134 |             ]
135 |         ],
136 |         'get_discussions_by_trending'           => [
137 |             'apiName' => 'database_api',
138 |             'fields'  => [
139 |                 '*:tag'            => ['nullOrString'], //'author',
140 |                 '*:limit'          => ['integer'], //'limit'
141 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
142 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
143 |             ]
144 |         ],
145 |         'get_dynamic_global_properties'         => [
146 |             'apiName' => 'database_api',
147 |             'fields'  => [
148 |             ]
149 |         ],
150 |         'get_feed_history'         => [
151 |             'apiName' => 'witness_api',
152 |             'fields'  => [
153 |             ]
154 |         ],
155 |         'get_follow_count'                         => [
156 |             'apiName' => 'follow_api',
157 |             'fields'  => [
158 |                 '0' => ['string'], //author
159 |             ]
160 |         ],
161 |         'get_ops_in_block'                      => [
162 |             'apiName' => 'database_api',
163 |             'fields'  => [
164 |                 '0' => ['integer'], //blockNum
165 |                 '1' => ['bool'], //onlyVirtual
166 |             ]
167 |         ],
168 |         'get_transaction'                      => [
169 |             'apiName' => 'database_api',
170 |             'fields'  => [
171 |                 '0' => ['string'], //trxId
172 |             ]
173 |         ],
174 |         'get_reward_fund'                          => [
175 |             'apiName' => 'database_api',
176 |             'fields'  => [
177 |                 '0' => ['string'], //post or comments
178 |             ]
179 |         ],
180 |         'get_trending_categories'               => [
181 |             'apiName' => 'database_api',
182 |             'fields'  => [
183 |                 '0' => ['nullOrString'], //after
184 |                 '1' => ['integer'], //permlink
185 |             ]
186 |         ],
187 |         'get_trending_tags'               => [
188 |             'apiName' => 'database_api',
189 |             'fields'  => [
190 |                 '0' => ['nullOrString'], //after
191 |                 '1' => ['integer'], //permlink
192 |             ]
193 |         ],
194 |         'get_witnesses_by_vote'                 => [
195 |             'apiName' => 'database_api',
196 |             'fields'  => [
197 |                 '0' => ['string'], //from accountName, can be empty string ''
198 |                 '1' => ['integer'] //limit
199 |             ]
200 |         ],
201 |         'get_witness_by_account'                 => [
202 |             'apiName' => 'witness_api',
203 |             'fields'  => [
204 |                 '0' => ['string'] //account
205 |             ]
206 |         ],
207 |         'get_followers'                         => [
208 |             'apiName' => 'follow_api',
209 |             'fields'  => [
210 |                 '0' => ['string'], //author
211 |                 '1' => ['nullOrString'], //startFollower
212 |                 '2' => ['string'], //followType //blog, ignore
213 |                 '3' => ['integer'], //limit
214 |             ]
215 |         ],
216 |         'login'                                 => [
217 |             'apiName' => 'login_api',
218 |             'fields'  => [
219 |                 0 => ['string'],
220 |                 1 => ['string']
221 |             ]
222 |         ],
223 |         'get_version'                           => [
224 |             'apiName' => 'login_api',
225 |             'fields'  => [
226 |             ]
227 |         ],
228 |         'get_api_by_name'                       => [
229 |             'apiName' => 'login_api',
230 |             'fields'  => [
231 |                 '0' => ['string'], //'api_name',for example follow_api, database_api, login_api and ect.
232 |             ]
233 |         ],
234 |         'get_ticker'                           => [
235 |             'apiName' => 'market_history',
236 |             'fields'  => [
237 |             ]
238 |         ],
239 |         'broadcast_transaction'                 => [
240 |             'apiName' => 'network_broadcast_api',
241 |             'fields'  => [
242 |                 '0:ref_block_num'    => ['integer'],
243 |                 '0:ref_block_prefix' => ['integer'],
244 |                 '0:expiration'       => ['string'],
245 |                 '0:operations:*:0'   => ['string'],
246 |                 '0:operations:*:1'   => ['array'],
247 |                 '0:extensions'       => ['array'],
248 |                 '0:signatures'       => ['array']
249 |             ]
250 |         ],
251 |         'broadcast_transaction_synchronous'     => [
252 |             'apiName' => 'network_broadcast_api',
253 |             'fields'  => [
254 |                 '0:ref_block_num'    => ['integer'],
255 |                 '0:ref_block_prefix' => ['integer'],
256 |                 '0:expiration'       => ['string'],
257 |                 '0:operations:*:0'   => ['string'],
258 |                 '0:operations:*:1'   => ['array'],
259 |                 '0:extensions'       => ['array'],
260 |                 '0:signatures'       => ['array']
261 |             ]
262 |         ],
263 |         'get_transaction_hex'                 => [
264 |             'apiName' => 'database_api',
265 |             'fields'  => [
266 |                 '0:ref_block_num'    => ['integer'],
267 |                 '0:ref_block_prefix' => ['integer'],
268 |                 '0:expiration'       => ['string'],
269 |                 '0:operations:*:0'   => ['string'],
270 |                 '0:operations:*:1'   => ['array'],
271 |                 '0:extensions'       => ['array'],
272 |                 '0:signatures'       => ['array']
273 |             ]
274 |         ],
275 |     ];
276 | }


--------------------------------------------------------------------------------
/Commands/SteemitApiMethods.php:
--------------------------------------------------------------------------------
  1 |  [ 'apiName' => 'api_name', 'fields'=>['массив с полями из команды']]];
 11 |      *
 12 |      * @var array
 13 |      */
 14 |     public static $map = [
 15 |         'get_block'                             => [
 16 |             'apiName' => 'database_api',
 17 |             'fields'  => [
 18 |                 '0' => ['integer'], //block_id
 19 |             ]
 20 |         ],
 21 |         'get_accounts'                          => [
 22 |             'apiName' => 'database_api',
 23 |             'fields'  => [
 24 |                 '0' => ['array'], //authors
 25 |             ]
 26 |         ],
 27 |         'get_account_count'                     => [
 28 |             'apiName' => 'database_api',
 29 |             'fields'  => []
 30 |         ],
 31 |         'get_account_history'                   => [
 32 |             'apiName' => 'database_api',
 33 |             'fields'  => [
 34 |                 '0' => ['string'], //authors
 35 |                 '1' => ['integer'], //from
 36 |                 '2' => ['integer'], //limit max 2000
 37 |             ]
 38 |         ],
 39 |         'get_account_votes'                     => [
 40 |             'apiName' => 'database_api',
 41 |             'fields'  => [
 42 |                 '0' => ['string'], //account name
 43 |             ]
 44 |         ],
 45 |         'get_active_votes'                      => [
 46 |             'apiName' => 'database_api',
 47 |             'fields'  => [
 48 |                 '0' => ['string'], //author
 49 |                 '1' => ['string'], //permlink
 50 |             ]
 51 |         ],
 52 |         'get_active_witnesses'                  => [
 53 |             'apiName' => 'database_api',
 54 |             'fields'  => [
 55 |             ]
 56 |         ],
 57 |         'get_content'                           => [
 58 |             'apiName' => 'database_api',
 59 |             'fields'  => [
 60 |                 '0' => ['string'], //author
 61 |                 '1' => ['string'], //permlink
 62 |             ]
 63 |         ],
 64 |         'get_block_header'                      => [
 65 |             'apiName' => 'database_api',
 66 |             'fields'  => [
 67 |                 '0' => ['integer'], //block_id
 68 |             ]
 69 |         ],
 70 |         'get_chain_properties'         => [
 71 |             'apiName' => 'database_api',
 72 |             'fields'  => [
 73 |             ]
 74 |         ],
 75 |         'get_config'                   => [
 76 |             'apiName' => 'database_api',
 77 |             'fields'  => [
 78 |             ]
 79 |         ],
 80 |         'get_content_replies'                   => [
 81 |             'apiName' => 'database_api',
 82 |             'fields'  => [
 83 |                 '0' => ['string'], //author
 84 |                 '1' => ['string'], //permlink
 85 |             ]
 86 |         ],
 87 |         'get_current_median_history_price'      => [
 88 |             'apiName' => 'database_api',
 89 |             'fields'  => [
 90 |             ]
 91 |         ],
 92 |         'get_discussions_by_author_before_date' => [
 93 |             'apiName' => 'database_api',
 94 |             'fields'  => [
 95 |                 '0' => ['string'], //'author',
 96 |                 '1' => ['string'], //'start_permlink' for pagination,
 97 |                 '2' => ['string'], //'before_date'
 98 |                 '3' => ['integer'], //'limit'
 99 |             ]
100 |         ],
101 |         'get_discussions_by_blog'               => [
102 |             'apiName' => 'database_api',
103 |             'fields'  => [
104 |                 '*:tag'            => ['string'], //'author',
105 |                 '*:limit'          => ['integer'], //'limit'
106 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
107 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
108 |             ]
109 |         ],
110 |         'get_discussions_by_comments'               => [
111 |             'apiName' => 'database_api',
112 |             'fields'  => [
113 |                 '*:limit'          => ['integer'], //'limit'
114 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
115 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
116 |             ]
117 |         ],
118 |         'get_discussions_by_created'            => [
119 |             'apiName' => 'database_api',
120 |             'fields'  => [
121 |                 '*:tag'            => ['nullOrString'], //'author',
122 |                 '*:limit'          => ['integer'], //'limit'
123 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
124 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
125 |             ]
126 |         ],
127 |         'get_discussions_by_feed'               => [
128 |             'apiName' => 'database_api',
129 |             'fields'  => [
130 |                 '*:tag'            => ['string'], //'author',
131 |                 '*:limit'          => ['integer'], //'limit'
132 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
133 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
134 |             ]
135 |         ],
136 |         'get_discussions_by_trending'           => [
137 |             'apiName' => 'database_api',
138 |             'fields'  => [
139 |                 '*:tag'            => ['nullOrString'], //'author',
140 |                 '*:limit'          => ['integer'], //'limit'
141 |                 '*:start_author'   => ['nullOrString'], //'start_author' for pagination,
142 |                 '*:start_permlink' => ['nullOrString'] //'start_permlink' for pagination,
143 |             ]
144 |         ],
145 |         'get_dynamic_global_properties'         => [
146 |             'apiName' => 'database_api',
147 |             'fields'  => [
148 |             ]
149 |         ],
150 |         'get_feed_history'         => [
151 |             'apiName' => 'witness_api',
152 |             'fields'  => [
153 |             ]
154 |         ],
155 |         'get_follow_count'                         => [
156 |             'apiName' => 'follow_api',
157 |             'fields'  => [
158 |                 '0' => ['string'], //author
159 |             ]
160 |         ],
161 |         'get_ops_in_block'                      => [
162 |             'apiName' => 'database_api',
163 |             'fields'  => [
164 |                 '0' => ['integer'], //blockNum
165 |                 '1' => ['bool'], //onlyVirtual
166 |             ]
167 |         ],
168 |         'get_transaction'                      => [
169 |             'apiName' => 'database_api',
170 |             'fields'  => [
171 |                 '0' => ['string'], //trxId
172 |             ]
173 |         ],
174 |         'get_reward_fund'                          => [
175 |             'apiName' => 'database_api',
176 |             'fields'  => [
177 |                 '0' => ['string'], //post or comments
178 |             ]
179 |         ],
180 |         'get_trending_categories'               => [
181 |             'apiName' => 'database_api',
182 |             'fields'  => [
183 |                 '0' => ['nullOrString'], //after
184 |                 '1' => ['integer'], //permlink
185 |             ]
186 |         ],
187 |         'get_trending_tags'               => [
188 |             'apiName' => 'database_api',
189 |             'fields'  => [
190 |                 '0' => ['nullOrString'], //after
191 |                 '1' => ['integer'], //permlink
192 |             ]
193 |         ],
194 |         'get_witnesses_by_vote'                 => [
195 |             'apiName' => 'database_api',
196 |             'fields'  => [
197 |                 '0' => ['string'], //from accountName, can be empty string ''
198 |                 '1' => ['integer'] //limit
199 |             ]
200 |         ],
201 |         'get_witness_by_account'                 => [
202 |             'apiName' => 'witness_api',
203 |             'fields'  => [
204 |                 '0' => ['string'] //account
205 |             ]
206 |         ],
207 |         'get_followers'                         => [
208 |             'apiName' => 'follow_api',
209 |             'fields'  => [
210 |                 '0' => ['string'], //author
211 |                 '1' => ['nullOrString'], //startFollower
212 |                 '2' => ['string'], //followType //blog, ignore
213 |                 '3' => ['integer'], //limit
214 |             ]
215 |         ],
216 |         'login'                                 => [
217 |             'apiName' => 'login_api',
218 |             'fields'  => [
219 |                 0 => ['string'],
220 |                 1 => ['string']
221 |             ]
222 |         ],
223 |         'get_version'                           => [
224 |             'apiName' => 'login_api',
225 |             'fields'  => [
226 |             ]
227 |         ],
228 |         'get_api_by_name'                       => [
229 |             'apiName' => 'login_api',
230 |             'fields'  => [
231 |                 '0' => ['string'], //'api_name',for example follow_api, database_api, login_api and ect.
232 |             ]
233 |         ],
234 |         'get_ticker'                           => [
235 |             'apiName' => 'market_history',
236 |             'fields'  => [
237 |             ]
238 |         ],
239 |         'broadcast_transaction'                 => [
240 |             'apiName' => 'network_broadcast_api',
241 |             'fields'  => [
242 |                 '0:ref_block_num'    => ['integer'],
243 |                 '0:ref_block_prefix' => ['integer'],
244 |                 '0:expiration'       => ['string'],
245 |                 '0:operations:*:0'   => ['string'],
246 |                 '0:operations:*:1'   => ['array'],
247 |                 '0:extensions'       => ['array'],
248 |                 '0:signatures'       => ['array']
249 |             ]
250 |         ],
251 |         'broadcast_transaction_synchronous'     => [
252 |             'apiName' => 'network_broadcast_api',
253 |             'fields'  => [
254 |                 '0:ref_block_num'    => ['integer'],
255 |                 '0:ref_block_prefix' => ['integer'],
256 |                 '0:expiration'       => ['string'],
257 |                 '0:operations:*:0'   => ['string'],
258 |                 '0:operations:*:1'   => ['array'],
259 |                 '0:extensions'       => ['array'],
260 |                 '0:signatures'       => ['array']
261 |             ]
262 |         ],
263 |         'get_transaction_hex'                 => [
264 |             'apiName' => 'database_api',
265 |             'fields'  => [
266 |                 '0:ref_block_num'    => ['integer'],
267 |                 '0:ref_block_prefix' => ['integer'],
268 |                 '0:expiration'       => ['string'],
269 |                 '0:operations:*:0'   => ['string'],
270 |                 '0:operations:*:1'   => ['array'],
271 |                 '0:extensions'       => ['array'],
272 |                 '0:signatures'       => ['array']
273 |             ]
274 |         ],
275 |     ];
276 | }


--------------------------------------------------------------------------------
/Commands/VizApiMethods.php:
--------------------------------------------------------------------------------
  1 |  [ 'apiName' => 'api_name', 'fields'=>['массив с полями из команды']]];
 11 |      *
 12 |      * @var array
 13 |      */
 14 |     public static $map = [
 15 |         'get_block'                             => [
 16 |             'apiName' => 'database_api',
 17 |             'fields'  => [
 18 |                 '0' => ['integer'], //block_id
 19 |             ]
 20 |         ],
 21 |         'get_accounts'                          => [
 22 |             'apiName' => 'database_api',
 23 |             'fields'  => [
 24 |                 '0' => ['array'], //authors
 25 |             ]
 26 |         ],
 27 |         'get_account_count'                     => [
 28 |             'apiName' => 'database_api',
 29 |             'fields'  => []
 30 |         ],
 31 |         'get_account_history'                   => [
 32 |             'apiName' => 'account_history',
 33 |             'fields'  => [
 34 |                 '0' => ['string'], //authors
 35 |                 '1' => ['integer'], //from
 36 |                 '2' => ['integer'], //limit max 2000
 37 |             ]
 38 |         ],
 39 |         'get_account_votes'                     => [
 40 |             'apiName' => 'social_network',
 41 |             'fields'  => [
 42 |                 '0' => ['string'], //account name
 43 |                 '1' => ['integer'], //from as offset
 44 |                 '2' => ['integer'] //voteLimit by default 10 000
 45 |             ]
 46 |         ],
 47 |         'get_active_votes'                      => [
 48 |             'apiName' => 'social_network',
 49 |             'fields'  => [
 50 |                 '0' => ['string'], //author
 51 |                 '1' => ['string'], //permlink
 52 |                 '2' => ['nullOrInteger'] //voteLimit by default 10 000
 53 |             ]
 54 |         ],
 55 |         'get_active_witnesses'                  => [
 56 |             'apiName' => 'witness_api',
 57 |             'fields'  => [
 58 |             ]
 59 |         ],
 60 |         'get_block_header'                      => [
 61 |             'apiName' => 'database_api',
 62 |             'fields'  => [
 63 |                 '0' => ['integer'], //block_id
 64 |             ]
 65 |         ],
 66 |         'get_chain_properties'         => [
 67 |             'apiName' => 'database_api',
 68 |             'fields'  => [
 69 |             ]
 70 |         ],
 71 |         'get_config'                   => [
 72 |             'apiName' => 'database_api',
 73 |             'fields'  => [
 74 |             ]
 75 |         ],
 76 |         'get_content'                           => [
 77 |             'apiName' => 'social_network',
 78 |             'fields'  => [
 79 |                 '0' => ['string'], //author
 80 |                 '1' => ['string'], //permlink
 81 |                 '2' => ['nullOrInteger'] //voteLimit by default 10 000
 82 |             ]
 83 |         ],
 84 |         'get_content_replies'                   => [
 85 |             'apiName' => 'social_network',
 86 |             'fields'  => [
 87 |                 '0' => ['string'], //author
 88 |                 '1' => ['string'], //permlink
 89 |                 '2' => ['nullOrInteger'] //voteLimit by default 10 000
 90 |             ]
 91 |         ],
 92 |         'get_discussions_by_author_before_date' => [
 93 |             'apiName' => 'tags',
 94 |             'fields'  => [
 95 |                 '0' => ['string'], //'author',
 96 |                 '1' => ['string'], //'start_permlink' for pagination,
 97 |                 '2' => ['string'], //'before_date'
 98 |                 '3' => ['integer'] //'limit'
 99 |             ]
100 |         ],
101 |         'get_discussions_by_blog'               => [
102 |             'apiName' => 'tags',
103 |             'fields'  => [
104 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
105 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
106 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
107 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
108 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
109 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
110 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
111 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
112 |             ]
113 |         ],
114 |         'get_discussions_by_created'            => [
115 |             'apiName' => 'tags',
116 |             'fields'  => [
117 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
118 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
119 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
120 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
121 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
122 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
123 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
124 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
125 |             ],
126 |         ],
127 |         'get_discussions_by_feed'               => [
128 |             'apiName' => 'tags',
129 |             'fields'  => [
130 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
131 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
132 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
133 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
134 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
135 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
136 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
137 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
138 |             ]
139 |         ],
140 |         'get_discussions_by_trending'           => [
141 |             'apiName' => 'tags',
142 |             'fields'  => [
143 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
144 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
145 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
146 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
147 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
148 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
149 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
150 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
151 |             ]
152 |         ],
153 |         'get_dynamic_global_properties'         => [
154 |             'apiName' => 'database_api',
155 |             'fields'  => [
156 |             ]
157 |         ],
158 |         'get_ops_in_block'                      => [
159 |             'apiName' => 'operation_history',
160 |             'fields'  => [
161 |                 '0' => ['integer'], //blockNum
162 |                 '1' => ['bool'], //onlyVirtual
163 |             ]
164 |         ],
165 |         'get_transaction'                      => [
166 |             'apiName' => 'operation_history',
167 |             'fields'  => [
168 |                 '0' => ['string'], //trxId
169 |             ]
170 |         ],
171 |         'get_trending_tags'               => [
172 |             'apiName' => 'tags',
173 |             'fields'  => [
174 |                 '0' => ['nullOrString'], //after
175 |                 '1' => ['integer'], //permlink
176 |             ]
177 |         ],
178 |         'get_witnesses_by_vote'                 => [
179 |             'apiName' => 'witness_api',
180 |             'fields'  => [
181 |                 '0' => ['string'], //from accountName, can be empty string ''
182 |                 '1' => ['integer'] //limit
183 |             ]
184 |         ],
185 |         'get_witness_by_account'                 => [
186 |             'apiName' => 'witness_api',
187 |             'fields'  => [
188 |                 '0' => ['string'] //account
189 |             ]
190 |         ],
191 |         'get_followers'                         => [
192 |             'apiName' => 'follow',
193 |             'fields'  => [
194 |                 '0' => ['string'], //author
195 |                 '1' => ['nullOrString'], //startFollower
196 |                 '2' => ['string'], //followType //blog, ignore
197 |                 '3' => ['integer'], //limit
198 |             ]
199 |         ],
200 |         'get_follow_count'                         => [
201 |             'apiName' => 'follow',
202 |             'fields'  => [
203 |                 '0' => ['string'], //author
204 |             ]
205 |         ],
206 |         'broadcast_transaction'                 => [
207 |             'apiName' => 'network_broadcast_api',
208 |             'fields'  => [
209 |                 '0:ref_block_num'    => ['integer'],
210 |                 '0:ref_block_prefix' => ['integer'],
211 |                 '0:expiration'       => ['string'],
212 |                 '0:operations:*:0'   => ['string'],
213 |                 '0:operations:*:1'   => ['array'],
214 |                 '0:extensions'       => ['array'],
215 |                 '0:signatures'       => ['array']
216 |             ]
217 |         ],
218 |         'broadcast_transaction_synchronous'     => [
219 |             'apiName' => 'network_broadcast_api',
220 |             'fields'  => [
221 |                 '0:ref_block_num'    => ['integer'],
222 |                 '0:ref_block_prefix' => ['integer'],
223 |                 '0:expiration'       => ['string'],
224 |                 '0:operations:*:0'   => ['string'],
225 |                 '0:operations:*:1'   => ['array'],
226 |                 '0:extensions'       => ['array'],
227 |                 '0:signatures'       => ['array']
228 |             ]
229 |         ],
230 |         'get_transaction_hex'                 => [
231 |             'apiName' => 'database_api',
232 |             'fields'  => [
233 |                 '0:ref_block_num'    => ['integer'],
234 |                 '0:ref_block_prefix' => ['integer'],
235 |                 '0:expiration'       => ['string'],
236 |                 '0:operations:*:0'   => ['string'],
237 |                 '0:operations:*:1'   => ['array'],
238 |                 '0:extensions'       => ['array'],
239 |                 '0:signatures'       => ['array']
240 |             ]
241 |         ],
242 |         'get_vesting_delegations'                 => [
243 |             'apiName' => 'database_api',
244 |             'fields'  => [
245 |                 '0' => ['string'], //account
246 |                 '1' => ['string'], //from
247 |                 '2' => ['integer'], //limit <= 100
248 |                 '3' => ['string'], //type //receive, delegated
249 |             ]
250 |         ],
251 |     ];
252 | }


--------------------------------------------------------------------------------
/Connectors/Http/HttpJsonRpcConnectorAbstract.php:
--------------------------------------------------------------------------------
  1 |  0 && is_array(static::$nodeURL) && count(static::$nodeURL) > 1) {
 66 |             $this->orderNodesByTimeoutMs($orderNodesByTimeoutMs);
 67 |         }
 68 |     }
 69 | 
 70 |     /**
 71 |      * @param int $timeoutSeconds
 72 |      */
 73 |     public function setConnectionTimeoutSeconds($timeoutSeconds)
 74 |     {
 75 |         $this->wsTimeoutSeconds = $timeoutSeconds;
 76 |     }
 77 | 
 78 |     /**
 79 |      * Number of tries to reconnect (get correct answer) to api
 80 |      *
 81 |      * @param int $triesN
 82 |      */
 83 |     public function setMaxNumberOfTriesToReconnect($triesN)
 84 |     {
 85 |         $this->maxNumberOfTriesToCallApi = $triesN;
 86 |     }
 87 | 
 88 | 
 89 |     /**
 90 |      * @param integer $orderNodesByTimeoutMs Only if you set few nodes. do not set it is too low, if node do not answer it go out from list
 91 |      *
 92 |      * @return void
 93 |      */
 94 |     public function orderNodesByTimeoutMs($orderNodesByTimeoutMs)
 95 |     {
 96 |         $requestId = $this->getNextId();
 97 |         $limits = [4, 7];
 98 |         $requestData = [
 99 |             'jsonrpc' => '2.0',
100 |             'id'      => $requestId,
101 |             'method'  => 'call',
102 |             'params'  => [
103 |                 'database_api',
104 |                 'get_discussions_by_created',
105 |                 [['limit' => 7]]
106 |             ]
107 |         ];
108 |         $timeouts = [];
109 |         foreach (static::$nodeURL as $currentNodeURL) {
110 |             try {
111 |                 $curlOptions = [];
112 |                 $curlOptions['CURLOPT_CONNECTTIMEOUT_MS'] = $orderNodesByTimeoutMs;
113 |                 $startMTime = microtime(true);
114 |                 foreach ($limits as $limit) {
115 |                     $requestData['params'][2] = [['limit' => $limit]];
116 |                     $answerRaw = $this->curlRequest(
117 |                         $currentNodeURL,
118 |                         'post',
119 |                         $requestData,
120 |                         $curlOptions
121 |                     );
122 | 
123 |                     if ($answerRaw['code'] !== 200) {
124 |                         throw new \Exception("Curl answer code is '{$answerRaw['code']}' and response '{$answerRaw['response']}'");
125 |                     }
126 |                     $answer = json_decode($answerRaw['response'], self::ANSWER_FORMAT_ARRAY);
127 |                     if (isset($answer['error'])) {
128 |                         throw new \Exception('got error in answer: ' . $answer['error']['code'] . ' ' . $answer['error']['message']);
129 |                     }
130 |                 }
131 |                 $timeout = $requestTimeout = microtime(true) - $startMTime;
132 |                 $timeouts[$currentNodeURL] = round($timeout, 4);
133 | 
134 |             } catch (\Exception $e) {
135 |             }
136 |         }
137 |         asort($timeouts);
138 |         static::$nodeURL = array_keys($timeouts);
139 |     }
140 | 
141 |     public function getCurrentUrl()
142 |     {
143 |         if (
144 |             !isset(static::$currentNodeURL[static::class])
145 |             || static::$currentNodeURL[static::class] === null
146 |             || !in_array(static::$currentNodeURL[static::class], static::$nodeURL)
147 |         ) {
148 |             if (is_array(static::$nodeURL)) {
149 |                 $url = array_values(static::$nodeURL)[0];
150 |             } else {
151 |                 $url = static::$nodeURL;
152 |             }
153 | 
154 |             static::$currentNodeURL[static::class] = $url;
155 |         }
156 | 
157 |         return static::$currentNodeURL[static::class];
158 |     }
159 | 
160 |     protected function setReserveNodeUrlToCurrentUrl()
161 |     {
162 |         $totalNodes = count(static::$nodeURL);
163 |         foreach (static::$nodeURL as $key => $node) {
164 |             if ($node === $this->getCurrentUrl()) {
165 |                 if ($key + 1 < $totalNodes) {
166 |                     static::$currentNodeURL[static::class] = static::$nodeURL[$key + 1];
167 |                 } else {
168 |                     static::$currentNodeURL[static::class] = static::$nodeURL[0];
169 |                 }
170 |                 break;
171 |             }
172 |         }
173 |     }
174 | 
175 |     public function getCurrentId()
176 |     {
177 |         if (
178 |             !isset(self::$currentId[static::class])
179 |             || self::$currentId[static::class] === null
180 |         ) {
181 |             self::$currentId[static::class] = 1;
182 |         }
183 | 
184 |         return self::$currentId[static::class];
185 |     }
186 | 
187 |     public function getPlatform()
188 |     {
189 |         return $this->platform;
190 |     }
191 | 
192 |     public function setCurrentId($id)
193 |     {
194 |         self::$currentId[static::class] = $id;
195 |     }
196 | 
197 |     public function getNextId()
198 |     {
199 |         $next = $this->getCurrentId() + 1;
200 |         $this->setCurrentId($next);
201 | 
202 |         return $next;
203 |     }
204 | 
205 |     /**
206 |      * @param string $apiName
207 |      * @param array  $data
208 |      * @param string $answerFormat
209 |      * @param int    $try_number Try number of getting answer from api
210 |      * @param bool   $resetTryNodes update total requested nodes tries counter
211 |      *
212 |      * @return array|object
213 |      * @throws \Exception
214 |      */
215 |     public function doRequest($apiName, array $data, $answerFormat = self::ANSWER_FORMAT_ARRAY, $try_number = 1, $resetTryNodes = true)
216 |     {
217 |         if ($resetTryNodes) {
218 |             $this->resetTryNodes = count(static::$nodeURL);
219 |         }
220 |         $requestId = $this->getNextId();
221 |         $requestData = [
222 |             'jsonrpc' => '2.0',
223 |             'id'      => $requestId,
224 |             'method'  => 'call',
225 |             'params'  => [
226 |                 $apiName,
227 |                 $data['method'],
228 |                 $data['params']
229 |             ]
230 |         ];
231 |         try {
232 |             $curlOptions = [];
233 |             $curlOptions['CURLOPT_CONNECTTIMEOUT'] = $this->wsTimeoutSeconds;
234 |             $answerRaw = $this->curlRequest(
235 |                 $this->getCurrentUrl(),
236 |                 'post',
237 |                 $requestData,
238 |                 $curlOptions
239 |             );
240 |             if ($answerRaw['code'] !== 200) {
241 |                 throw new \Exception("Curl answer code is '{$answerRaw['code']}' and response '{$answerRaw['response']}'");
242 |             }
243 |             $answer = json_decode($answerRaw['response'], self::ANSWER_FORMAT_ARRAY === $answerFormat);
244 | 
245 |             if (isset($answer['error'])) {
246 |                 throw new \Exception('got error in answer: ' . $answer['error']['code'] . ' ' . $answer['error']['message']);
247 |             }
248 |             //check that answer has the same id or id from previous tries, else it is answer from other request
249 |             if (self::ANSWER_FORMAT_ARRAY === $answerFormat) {
250 |                 $answerId = $answer['id'];
251 |             } else { //if (self::ANSWER_FORMAT_OBJECT === $answerFormat) {
252 |                 $answerId = $answer->id;
253 |             }
254 |             if ($requestId - $answerId > ($try_number - 1)) {
255 |                 throw new \Exception('got answer from old request');
256 |             }
257 | 
258 | 
259 |         } catch (\Exception $e) {
260 |             if ($try_number < $this->maxNumberOfTriesToCallApi) {
261 |                 //if got WS Exception, try to get answer again
262 |                 $answer = $this->doRequest($apiName, $data, $answerFormat, $try_number + 1, false);
263 |             } elseif ($this->resetTryNodes > 1) {
264 |                 $this->resetTryNodes = $this->resetTryNodes - 1;
265 |                 //if got WS Exception after few ties, connect to reserve node
266 |                 $this->setReserveNodeUrlToCurrentUrl();
267 |                 $answer = $this->doRequest($apiName, $data, $answerFormat, 1, false);
268 |             } else {
269 |                 //if nothing helps
270 |                 throw $e;
271 |             }
272 |         }
273 | 
274 |         return $answer;
275 |     }
276 | 
277 |     public function curlRequest($url, $type = 'get', $data = [], $curlOptions = [])
278 |     {
279 |         $ch = curl_init();
280 |         curl_setopt($ch, CURLOPT_HEADER, 0);
281 |         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
282 |         curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0");
283 | 
284 |         if ($type == 'post') {
285 |             curl_setopt($ch, CURLOPT_POST, true);
286 |             curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
287 |         } elseif ($type == 'get' && !empty($data)) {
288 |             $temp = parse_url($url);
289 |             if (!empty($temp['query'])) {
290 |                 $data = parse_str($temp['query']) + $data;
291 |             }
292 |             $temp['query'] = $data;
293 | 
294 |             $url = $this->makeUrlFromArray($temp);
295 |         }
296 | 
297 |         curl_setopt($ch, CURLOPT_URL, $url);
298 | 
299 |         foreach ($curlOptions as $option => $val) {
300 |             curl_setopt($ch, constant($option), $val);
301 |         }
302 |         if (empty($curlOptions['CURLOPT_CONNECTTIMEOUT']) && empty($curlOptions['CURLOPT_CONNECTTIMEOUT_MS'])) {
303 |             curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
304 |         }
305 | 
306 |         $response = curl_exec($ch);
307 | 
308 |         $data = [];
309 |         $data['code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
310 | 
311 |         $data['response'] = $response;
312 |         curl_close($ch);
313 | 
314 |         return $data;
315 |     }
316 | 
317 | 
318 |     public function makeUrlFromArray($data)
319 |     {
320 |         $url = '';
321 |         if (!empty($data['scheme'])) {
322 |             $url .= $data['scheme'] . '://';
323 |         }
324 |         if (!empty($data['host'])) {
325 |             $url .= $data['host'];
326 |         }
327 |         if (!empty($data['port'])) {
328 |             $url .= ':' . $data['port'];
329 |         }
330 |         if (!empty($data['path'])) {
331 |             $url .= $data['path'];
332 |         }
333 |         if (!empty($data['query'])) {
334 |             if (is_array($data['query'])) {
335 |                 $data['query'] = http_build_query($data['query']);
336 |             }
337 |             $url .= '?' . $data['query'];
338 |         }
339 | 
340 |         return $url;
341 |     }
342 | }


--------------------------------------------------------------------------------
/Tools/Transaction.php:
--------------------------------------------------------------------------------
  1 |  '782a3039b478c839e4cb0c941ff4eaeb7df40bdd68bd441afd444b9da763de12',
 25 |         self::CHAIN_STEEM       => '0000000000000000000000000000000000000000000000000000000000000000',
 26 |         self::CHAIN_VIZ         => '2040effda178d4fffff5eab7a915d4019879f5205cc5392e4bcced2b6edda0cd',
 27 |         self::CHAIN_WHALESHARES => 'de999ada2ff7ed3d3d580381f229b40b5a0261aec48eb830e540080817b72866'
 28 |     ];
 29 | 
 30 |     public static function getChainId($chainName)
 31 |     {
 32 |         if (!isset(self::CHAIN_ID[$chainName])) {
 33 |             throw new TransactionSignException('Can\'t find chain_id');
 34 |         }
 35 | 
 36 |         return self::CHAIN_ID[$chainName];
 37 |     }
 38 | 
 39 |     /**
 40 |      * @param ConnectorInterface $connector
 41 |      * @param string             $expirationTime is string in DateInterval format, example 'PT2M'
 42 |      *
 43 |      * @return CommandQueryData
 44 |      * @throws \Exception
 45 |      */
 46 |     public static function init(ConnectorInterface $connector, $expirationTime = 'PT2M')
 47 |     {
 48 |         $tx = null;
 49 | 
 50 |         $command = new GetDynamicGlobalPropertiesCommand($connector);
 51 |         $commandQueryData = new CommandQueryData();
 52 |         $properties = $command->execute(
 53 |             $commandQueryData
 54 |         );
 55 | 
 56 |         $blockId = $properties['result']['head_block_number'] - 2;
 57 |         $command = new GetBlockCommand($connector);
 58 |         $commandQueryData = new CommandQueryData();
 59 |         $commandQueryData->setParamByKey('0', $blockId);
 60 |         $block = $command->execute(
 61 |             $commandQueryData
 62 |         );
 63 | 
 64 |         if (isset($properties['result']['head_block_number']) && isset($block['result']['previous'])) {
 65 |             $refBlockNum = ($properties['result']['head_block_number'] - 3) & 0xFFFF;
 66 | 
 67 |             $tx = new CommandQueryData();
 68 |             $buf = new ByteBuffer();
 69 |             $buf->write(hex2bin($block['result']['previous']));
 70 | 
 71 |             $tx->setParams(
 72 |                 [[
 73 |                     'ref_block_num'    => $refBlockNum,
 74 |                     'ref_block_prefix' => $buf->readInt32lE(4),
 75 |                     'expiration'       => (new \DateTime($properties['result']['time']))->add(new \DateInterval($expirationTime))->format('Y-m-d\TH:i:s\.000'),
 76 |                     'operations'       => [],
 77 |                     'extensions'       => [],
 78 |                     'signatures'       => []
 79 |                 ]]
 80 |             );
 81 |         }
 82 | 
 83 |         if (!($tx instanceof CommandQueryDataInterface)) {
 84 |             throw new \Exception('cant init Tx');
 85 |         }
 86 | 
 87 |         return $tx;
 88 |     }
 89 | 
 90 |     /**
 91 |      * @param string                    $chainName
 92 |      * @param CommandQueryDataInterface $trxData
 93 |      *
 94 |      * @return string
 95 |      */
 96 |     public static function getTxMsg($chainName, CommandQueryDataInterface $trxData)
 97 |     {
 98 | 
 99 |         //serialize transaction
100 |         $trxParams = $trxData->getParams();
101 |         $serBuffer = OperationSerializer::serializeTransaction($chainName, $trxParams, new ByteBuffer());
102 |         $serializedTx = self::getChainId($chainName) . bin2hex($serBuffer->read(0, $serBuffer->length()));
103 | 
104 |         return $serializedTx;
105 |     }
106 | 
107 | 
108 |     /**
109 |      * @param string                    $chainName
110 |      * @param CommandQueryDataInterface $trxData
111 |      * @param string[]                  $privateWIFs
112 |      *
113 |      * @return mixed
114 |      * @throws \Exception
115 |      */
116 |     public static function sign($chainName, CommandQueryDataInterface $trxData, $privateWIFs)
117 |     {
118 |         //becouse spec256k1-php canonical sign trouble will use php hack.
119 |         //If sign is not canonical, we have to chang msg (we will add 1 sec to tx expiration time) and try to sign again
120 |         $nTries = 0;
121 |         while (true) {
122 |             $nTries++;
123 |             $msg = self::getTxMsg($chainName, $trxData);
124 | 
125 |             try {
126 |                 foreach ($privateWIFs as $keyName => $privateWif) {
127 |                     $index = count($trxData->getParams()[0]['signatures']);
128 | 
129 |                     /** @var CommandQueryData $trxData */
130 |                     $trxData->setParamByKey('0:signatures:' . $index, self::signOperation($msg, $privateWif));
131 |                 }
132 |                 break;
133 |             } catch (TransactionSignException $e) {
134 |                 if ($nTries > 200) {
135 |                     //stop tries to find canonical sign
136 |                     throw $e;
137 |                     break;
138 |                 } else {
139 |                     /** @var CommandQueryData $trxData */
140 |                     $params = $trxData->getParams();
141 |                     foreach ($params as $key => $tx) {
142 |                         $tx['expiration'] = (new \DateTime($tx['expiration']))
143 |                             ->add(new \DateInterval('PT0M1S'))
144 |                             ->format('Y-m-d\TH:i:s\.000');
145 |                         $params[$key] = $tx;
146 |                     }
147 |                     $trxData->setParams($params);
148 |                 }
149 |             }
150 |         }
151 | 
152 |         return $trxData;
153 |     }
154 | 
155 | 
156 | //    /**
157 | //     * @param string $msg serialized Tx with prefix chain id
158 | //     * @param string $privateWif
159 | //     *
160 | //     * @return string hex
161 | //     * @throws \Exception
162 | //     */
163 | //    protected static function signOperation($msg, $privateWif)
164 | //    {
165 | //        $context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
166 | //
167 | //        $msg32 = hash('sha256', hex2bin($msg), true);
168 | //        $privateKey = Auth::PrivateKeyFromWif($privateWif);
169 | //
170 | //        /** @var resource $signature */
171 | //        $signatureRec = null;
172 | //        $i = 0;
173 | //        while (true) {
174 | //            if ($i === 1) {
175 | //                //sing always the same
176 | //                throw new TransactionSignException("Can't to find canonical signature, {$i} ties");
177 | //            }
178 | //            $i++;
179 | ////            echo "\n i=" . print_r($i, true) . '
'; //FIXME delete it
180 | //            if (secp256k1_ecdsa_sign_recoverable($context, $signatureRec, $msg32, $privateKey) !== 1) {
181 | //                throw new TransactionSignException("Failed to create recoverable signature");
182 | //            }
183 | //
184 | //            $signature = null;
185 | //            if (secp256k1_ecdsa_recoverable_signature_convert($context, $signature, $signatureRec) !== 1) {
186 | //                throw new TransactionSignException("Failed to create signature");
187 | //            }
188 | //            $der = null;
189 | //            if (secp256k1_ecdsa_signature_serialize_der($context, $der, $signature) !== 1) {
190 | //                throw new TransactionSignException("Failed to create DER");
191 | //            }
192 | ////            echo "\n" . print_r(bin2hex($der), true) . '
'; //FIXME delete it
193 | //
194 | //            echo PHP_EOL . 'der 1: ' . print_r(bin2hex($der), true) . ''; //FIXME delete it
195 | //            if (self::isSignatureCanonical($der)) {
196 | //                break;
197 | //            }
198 | //        }
199 | //
200 | //        $serializedSig = null;
201 | //        $recid = 0;
202 | //        secp256k1_ecdsa_recoverable_signature_serialize_compact($context, $serializedSig, $recid, $signatureRec);
203 | //        echo PHP_EOL . 'serializedSig 1: ' . print_r(bin2hex($serializedSig), true) . ''; //FIXME delete it
204 | //        $serializedSig = hex2bin(base_convert($recid + 4 + 27, 10, 16)) . $serializedSig;
205 | //        $length = strlen($serializedSig);
206 | //        if ($length !== 65) {
207 | //            throw new \Exception('Expecting 65 bytes for Tx signature, instead got ' . $length);
208 | //        }
209 | //
210 | //        return bin2hex($serializedSig);
211 | //    }
212 | 
213 | 
214 |     /**
215 |      * @param string $msg serialized Tx with prefix chain id
216 |      * @param string $privateWif
217 |      *
218 |      * @return string hex
219 |      * @throws \Exception
220 |      */
221 |     protected static function signOperation($msg, $privateWif)
222 |     {
223 |         $ec = new EC('secp256k1');
224 | 
225 |         $msg32Hex = hash('sha256', hex2bin($msg), false);
226 |         $privateKeyHex = bin2hex(Auth::PrivateKeyFromWif($privateWif));
227 |         $key = $ec->keyFromPrivate($privateKeyHex, 'hex');
228 | 
229 |         $i = 0;
230 |         while (true) {
231 |             if ($i === 1) {
232 |                 //sing always the same
233 |                 throw new TransactionSignException("Can't to find canonical signature, {$i} ties");
234 |             }
235 |             $i++;
236 | 
237 |             $signature = $key->sign($msg32Hex, 'hex', ['canonical' => true]);
238 |             /** @var Signature $signature*/
239 | 
240 | 
241 |             $der = $signature->toDER('hex');
242 |             if (self::isSignatureCanonical(hex2bin($der))) {
243 |                 break;
244 |             }
245 |         }
246 | 
247 |         $recid = $ec->getKeyRecoveryParam($msg32Hex, $signature, $key->getPublic());
248 | 
249 |         $compactSign = $signature->r->toString(16) . $signature->s->toString(16);
250 |         $serializedSig = base_convert($recid + 4 + 27, 10, 16) . $compactSign;
251 | 
252 |         $length = strlen($serializedSig);
253 |         if ($length !== 130) { //65 symbols
254 |             throw new \Exception('Expecting 65 bytes for Tx signature, instead got ' . $length);
255 |         }
256 | 
257 |         return $serializedSig;
258 |     }
259 | 
260 | 
261 |     /**
262 |      * @param string $der string of binary
263 |      *
264 |      * @return bool
265 |      */
266 |     public static function isSignatureCanonical($der)
267 |     {
268 |         $buffer = new ByteBuffer();
269 |         $buffer->write($der);
270 |         $lenR = $buffer->readInt8(3);
271 |         $lenS = $buffer->readInt8(5 + $lenR);
272 | 
273 |         return $lenR === 32 && $lenS === 32;
274 |     }
275 | 
276 | 
277 | 
278 | 
279 | //    /**
280 | //     * @param string $serializedSig binary string serialized signature
281 | //     * @param string $skip skip the first byte with sing technical data (4 - compressed | 27 - compact)
282 | //     *
283 | //     * @return bool
284 | //     */
285 | //    public static function isSignatureCanonical($serializedSig, $skip)
286 | //    {
287 | //        //             test after secp256k1_ecdsa_recoverable_signature_serialize_compact
288 | //        //        public static bool IsCanonical(byte[] sig, int skip)
289 | //        //        {
290 | //        //        return !((sig[skip + 0] & 0x80) > 0)
291 | //        //        && !(sig[skip + 0] == 0 && !((sig[skip + 1] & 0x80) > 0))
292 | //        //        && !((sig[skip + 32] & 0x80) > 0)
293 | //        //        && !(sig[skip + 32] == 0 && !((sig[skip + 33] & 0x80) > 0));
294 | //        //        }
295 | //
296 | //        $buffer = new ByteBuffer();
297 | //        $buffer->write($serializedSig);
298 | //
299 | //        return !(($buffer->readInt8($skip + 0, 1) & 0x80) > 0)
300 | //            && !($buffer->readInt8($skip + 0, 1) === 0 && !(($buffer->readInt8($skip + 1, 1) & 0x80) > 0))
301 | //            && !(($buffer->readInt8($skip + 32, 1) & 0x80) > 0)
302 | //            && !($buffer->readInt8($skip + 32, 1) === 0 && !(($buffer->readInt8($skip + 33, 1) & 0x80) > 0));
303 | //    }
304 | }


--------------------------------------------------------------------------------
/Commands/GolosApiMethods.php:
--------------------------------------------------------------------------------
  1 |  [ 'apiName' => 'api_name', 'fields'=>['массив с полями из команды']]];
 11 |      *
 12 |      * @var array
 13 |      */
 14 |     public static $map = [
 15 |         'get_block'                             => [
 16 |             'apiName' => 'database_api',
 17 |             'fields'  => [
 18 |                 '0' => ['integer'], //block_id
 19 |             ]
 20 |         ],
 21 |         'get_accounts'                          => [
 22 |             'apiName' => 'database_api',
 23 |             'fields'  => [
 24 |                 '0' => ['array'], //authors
 25 |             ]
 26 |         ],
 27 |         'get_account_count'                     => [
 28 |             'apiName' => 'database_api',
 29 |             'fields'  => []
 30 |         ],
 31 |         'get_account_history'                   => [
 32 |             'apiName' => 'account_history',
 33 |             'fields'  => [
 34 |                 '0' => ['string'], //authors
 35 |                 '1' => ['integer'], //from
 36 |                 '2' => ['integer'], //limit max 10000
 37 |                 '3' => ['array'], //query
 38 |                 ]
 39 |         ],
 40 |         'get_account_votes'                     => [
 41 |             'apiName' => 'social_network',
 42 |             'fields'  => [
 43 |                 '0' => ['string'], //account name
 44 |                 '1' => ['nullOrInteger'] //voteLimit by default 10 000
 45 |             ]
 46 |         ],
 47 |         'get_active_votes'                      => [
 48 |             'apiName' => 'social_network',
 49 |             'fields'  => [
 50 |                 '0' => ['string'], //author
 51 |                 '1' => ['string'], //permlink
 52 |                 '2' => ['nullOrInteger'] //voteLimit by default 10 000
 53 |             ]
 54 |         ],
 55 |         'get_active_witnesses'                  => [
 56 |             'apiName' => 'witness_api',
 57 |             'fields'  => [
 58 |             ]
 59 |         ],
 60 |         'get_block_header'                      => [
 61 |             'apiName' => 'database_api',
 62 |             'fields'  => [
 63 |                 '0' => ['integer'], //block_id
 64 |             ]
 65 |         ],
 66 |         'get_chain_properties'         => [
 67 |             'apiName' => 'database_api',
 68 |             'fields'  => [
 69 |             ]
 70 |         ],
 71 |         'get_config'                   => [
 72 |             'apiName' => 'database_api',
 73 |             'fields'  => [
 74 |             ]
 75 |         ],
 76 |         'get_content'                           => [
 77 |             'apiName' => 'social_network',
 78 |             'fields'  => [
 79 |                 '0' => ['string'], //author
 80 |                 '1' => ['string'], //permlink
 81 |                 '2' => ['nullOrInteger'] //voteLimit by default 10 000
 82 |             ]
 83 |         ],
 84 |         'get_content_replies'                   => [
 85 |             'apiName' => 'social_network',
 86 |             'fields'  => [
 87 |                 '0' => ['string'], //author
 88 |                 '1' => ['string'], //permlink
 89 |                 '2' => ['nullOrInteger'] //voteLimit by default 10 000
 90 |             ]
 91 |         ],
 92 |         'get_current_median_history_price'      => [
 93 |             'apiName' => 'witness_api',
 94 |             'fields'  => [
 95 |             ]
 96 |         ],
 97 |         'get_discussions_by_author_before_date' => [
 98 |             'apiName' => 'tags',
 99 |             'fields'  => [
100 |                 '0' => ['string'], //'author',
101 |                 '1' => ['string'], //'start_permlink' for pagination,
102 |                 '2' => ['string'], //'before_date'
103 |                 '3' => ['integer'], //'limit'
104 |                 '4' => ['nullOrInteger'] //voteLimit by default 10 000
105 |             ]
106 |         ],
107 |         'get_discussions_by_blog'               => [
108 |             'apiName' => 'tags',
109 |             'fields'  => [
110 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
111 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
112 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
113 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
114 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
115 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
116 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
117 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
118 |             ]
119 |         ],
120 |         'get_discussions_by_comments'               => [
121 |             'apiName' => 'tags',
122 |             'fields'  => [
123 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
124 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
125 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
126 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
127 |                 '*:voteLimit' => ['nullOrInteger'] //voteLimit by default 10 000
128 |             ]
129 |         ],
130 |         'get_discussions_by_created'            => [
131 |             'apiName' => 'tags',
132 |             'fields'  => [
133 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
134 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
135 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
136 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
137 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
138 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
139 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
140 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
141 |             ],
142 |         ],
143 |         'get_discussions_by_feed'               => [
144 |             'apiName' => 'tags',
145 |             'fields'  => [
146 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
147 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
148 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
149 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
150 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
151 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
152 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
153 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
154 |             ]
155 |         ],
156 |         'get_discussions_by_trending'           => [
157 |             'apiName' => 'tags',
158 |             'fields'  => [
159 |                 '*:limit'            => ['integer'], //the discussions return amount top limit
160 |                 '*:select_tags:*'    => ['nullOrString'], //list of tags to include, posts without these tags are filtered
161 |                 '*:select_authors:*' => ['nullOrString'], //list of authors to select
162 |                 '*:truncate_body'    => ['nullOrInteger'], //the amount of bytes of the post body to return, 0 for all
163 |                 '*:start_author'     => ['nullOrString'], //the author of discussion to start searching from
164 |                 '*:start_permlink'   => ['nullOrString'], //the permlink of discussion to start searching from
165 |                 '*:parent_author'    => ['nullOrString'], //the author of parent discussion
166 |                 '*:parent_permlink'  => ['nullOrString'] //the permlink of parent discussion
167 |             ]
168 |         ],
169 |         'get_dynamic_global_properties'         => [
170 |             'apiName' => 'database_api',
171 |             'fields'  => [
172 |             ]
173 |         ],
174 |         'get_feed_history'         => [
175 |             'apiName' => 'witness_api',
176 |             'fields'  => [
177 |             ]
178 |         ],
179 |         'get_ops_in_block'                      => [
180 |             'apiName' => 'operation_history',
181 |             'fields'  => [
182 |                 '0' => ['integer'], //blockNum
183 |                 '1' => ['bool'], //onlyVirtual
184 |             ]
185 |         ],
186 |         'get_transaction'                      => [
187 |             'apiName' => 'operation_history',
188 |             'fields'  => [
189 |                 '0' => ['string'], //trxId
190 |             ]
191 |         ],
192 |         'get_trending_tags'               => [
193 |             'apiName' => 'tags',
194 |             'fields'  => [
195 |                 '0' => ['nullOrString'], //after
196 |                 '1' => ['integer'], //permlink
197 |             ]
198 |         ],
199 |         'get_witnesses_by_vote'                 => [
200 |             'apiName' => 'witness_api',
201 |             'fields'  => [
202 |                 '0' => ['string'], //from accountName, can be empty string ''
203 |                 '1' => ['integer'] //limit
204 |             ]
205 |         ],
206 |         'get_witness_by_account'                 => [
207 |             'apiName' => 'witness_api',
208 |             'fields'  => [
209 |                 '0' => ['string'] //account
210 |             ]
211 |         ],
212 |         'get_followers'                         => [
213 |             'apiName' => 'follow',
214 |             'fields'  => [
215 |                 '0' => ['string'], //author
216 |                 '1' => ['nullOrString'], //startFollower
217 |                 '2' => ['string'], //followType //blog, ignore
218 |                 '3' => ['integer'], //limit
219 |             ]
220 |         ],
221 |         'get_following'                         => [
222 |             'apiName' => 'follow',
223 |             'fields'  => [
224 |                 '0' => ['string'], //author
225 |                 '1' => ['nullOrString'], //startFollower
226 |                 '2' => ['string'], //followType //blog, ignore
227 |                 '3' => ['integer'], //limit
228 |             ]
229 |         ],
230 |         'get_follow_count'                         => [
231 |             'apiName' => 'follow',
232 |             'fields'  => [
233 |                 '0' => ['string'], //author
234 |             ]
235 |         ],
236 |         'get_version'                           => [
237 |             'apiName' => 'login_api',
238 |             'fields'  => [
239 |             ]
240 |         ],
241 |         'get_ticker'                           => [
242 |             'apiName' => 'market_history',
243 |             'fields'  => [
244 |             ]
245 |         ],
246 |         'broadcast_transaction'                 => [
247 |             'apiName' => 'network_broadcast_api',
248 |             'fields'  => [
249 |                 '0:ref_block_num'    => ['integer'],
250 |                 '0:ref_block_prefix' => ['integer'],
251 |                 '0:expiration'       => ['string'],
252 |                 '0:operations:*:0'   => ['string'],
253 |                 '0:operations:*:1'   => ['array'],
254 |                 '0:extensions'       => ['array'],
255 |                 '0:signatures'       => ['array']
256 |             ]
257 |         ],
258 |         'broadcast_transaction_synchronous'     => [
259 |             'apiName' => 'network_broadcast_api',
260 |             'fields'  => [
261 |                 '0:ref_block_num'    => ['integer'],
262 |                 '0:ref_block_prefix' => ['integer'],
263 |                 '0:expiration'       => ['string'],
264 |                 '0:operations:*:0'   => ['string'],
265 |                 '0:operations:*:1'   => ['array'],
266 |                 '0:extensions'       => ['array'],
267 |                 '0:signatures'       => ['array']
268 |             ]
269 |         ],
270 |         'get_transaction_hex'                 => [
271 |             'apiName' => 'database_api',
272 |             'fields'  => [
273 |                 '0:ref_block_num'    => ['integer'],
274 |                 '0:ref_block_prefix' => ['integer'],
275 |                 '0:expiration'       => ['string'],
276 |                 '0:operations:*:0'   => ['string'],
277 |                 '0:operations:*:1'   => ['array'],
278 |                 '0:extensions'       => ['array'],
279 |                 '0:signatures'       => ['array']
280 |             ]
281 |         ],
282 |         'get_vesting_delegations'                 => [
283 |             'apiName' => 'database_api',
284 |             'fields'  => [
285 |                 '0' => ['string'], //account
286 |                 '1' => ['string'], //from
287 |                 '2' => ['integer'], //limit <= 100
288 |                 '3' => ['string'], //type //receive, delegated
289 |             ]
290 |         ],
291 |     ];
292 | }


--------------------------------------------------------------------------------
/Tests/CommandsTest.php:
--------------------------------------------------------------------------------
  1 | setParamByKey('0', $block_id);
 37 | 
 38 |         $command = new Commands($connect);
 39 |         $command = $command->get_block();
 40 | 
 41 |         $data1 = $command->execute($commandQuery);
 42 |         $this->assertArrayHasKey('result', $data1);
 43 |         $this->assertArrayHasKey('witness', $data1['result']);
 44 |         //var_dump($data1);
 45 |     }
 46 | 
 47 |     /**
 48 |      * @dataProvider connectorsDataProvider
 49 |      */
 50 |     public function testGetAccountHistory($connect, $api)
 51 |     {
 52 |         $acc = 'semasping';
 53 |         $from = -1;
 54 |         $limit = 0;
 55 |         $command = new Commands($connect);
 56 |         $command = $command->get_account_history();
 57 | 
 58 |         $commandQuery = new CommandQueryData();
 59 |         $commandQuery->setParamByKey('0', $acc);
 60 |         $commandQuery->setParamByKey('1', $from);
 61 |         $commandQuery->setParamByKey('2', $limit);
 62 | 
 63 |         $content = $command->execute($commandQuery);
 64 |         $this->assertArrayHasKey('result', $content);
 65 | 
 66 |     }
 67 | 
 68 |     /**
 69 |      * @dataProvider connectorsDataProvider
 70 |      */
 71 |     public function testGetAccounts($connect, $api)
 72 |     {
 73 |         $acc = 'semasping';
 74 |         $command = new Commands($connect);
 75 |         $command = $command->get_accounts();
 76 | 
 77 |         $commandQuery = new CommandQueryData();
 78 |         $commandQuery->setParamByKey('0', [$acc]);
 79 |         $content = $command->execute($commandQuery);
 80 |         $this->assertArrayHasKey('result', $content);
 81 | 
 82 | 
 83 |     }
 84 | 
 85 |     /**
 86 |      * @dataProvider connectorsDataProvider
 87 |      */
 88 |     public function testGetDynamicGlobalProperties($connect, $api)
 89 |     {
 90 |         $command = new Commands($connect);
 91 |         $command = $command->get_dynamic_global_properties();
 92 | 
 93 |         $commandQuery = new CommandQueryData();
 94 |         $content = $command->execute($commandQuery);
 95 |         $this->assertArrayHasKey('result', $content);
 96 | 
 97 |     }
 98 | 
 99 |     /**
100 |      * @dataProvider connectorsDataProvider
101 |      */
102 |     public function testGetAccountCount($connect, $api)
103 |     {
104 |         $command = new Commands($connect);
105 |         $command = $command->get_account_count();
106 | 
107 |         $commandQuery = new CommandQueryData();
108 |         $content = $command->execute($commandQuery);
109 |         $this->assertArrayHasKey('result', $content);
110 |         //var_dump($content['result'][0]);
111 |     }
112 | 
113 |     /**
114 |      * @dataProvider connectorsDataProvider
115 |      */
116 |     public function testGetAccountVotes($connect, $api)
117 |     {
118 |         $acc = 'semasping';
119 |         $command = new Commands($connect);
120 |         $command = $command->get_account_votes();
121 | 
122 |         $commandQuery = new CommandQueryData();
123 |         $commandQuery->setParamByKey('0', $acc);
124 |         $content = $command->execute($commandQuery);
125 |         $this->assertArrayHasKey('result', $content);
126 |         //var_dump($content['result'][0]);
127 |     }
128 | 
129 |     /**
130 |      * @dataProvider connectorsDataProvider
131 |      */
132 |     public function testGetActiveWitnesses($connect, $api)
133 |     {
134 |         $command = new Commands($connect);
135 |         $command = $command->get_active_witnesses();
136 | 
137 |         $commandQuery = new CommandQueryData();
138 |         $content = $command->execute($commandQuery);
139 |         $this->assertArrayHasKey('result', $content);
140 |         //var_dump($content['result'][0]);
141 |     }
142 | 
143 |     /**
144 |      * @dataProvider connectorsDataProvider
145 |      */
146 |     public function testGetContent($connect, $api)
147 |     {
148 |         if ($api == 'golos') {
149 |             $author = 'semasping';
150 |             $permlink = 'accusta-zapusk-servisa-dlya-steemit';
151 |         }
152 |         if ($api == 'steem') {
153 |             $author = 'semasping';
154 |             $permlink = 'new-structure-of-commands-php-graphene-node-client';
155 |         }
156 |         $command = new Commands($connect);
157 |         $command = $command->get_content();
158 | 
159 |         $commandQuery = new CommandQueryData();
160 |         $commandQuery->setParamByKey('0', $author);
161 |         $commandQuery->setParamByKey('1', $permlink);
162 |         $content = $command->execute($commandQuery);
163 |         $this->assertArrayHasKey('result', $content);
164 |         //var_dump($content['result'][0]);
165 |     }
166 | 
167 |     /**
168 |      * @dataProvider connectorsDataProvider
169 |      */
170 |     public function testGetBlockHeader($connect, $api)
171 |     {
172 |         $block_id = 777777;
173 |         $command = new Commands($connect);
174 |         $command = $command->get_block_header();
175 | 
176 |         $commandQuery = new CommandQueryData();
177 |         $commandQuery->setParamByKey('0', $block_id);
178 |         $content = $command->execute($commandQuery);
179 |         $this->assertArrayHasKey('result', $content);
180 |         //var_dump($content['result'][0]);
181 |     }
182 | 
183 |     /**
184 |      * @dataProvider connectorsDataProvider
185 |      */
186 |     public function testGetContentReplies($connect, $api)
187 |     {
188 |         if ($api == 'golos') {
189 |             $author = 'semasping';
190 |             $permlink = 'accusta-zapusk-servisa-dlya-steemit';
191 |         }
192 |         if ($api == 'steem') {
193 |             $author = 'semasping';
194 |             $permlink = 'new-structure-of-commands-php-graphene-node-client';
195 |         }
196 |         $command = new Commands($connect);
197 |         $command = $command->get_content_replies();
198 | 
199 |         $commandQuery = new CommandQueryData();
200 |         $commandQuery->setParamByKey('0', $author);
201 |         $commandQuery->setParamByKey('1', $permlink);
202 |         $content = $command->execute($commandQuery);
203 |         $this->assertArrayHasKey('result', $content);
204 |         //var_dump($content['result'][0]);
205 |     }
206 | 
207 |     /**
208 |      * @dataProvider connectorsDataProvider
209 |      */
210 |     public function testGetCurrentMedianHistoryPrice($connect, $api)
211 |     {
212 |         $command = new Commands($connect);
213 |         $command = $command->get_current_median_history_price();
214 | 
215 |         $commandQuery = new CommandQueryData();
216 |         $content = $command->execute($commandQuery);
217 |         $this->assertArrayHasKey('result', $content);
218 |         //var_dump($content['result'][0]);
219 |     }
220 | 
221 |     /**
222 |      * @dataProvider connectorsDataProvider
223 |      */
224 |     public function testGetDiscussionsByAuthorBeforeDate($connect, $api)
225 |     {
226 |         if ($api == 'golos') {
227 |             $author = 'semasping';
228 |             $permlink = 'accusta-zapusk-servisa-dlya-steemit';
229 |             $date = '2018-01-01T00:00:00';
230 |             $limit = 10;
231 |         }
232 |         if ($api == 'steem') {
233 |             $author = 'semasping';
234 |             $permlink = 'new-structure-of-commands-php-graphene-node-client';
235 |             $date = '2018-01-01T00:00:00';
236 |             $limit = 10;
237 |         }
238 |         $command = new Commands($connect);
239 |         $command = $command->get_discussions_by_author_before_date();
240 | 
241 |         $commandQuery = new CommandQueryData();
242 |         $commandQuery->setParamByKey('0', $author);
243 |         $commandQuery->setParamByKey('1', $permlink);
244 |         $commandQuery->setParamByKey('2', $date);
245 |         $commandQuery->setParamByKey('3', $limit);
246 |         $content = $command->execute($commandQuery);
247 |         $this->assertArrayHasKey('result', $content);
248 |         //var_dump($content['result'][0]);
249 |     }
250 | 
251 |     /**
252 |      * @dataProvider connectorsDataProvider
253 |      */
254 |     public function testGetDiscussionsByBlog($connect, $api)
255 |     {
256 | 
257 |         $tag = 'php';
258 |         $limit = 10;
259 |         $command = new Commands($connect);
260 |         $command = $command->get_discussions_by_blog();
261 | 
262 |         $commandQuery = new CommandQueryData();
263 |         $commandQuery->setParamByKey('0:tag', $tag);
264 |         $commandQuery->setParamByKey('0:limit', $limit);
265 |         $commandQuery->setParamByKey('0:start_author', null);
266 |         if ($api == 'golos')
267 |             $commandQuery->setParamByKey('0:select_authors', ['semasping']);
268 |         $commandQuery->setParamByKey('0:start_permlink', null);
269 |         $content = $command->execute($commandQuery);
270 |         $this->assertArrayHasKey('result', $content);
271 |         //var_dump($content['result'][0]);
272 |     }
273 | 
274 |     /**
275 |      * @dataProvider connectorsDataProvider
276 |      */
277 |     public function testGetDiscussionsByCreated($connect, $api)
278 |     {
279 |         $tag = 'php';
280 |         $limit = 10;
281 |         $command = new Commands($connect);
282 |         $command = $command->get_discussions_by_created();
283 | 
284 |         $commandQuery = new CommandQueryData();
285 |         $commandQuery->setParamByKey('0:tag', $tag);
286 |         $commandQuery->setParamByKey('0:limit', $limit);
287 |         $commandQuery->setParamByKey('0:start_author', null);
288 |         $commandQuery->setParamByKey('0:start_permlink', null);
289 |         $content = $command->execute($commandQuery);
290 |         $this->assertArrayHasKey('result', $content);
291 |         //var_dump($content['result'][0]);
292 |     }
293 | 
294 |     /**
295 |      * @dataProvider connectorsDataProvider
296 |      */
297 |     public function testGetDiscussionsByFeed($connect, $api)
298 |     {
299 |         $tag = 'php';
300 |         $limit = 10;
301 |         $command = new Commands($connect);
302 |         $command = $command->get_discussions_by_feed();
303 | 
304 |         $commandQuery = new CommandQueryData();
305 |         $commandQuery->setParamByKey('0:tag', $tag);
306 |         $commandQuery->setParamByKey('0:limit', $limit);
307 |         $commandQuery->setParamByKey('0:start_author', null);
308 |         $commandQuery->setParamByKey('0:start_permlink', null);
309 |         if ($api == 'golos')
310 |             $commandQuery->setParamByKey('0:select_authors', ['semasping']);
311 |         $content = $command->execute($commandQuery);
312 |         $this->assertArrayHasKey('result', $content);
313 |         //var_dump($content['result'][0]);
314 |     }
315 | 
316 |     /**
317 |      * @dataProvider connectorsDataProvider
318 |      */
319 |     public function testGetDiscussionsByTrending($connect, $api)
320 |     {
321 |         $tag = 'php';
322 |         $limit = 10;
323 |         $command = new Commands($connect);
324 |         $command = $command->get_discussions_by_trending();
325 | 
326 |         $commandQuery = new CommandQueryData();
327 |         $commandQuery->setParamByKey('0:tag', $tag);
328 |         $commandQuery->setParamByKey('0:limit', $limit);
329 |         $commandQuery->setParamByKey('0:start_author', null);
330 |         $commandQuery->setParamByKey('0:start_permlink', null);
331 |         $content = $command->execute($commandQuery);
332 |         $this->assertArrayHasKey('result', $content);
333 |         //var_dump($content['result'][0]);
334 |     }
335 | 
336 |     /**
337 |      * @dataProvider connectorsDataProvider
338 |      */
339 |     public function testGetOpsInBlock($connect, $api)
340 |     {
341 |         $block_id = 777777;
342 |         $command = new Commands($connect);
343 |         $command = $command->get_ops_in_block();
344 | 
345 |         $commandQuery = new CommandQueryData();
346 |         $commandQuery->setParamByKey('0', $block_id);
347 |         $commandQuery->setParamByKey('1', true);
348 | 
349 |         $content = $command->execute($commandQuery);
350 |         $this->assertArrayHasKey('result', $content);
351 |         //var_dump($content['result'][0]);
352 |     }
353 | 
354 |     /**
355 |      * @dataProvider connectorsDataProvider
356 |      */
357 |     public function testGetTrendingCategories($connect, $api)
358 |     {
359 |         $command = new Commands($connect);
360 |         $command = $command->get_trending_tags();
361 | 
362 |         $commandQuery = new CommandQueryData();
363 |         $commandQuery->setParamByKey('0', '');
364 |         $commandQuery->setParamByKey('1', 10);
365 |         $content = $command->execute($commandQuery);
366 |         $this->assertArrayHasKey('result', $content);
367 |         //var_dump($content['result'][0]);
368 |     }
369 | 
370 |     /**
371 |      * @dataProvider connectorsDataProvider
372 |      */
373 |     public function testGetWitnessesByVote($connect, $api)
374 |     {
375 |         $acc = 'arcange';
376 |         $command = new Commands($connect);
377 |         $command = $command->get_witnesses_by_vote();
378 | 
379 |         $commandQuery = new CommandQueryData();
380 |         $commandQuery->setParamByKey('0', $acc);
381 |         $commandQuery->setParamByKey('1', 10);
382 |         $content = $command->execute($commandQuery);
383 |         $this->assertArrayHasKey('result', $content);
384 |         //var_dump($content['result'][0]);
385 |     }
386 | 
387 |     /**
388 |      * @dataProvider connectorsDataProvider
389 |      */
390 |     public function testGetFollowers($connect, $api)
391 |     {
392 |         $author = 'semasping';
393 | 
394 |         $command = new Commands($connect);
395 |         $command = $command->get_followers();
396 | 
397 |         $commandQuery = new CommandQueryData();
398 |         $commandQuery->setParamByKey('0', $author);
399 |         $commandQuery->setParamByKey('1', null);
400 |         $commandQuery->setParamByKey('2', 'blog');
401 |         $commandQuery->setParamByKey('3', 10);
402 |         $content = $command->execute($commandQuery);
403 |         $this->assertArrayHasKey('result', $content);
404 |         //var_dump($content['result'][0]);
405 |     }
406 | 
407 | }
408 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # php-graphene-node-client
  2 | PHP client for connection to [VIZ](https://github.com/viz-blockchain)/[STEEM](https://github.com/steemit)/[GOLOS](https://github.com/golos-blockchain/)/[WHALESHARES](https://gitlab.com/beyondbitcoin) node
  3 | 
  4 | 
  5 | ## Install Via Composer
  6 | #### For readonly, without broadcast
  7 | ```
  8 | composer require t3ran13/php-graphene-node-client
  9 | ```
 10 | #### with broadcast (sending transactions to blockchain)
 11 | actual dockerfile and requests examples see in branch ["debug"](https://github.com/t3ran13/php-graphene-node-client/tree/debug)
 12 | 
 13 | install components
 14 | - libgmp-dev
 15 | 
 16 | install extensions
 17 | - gmp
 18 | 
 19 | 
 20 | 
 21 | ## Basic Usage
 22 | ```php
 23 |  $limit,
 37 |         'select_tags' => ['golos'], // for GOLOS
 38 |         'tag'         => 'steemit', // for STEEMIT     
 39 |     ]
 40 | ];
 41 | $commandQuery->setParams($data);
 42 | 
 43 | //OR 
 44 | $commandQuery = new CommandQueryData();
 45 | $commandQuery->setParamByKey('0:limit', $limit);
 46 | $commandQuery->setParamByKey('0:select_tags', [$tag]);
 47 | $commandQuery->setParamByKey('0:tag', $tag);
 48 | 
 49 | 
 50 | //and use single command
 51 | $command = new GetDiscussionsByCreatedCommand(new GolosWSConnector());
 52 | $golosPosts = $command->execute(
 53 |     $commandQuery
 54 | );
 55 | 
 56 | //or commands aggregator class
 57 | $commands = new Commands(new GolosWSConnector());
 58 | $golosPosts = $commands->get_discussions_by_created()
 59 |     ->execute(
 60 |        $commandQuery
 61 | );
 62 | 
 63 | 
 64 | // will return
 65 | // [
 66 | //      "id" => 1,
 67 | //      "result" => [
 68 | //            [
 69 | //                "id": 466628,
 70 | //                "author": "piranya",
 71 | //                "permlink": "devyatyi-krug",
 72 | //                ...
 73 | //            ],
 74 | //            ...
 75 | //      ]
 76 | // ]
 77 |   
 78 |   
 79 | //single command  
 80 | $command = new GetDiscussionsByCreatedCommand(new SteemitWSConnector());
 81 | $steemitPosts = $command->execute(
 82 |     $commandQuery,
 83 |     'result',
 84 |     SteemitWSConnector::ANSWER_FORMAT_ARRAY // or SteemitWSConnector::ANSWER_FORMAT_OBJECT
 85 | );
 86 | 
 87 | //or commands aggregator class
 88 | $commands = new Commands(new GolosWSConnector());
 89 | $golosPosts = $commands->get_discussions_by_created()
 90 |     ->execute(
 91 |         $commandQuery,
 92 |         'result',
 93 |         SteemitWSConnector::ANSWER_FORMAT_ARRAY // or SteemitWSConnector::ANSWER_FORMAT_OBJECT
 94 | );
 95 | 
 96 | 
 97 | // will return
 98 | // [
 99 | //      [
100 | //          "id": 466628,
101 | //          "author": "piranya",
102 | //          "permlink": "devyatyi-krug",
103 | //          ...
104 | //      ],
105 | //      ...
106 | // ]
107 | 
108 | 
109 | ```
110 |   
111 |    
112 | 
113 | ## Implemented Commands List
114 | 
115 | ### Single Commands
116 | - BroadcastTransactionCommand
117 | - BroadcastTransactionSynchronousCommand
118 | - GetAccountCountCommand
119 | - GetAccountHistoryCommand
120 | - GetAccountsCommand
121 | - GetAccountVotesCommand
122 | - GetActiveWitnessesCommand
123 | - GetApiByNameCommand //ONLY STEEM/whaleshares
124 | - GetBlockCommand
125 | - GetBlockHeaderCommand
126 | - GetConfigCommand
127 | - GetContentCommand
128 | - GetContentRepliesCommand
129 | - GetCurrentMedianHistoryPriceCommand //STEEM/GOLOS
130 | - GetDiscussionsByAuthorBeforeDateCommand
131 | - GetDiscussionsByBlogCommand
132 | - GetDiscussionsByCreatedCommand
133 | - GetDiscussionsByFeedCommand
134 | - GetDiscussionsByTrendingCommand
135 | - GetDynamicGlobalPropertiesCommand
136 | - GetFollowersCommand
137 | - GetOpsInBlock
138 | - GetTransactionHexCommand
139 | - GetTrendingCategoriesCommand //only steem/whaleshares
140 | - GetVersionCommand
141 | - GetWitnessesByVoteCommand
142 | - LoginCommand //ONLY for STEEM/whaleshares
143 | 
144 | All single commands can be called through Commands Class as methods (example: (new Commands)->get_block()->execute(...) )
145 | 
146 | 
147 | ### broadcast operations templates
148 | 
149 | namespace GrapheneNodeClient\Tools\ChainOperations
150 | 
151 | - vote
152 | - transfer
153 | - comment // steem or golos
154 | - content // only viz
155 | - witness_update
156 | 
157 | ```php
158 |  5
181 | //    [result] => Array
182 | //        (
183 | //            [id] => a2c52988ea870e446480782ff046994de2666e0d
184 | //            [block_num] => 17852337
185 | //            [trx_num] => 1
186 | //            [expired] =>
187 | //        )
188 | //
189 | //)
190 | 
191 | ```
192 | 
193 | ## Implemented Connectors List
194 | 
195 | namespace: GrapheneNodeClient\Connectors\WebSocket OR GrapheneNodeClient\Connectors\Http;
196 | 
197 | - VizWSConnector
198 | - VizHttpJsonRpcConnector
199 | - GolosWSConnector
200 | - GolosHttpJsonRpcConnector
201 | - SteemitWSConnector
202 | - SteemitHttpJsonRpcConnector
203 | 
204 | List of available STEEM nodes are [here](https://www.steem.center/index.php?title=Public_Websocket_Servers)
205 | 
206 | 
207 | #### Switching between connectors 
208 | ```php
209 | setParamByKey('0', 'author');
219 | $commandQuery->setParamByKey('1', 'permlink');
220 | 
221 | //OR
222 | $commandQuery = new CommandQueryData();
223 | $commandQuery->setParams(
224 |     [
225 |         0 => "author",
226 |         1 => "permlink"
227 |     ]
228 | );
229 | 
230 | $content = $command->execute(
231 |     $commandQuery
232 | );
233 | // will return
234 | // [
235 | //      "id" => 1,
236 | //      "result" => [
237 | //            ...
238 | //      ]
239 | // ]
240 | 
241 | 
242 | ```
243 | 
244 |    
245 | 
246 | ## Creating Own Connector
247 | ```php
248 |  [ 'apiName' => 'api_name', 'fields'=>['массив с полями из команды']]];
347 |     protected static $map = [
348 |         //...
349 |         'broadcast_transaction'                 => [
350 |             'apiName' => 'network_broadcast_api',
351 |             'fields'  => [
352 |                 '0:ref_block_num'    => ['integer'],
353 |                 '0:ref_block_prefix' => ['integer'],
354 |                 '0:expiration'       => ['string'],
355 |                 '0:operations:*:0'   => ['string'],
356 |                 '0:operations:*:1'   => ['array'],
357 |                 '0:extensions'       => ['array'],
358 |                 '0:signatures'       => ['array']
359 |             ]
360 |         ],
361 |         //...
362 |         'broadcast_transaction'                 => [
363 |             'apiName' => 'your_method',
364 |             'fields'  => [
365 |                 //your fields
366 |             ]
367 |         ]
368 |     ];
369 | }
370 | 
371 | 
372 | ```  
373 | 
374 | # Tools
375 | ## Transliterator
376 | 
377 | 
378 | ```php
379 | setParamByKey(
424 |     '0:operations:0',
425 |     [
426 |         'vote',
427 |         [
428 |             'voter'    => $voter,
429 |             'author'   => $author,
430 |             'permlink' => $permlink,
431 |             'weight'   => $weight
432 |         ]
433 |     ]
434 | );
435 | $command = new BroadcastTransactionSynchronousCommand($connector);
436 | Transaction::sign($chainName, $tx, ['posting' => $publicWif]);
437 | 
438 | $trxString = mb_strlen(json_encode($tx->getParams()), '8bit');
439 | if (Bandwidth::isEnough($connector, $voter, 'market', $trxString)) {
440 | 	$answer = $command->execute(
441 | 		$tx
442 | 	);
443 | }
444 | 
445 | //or other way
446 | 
447 | $bandwidth = Bandwidth::getBandwidthByAccountName($voter, 'market', $connector);
448 | 
449 | //Array
450 | //(
451 | //    [used] => 3120016
452 | //    [available] => 148362781
453 | //)
454 | 
455 | if ($trxString * 10 + $bandwidth['used'] < $bandwidth['available']) {
456 | 	$answer = $command->execute(
457 | 		$tx
458 | 	);
459 | }
460 | 
461 | // 
462 | 
463 | ```
464 | 
465 | 
466 | ## Transaction for blockchain (broadcast)
467 | 
468 | 
469 | ```php
470 | setParamByKey(
482 |     '0:operations:0',
483 |     [
484 |         'vote',
485 |         [
486 |             'voter'    => $voter,
487 |             'author'   => $author,
488 |             'permlink' => $permlink,
489 |             'weight'   => $weight
490 |         ]
491 |     ]
492 | );
493 | 
494 | $command = new BroadcastTransactionSynchronousCommand($connector);
495 | Transaction::sign($chainName, $tx, ['posting' => $publicWif]);
496 | 
497 | $answer = $command->execute(
498 |     $tx
499 | );
500 | 
501 | ```
502 | 
503 | 
504 | ** WARNING**
505 | 
506 | 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.
507 | It is was solved with php-hack in Transaction::sign()
508 | ```php
509 | ...
510 | //becouse spec256k1-php canonical sign trouble will use php hack.
511 | //If sign is not canonical, we have to chang msg (we will add 1 sec to tx expiration time) and try to sign again
512 | $nTries = 0;
513 | while (true) {
514 |     $nTries++;
515 |     $msg = self::getTxMsg($chainName, $trxData);
516 |     echo '
' . print_r($trxData->getParams(), true) . '
'; //FIXME delete it
517 | 
518 |     try {
519 |         foreach ($privateWIFs as $keyName => $privateWif) {
520 |             $index = count($trxData->getParams()[0]['signatures']);
521 | 
522 |             /** @var CommandQueryData $trxData */
523 |             $trxData->setParamByKey('0:signatures:' . $index, self::signOperation($msg, $privateWif));
524 |         }
525 |         break;
526 |     } catch (TransactionSignException $e) {
527 |         if ($nTries > 200) {
528 |             //stop tries to find canonical sign
529 |             throw $e;
530 |             break;
531 |         } else {
532 |             /** @var CommandQueryData $trxData */
533 |             $params = $trxData->getParams();
534 |             foreach ($params as $key => $tx) {
535 |                 $tx['expiration'] = (new \DateTime($tx['expiration']))
536 |                     ->add(new \DateInterval('PT0M1S'))
537 |                     ->format('Y-m-d\TH:i:s\.000');
538 |                 $params[$key] = $tx;
539 |             }
540 |             $trxData->setParams($params);
541 |         }
542 |     }
543 | ...
544 | ```
545 | 
546 | ## Tests
547 | You need to install PhpUnit in your system (https://phpunit.de/manual/3.7/en/installation.html)
548 | ```
549 | cd Tests
550 | phpunit CommandsTest.php 
551 | phpunit CommandsTest.php --filter=testGetBlock // test only one command
552 | ```


--------------------------------------------------------------------------------