├── .gitignore ├── .travis.yml ├── Commands ├── CommandInterface.php ├── CommandQueryData.php ├── CommandQueryDataException.php ├── CommandQueryDataInterface.php ├── Commands.php ├── GolosApiMethods.php ├── HiveApiMethods.php ├── Single │ ├── BroadcastTransactionCommand.php │ ├── BroadcastTransactionSynchronousCommand.php │ ├── CommandAbstract.php │ ├── GetAccountCountCommand.php │ ├── GetAccountHistoryCommand.php │ ├── GetAccountVotesCommand.php │ ├── GetAccountsCommand.php │ ├── GetActiveVotesCommand.php │ ├── GetActiveWitnessesCommand.php │ ├── GetApiByNameCommand.php │ ├── GetBlockCommand.php │ ├── GetBlockHeaderCommand.php │ ├── GetChainPropertiesCommand.php │ ├── GetConfigCommand.php │ ├── GetContentCommand.php │ ├── GetContentRepliesCommand.php │ ├── GetCurrentMedianHistoryPriceCommand.php │ ├── GetDiscussionsByAuthorBeforeDateCommand.php │ ├── GetDiscussionsByBlogCommand.php │ ├── GetDiscussionsByCreatedCommand.php │ ├── GetDiscussionsByFeedCommand.php │ ├── GetDiscussionsByTrendingCommand.php │ ├── GetDynamicGlobalPropertiesCommand.php │ ├── GetFeedHistoryCommand.php │ ├── GetFollowCountCommand.php │ ├── GetFollowersCommand.php │ ├── GetFollowingCommand.php │ ├── GetOpsInBlock.php │ ├── GetTransaction.php │ ├── GetTransactionHexCommand.php │ ├── GetTrendingCategoriesCommand.php │ ├── GetTrendingTagsCommand.php │ ├── GetVersionCommand.php │ ├── GetVestingDelegationsCommand.php │ ├── GetWitnessByAccountCommand.php │ ├── GetWitnessesByVoteCommand.php │ ├── LoginCommand.php │ ├── getDiscussionsByCommentsCommand.php │ ├── getDiscussionsByContentsCommand.php │ ├── getRewardFundCommand.php │ └── getTickerCommand.php ├── SteemitApiMethods.php ├── VizApiMethods.php └── WhalesharesApiMethods.php ├── Connectors ├── ConnectorInterface.php ├── Http │ ├── GolosHttpJsonRpcConnector.php │ ├── HiveHttpJsonRpcConnector.php │ ├── HttpJsonRpcConnectorAbstract.php │ ├── SteemitHttpJsonRpcConnector.php │ ├── VizHttpJsonRpcConnector.php │ └── WhalesharesHttpJsonRpcConnector.php ├── InitConnector.php └── WebSocket │ ├── GolosWSConnector.php │ ├── SteemitWSConnector.php │ ├── VizWSConnector.php │ ├── WSConnectorAbstract.php │ └── WhalesharesWSConnector.php ├── LICENSE ├── README.md ├── Tests └── CommandsTest.php ├── Tools ├── Auth.php ├── Bandwidth.php ├── ChainOperations │ ├── ChainOperations.php │ ├── ChainOperationsGolos.php │ ├── ChainOperationsHive.php │ ├── ChainOperationsSteem.php │ ├── ChainOperationsViz.php │ ├── ChainOperationsWhaleshares.php │ ├── OpComment.php │ ├── OpContent.php │ ├── OpTransfer.php │ ├── OpVote.php │ └── OperationSerializer.php ├── Reputation.php ├── Transaction.php ├── TransactionSignException.php ├── TransactionSignExceptionInterface.php └── Transliterator.php ├── composer.json └── examples └── Broadcast ├── Donate.php ├── GetTransactionHex.php └── WitnessUpdate.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | -------------------------------------------------------------------------------- /Commands/CommandInterface.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/CommandQueryDataException.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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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/Single/BroadcastTransactionCommand.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 | } -------------------------------------------------------------------------------- /Commands/Single/GetAccountCountCommand.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 | } -------------------------------------------------------------------------------- /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/ConnectorInterface.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 | } -------------------------------------------------------------------------------- /Connectors/Http/SteemitHttpJsonRpcConnector.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 | } -------------------------------------------------------------------------------- /Connectors/WebSocket/WhalesharesWSConnector.php: -------------------------------------------------------------------------------- 1 | $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 | ```


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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/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/ChainOperations.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/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/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 | }


--------------------------------------------------------------------------------
/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/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/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 | }


--------------------------------------------------------------------------------
/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/Reputation.php:
--------------------------------------------------------------------------------
 1 |  0 ? 1 : -1) + $addNumber;
21 |         }
22 | 
23 | 
24 |         return round($rating, $precision);
25 |     }
26 | }


--------------------------------------------------------------------------------
/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 | }


--------------------------------------------------------------------------------
/Tools/TransactionSignException.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 | }


--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "t3ran13/php-graphene-node-client",
 3 |     "description": "PHP client for connection to Graphene node (steem/golos)",
 4 |     "keywords": ["Web Socket client for graphene node", "Graphene", "Steemit", "Golos"],
 5 |     "homepage": "https://github.com/t3ran13/php-graphene-node-client",
 6 |     "license": "MIT",
 7 |     "authors": [
 8 |         {
 9 |             "name": "Tkachew",
10 |             "email": "7tkachew@gmail.com"
11 |         }
12 |     ],
13 |     "require": {
14 |         "php": "^7.0",
15 |         "textalk/websocket": "^1.2",
16 |         "stephenhill/base58": "^1.1",
17 |         "t3ran13/bytebuffer": "^1.0",
18 |         "simplito/elliptic-php": "^1.0"
19 |     },
20 |     "autoload": {
21 |         "psr-4": { "GrapheneNodeClient\\": "" }
22 |     },
23 |     "require-dev": {
24 |         "phpunit/phpunit": "6"
25 |     }
26 | }
27 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/examples/Broadcast/GetTransactionHex.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 | 


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