├── .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 | --------------------------------------------------------------------------------