4 use Graze\GuzzleHttp\JsonRpc\Client as RpcClient;
49 public $debugHtml =
'';
69 public function __construct(
string $url =
'http://localhost:8545')
73 require_once __DIR__ .
'/helpers/ethereum-client-workaround-helpers.php';
75 $this->client = RpcClient::factory($url, [
81 $this->definition = self::getDefinition();
83 foreach ($this->definition[
'methods'] as $name => $params) {
84 ${$name} =
function () {
89 $method = debug_backtrace()[2][
'args'][0];
90 $this->debug(
'Called function name', $method);
93 $param_definition = $this->definition[
'methods'][$method];
96 $valid_arguments = $param_definition[0];
97 $argument_class_names = [];
98 if (count($valid_arguments)) {
99 $this->debug(
'Valid arguments', $valid_arguments);
102 foreach ($valid_arguments as $type) {
104 if ($primitiveType) {
105 $argument_class_names[] = $primitiveType;
107 $argument_class_names[] = $type;
110 $this->debug(
'Valid arguments class names', $argument_class_names);
114 $args = func_get_args();
115 if (count($args) && isset($argument_class_names)) {
116 $this->debug(
'Arguments', $args);
119 foreach ($args as $i => $arg) {
122 if (is_subclass_of ($arg,
'Ethereum\DataType\EthDataType')) {
125 $argType = basename(str_replace(
'\\',
'/', get_class($arg)));
126 if ($argument_class_names[$i] !== $argType) {
127 throw new \InvalidArgumentException(
"Argument $i is " 129 .
" but expected $argument_class_names[$i] in $method().");
134 if ($arg->isPrimitive() && $argType !==
'EthB') {
136 if ($method ===
'eth_getFilterChanges') {
138 $request_params[] = $arg->hexValUnpadded();
141 $request_params[] = $arg->hexVal();
144 elseif ($arg->isPrimitive() && $argType ===
'EthB') {
145 $request_params[] = $arg->val();
148 $request_params[] = $arg->toArray();
154 throw new \InvalidArgumentException(
'Arg ' . $i .
' is not a EthDataType.');
160 if (isset($param_definition[2])) {
161 $required_params = array_slice($param_definition[0], 0, $param_definition[2]);
162 $this->debug(
'Required Params', $required_params);
165 if (isset($required_params) && count($required_params)) {
166 foreach ($required_params as $i => $param) {
167 if (!isset($request_params[$i])) {
168 throw new \InvalidArgumentException(
"Required argument $i $argument_class_names[$i] is missing in $method().");
175 $require_default_block =
false;
176 if (isset($param_definition[3])) {
177 $require_default_block = $param_definition[3];
178 $this->debug(
'Require default block parameter', $require_default_block);
180 if ($require_default_block) {
182 foreach ($argument_class_names as $i => $class) {
183 if ($class ===
'EthBlockParam' && !isset($request_params[$i])) {
184 $request_params[$i] =
'latest';
190 $return_type = $param_definition[1];
191 $this->debug(
'Return value type', $return_type);
195 if (is_array($return_type)) {
196 $return_type_class =
'[' .
EthD::typeMap($return_type[0]) .
']';
197 } elseif ($is_primitive) {
201 $return_type_class = $return_type;
203 $this->debug(
'Return value Class name ', $return_type_class);
206 $this->debug(
'Final request params', $request_params);
207 $value = $this->etherRequest($method, $request_params);
210 $functionName =
'eth_workaround_' . $method;
211 if (function_exists($functionName)) {
212 $value = call_user_func($functionName, $value);
215 $return = $this->createReturnValue($value, $return_type_class, $method);
216 $this->debug(
'Final return object', $return);
217 $this->debug(
'<hr />');
222 $this->methods[$name] = \Closure::bind(${$name}, $this, get_class());
234 public function __call(
string $method, array $args)
236 if (is_callable($this->methods[$method])) {
237 return call_user_func_array($this->methods[$method], $args);
239 throw new \InvalidArgumentException(
'Unknown Method: ' . $method);
259 private function createReturnValue($value,
string $return_type_class,
string $method)
263 if (is_null($value)) {
270 $array_val = $this->isArrayType($return_type_class);
272 $is_primitive = $class_name::isPrimitive();
275 if ($is_primitive && $array_val && is_array($value)) {
277 $return = $this->valueArray($value, $class_name);
278 } elseif ($is_primitive && !$array_val && !is_array($value)) {
279 $return =
new $class_name($value);
283 if (!$is_primitive && !$array_val && is_array($value)) {
284 $return = $this->arrayToComplexType($class_name, $value);
286 elseif (!$is_primitive) {
289 if ($method ===
'eth_getFilterChanges') {
291 $return = $this->handleFilterChangeValues($value);
293 elseif ($method ===
'shh_getFilterChanges') {
294 throw new \Exception(
'shh_getFilterChanges not implemented.');
298 throw new \Exception(
' Return is a array of non primitive types. Method: ' . $method);
302 $return =
new $class_name();
306 if (!$return && !is_array($return)) {
311 .
' (), couldn not be decoded. Value was: ' 312 . print_r($value,
true));
327 return (strpos($type,
'[') !== FALSE );
339 public function request(
string $method, array $params = [])
342 return $this->client->send($this->client->request($this->id, $method, $params))->getRpcResult();
359 return $this->request($method, $params);
361 if ($e->getCode() === 405) {
365 'message' => $e->getMessage(),
385 public function debug(
string $title, $content = null)
388 $return .=
'<p style="margin-left: 1em"><b>' . $title .
"</b></p>";
390 $return .=
'<pre style="background: rgba(0,0,0, .1); margin: .5em; padding: .25em; ">';
391 if (is_object($content) || is_array($content)) {
394 $return .= ob_get_clean();
396 $return .= ($content);
400 $this->debugHtml .= $return;
423 foreach ($values as $val) {
425 if (is_array($val)) {
429 if (substr($type, 0, 1) ===
'[') {
431 $className =
'\Ethereum\Datatype\\' . str_replace([
'[',
']'],
'', $type);
433 foreach ($val[$key] as $subVal) {
434 $sub[] =
new $className($subVal);
441 $className =
'\Ethereum\Datatype\\' . $type;
442 $processed[] = isset($val[$key]) ?
new $className($val[$key]) : null;
451 $return[] =
new EthD32($val);
473 protected static function arrayToComplexType(
string $class_name, array $values)
477 if (!substr($class_name, 1,8) === __NAMESPACE__) {
478 $class_name = __NAMESPACE__ .
"\\$class_name";
482 $functionName =
'eth_workaround_' . strtolower(str_replace(
'\\',
'_', substr($class_name,1)));
483 if (function_exists($functionName)) {
484 $values = call_user_func($functionName, $values);
489 $type_map = $class_name::getTypeArray();
492 foreach ($type_map as $name => $val_class) {
494 if (isset($values[$name])) {
498 if (is_array($values[$name])) {
500 foreach ($values[$name] as $sub_val) {
507 if (!is_array($sub_val) && $name ===
'transactions') {
508 $val_class =
'\\Ethereum\\DataType\\EthD32';
512 if (is_array($sub_val)) {
513 $sub_values[] = self::arrayToComplexType($val_class, $sub_val);
516 $sub_values[] =
new $val_class($sub_val);
519 $class_values[] = $sub_values;
522 $class_values[] =
new $val_class($values[$name]);
527 $class_values[] = null;
530 $return =
new $class_name(...$class_values);
531 if (!$return && !is_array($return)) {
532 throw new \Exception(
'Expected ' . $return_type_class .
' at ' . $method .
' (), couldn not be decoded. Value was: ' . print_r($value, TRUE));
552 return EcRecover::personalEcRecover($message, $signature->
hexVal());
571 public static function valueArray(array $values,
string $typeClass)
574 if (!class_exists($typeClass)) {
575 $typeClass =
'\\' . __NAMESPACE__ .
'\\DataType\\' . $typeClass;
577 foreach ($values as $i => $val) {
578 if (is_object($val)) {
579 $return[$i] = $val->toArray();
581 if (is_array($val)) {
582 $return[$i] = self::arrayToComplexType($typeClass, $val);
584 $return[$i] =
new $typeClass($val);
static personalEcRecover(string $message, EthD $signature)
static handleFilterChangeValues(array $values)
static getTypeClass(string $type, bool $typed_constructor=false)
request(string $method, array $params=[])
static typeMap(string $type)
__construct(string $url='http://localhost:8545')
static valueArray(array $values, string $typeClass)
static isArrayType(string $type)
__call(string $method, array $args)
debug(string $title, $content=null)
etherRequest(string $method, array $params=[])