├── .env.example ├── .gitignore ├── .php_cs.dist ├── LICENSE ├── README.md ├── composer.json ├── examples ├── chain.php ├── eosrpc.php └── wallet.php ├── phpunit.xml ├── src ├── Adapter │ ├── Http │ │ ├── CurlAdapter.php │ │ ├── GuzzleAdapter.php │ │ └── HttpInterface.php │ └── Settings │ │ ├── DotenvAdapter.php │ │ └── SettingsInterface.php ├── ChainController.php ├── ChainFactory.php ├── EosRpc.php ├── Exception │ ├── EosRpcException.php │ ├── EosRpcThrowable.php │ ├── HttpException.php │ ├── SettingsException.php │ └── SettingsNotFoundException.php ├── WalletController.php └── WalletFactory.php └── tests ├── ControllerTest.php └── FactoryTest.php /.env.example: -------------------------------------------------------------------------------- 1 | RPC_NODE_URL=https://eosapi.blockmatrix.network 2 | RPC_KEOSD_URL=http://127.0.0.1:8900 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .DS_Store 4 | .idea/ 5 | .env 6 | .php_cs.cache 7 | tests/coverage 8 | tests/static 9 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | setRules([ 5 | '@PSR2' => true, 6 | 'no_leading_import_slash' => true, 7 | 'no_trailing_comma_in_singleline_array' => true, 8 | 'no_singleline_whitespace_before_semicolons' => true, 9 | 'no_unused_imports' => true, 10 | 'concat_space' => ['spacing' => 'one'], 11 | 'no_whitespace_in_blank_line' => true, 12 | 'ordered_imports' => true, 13 | 'single_quote' => true, 14 | 'no_empty_statement' => true, 15 | 'no_extra_consecutive_blank_lines' => true, 16 | 'phpdoc_no_package' => true, 17 | 'phpdoc_scalar' => true, 18 | 'no_blank_lines_after_phpdoc' => true, 19 | 'array_syntax' => ['syntax' => 'short'], 20 | 'whitespace_after_comma_in_array' => true, 21 | 'function_typehint_space' => true, 22 | 'hash_to_slash_comment' => true, 23 | 'lowercase_cast' => true, 24 | 'no_leading_namespace_whitespace' => true, 25 | 'native_function_casing' => true, 26 | 'no_short_bool_cast' => true, 27 | 'no_unneeded_control_parentheses' => true, 28 | 'ternary_to_null_coalescing' => true, 29 | 'visibility_required' => true, 30 | 'cast_spaces' => true, 31 | 'phpdoc_add_missing_param_annotation' => true, 32 | 'phpdoc_align' => true, 33 | 'phpdoc_indent' => true, 34 | 'phpdoc_order' => true, 35 | 'phpdoc_scalar' => true, 36 | 'phpdoc_single_line_var_spacing' => true, 37 | 'no_empty_phpdoc' => true, 38 | 'trailing_comma_in_multiline_array' => true, 39 | 'binary_operator_spaces' => ['align_double_arrow' => true], 40 | ]) 41 | ->setFinder( 42 | PhpCsFixer\Finder::create() 43 | ->in(__DIR__ . '/src') 44 | ->in(__DIR__ . '/tests') 45 | ) 46 | ; 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Block Matrix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The PHP SDK for the EOS RPC API 2 | 3 | A PHP wrapper for the EOS Chain/Wallet RPC API. 4 | 5 | ## Background 6 | 7 | You can check out the [RPC API reference](https://developers.eos.io/eosio-nodeos/reference) but 8 | beware that some of the newer methods are missing. 9 | Wallet RPC APIs are implemented as [v1.1.0 of RPC API reference](https://developers.eos.io/eosio-nodeos/v1.1.0/reference). 10 | Also, some of the examples in those docs use outdated syntax. 11 | 12 | ## Installing 13 | 14 | ```php 15 | composer require manamine/php-eos-rpc-sdk 16 | ``` 17 | 18 | ## Configuration 19 | 20 | Create a dotenv `.env` file in the project root, with your favorite RPC API host and KEOSD. You can use 21 | `.env.example` as a template: 22 | 23 | ``` 24 | cp .env.example .env 25 | ``` 26 | 27 | ## Usage 28 | 29 | There is a shiny factory method to auto instantiate all dependencies: 30 | 31 | ```php 32 | $api = (new ChainFactory)->api(); 33 | $walapi = (new WalletFactory)->api(); 34 | $eos = (new EosRpc($api, $walapi)); 35 | ``` 36 | 37 | ## Examples 38 | 39 | To get you started, there is a simple example runner which covers all API commands. 40 | 41 | Just run this via cli to see example output for all commands: 42 | 43 | ``` 44 | cd examples 45 | php chain.php 46 | php wallet.php 47 | php eosrpc.php 48 | ``` 49 | 50 | ## API Methods 51 | 52 | Almost Chain/Wallet API methods are covered. 53 | 54 | ## Chain APIs 55 | 56 | ### Get Info 57 | 58 | Get latest information related to a node 59 | 60 | ```php 61 | echo $api->getInfo(); 62 | ``` 63 | 64 | ### Get Block 65 | 66 | Get information related to a block 67 | 68 | ```php 69 | echo $api->getBlock("1337"); 70 | ``` 71 | 72 | ### Get Block Header State 73 | 74 | Get information related to a block header state 75 | 76 | ```php 77 | echo $api->getBlockHeaderState("0016e48707b181d93117b07451d9837526eba34a9a37125689fb5a73a5d28a38"); 78 | ``` 79 | 80 | ### Get Account 81 | 82 | Get information related to an account 83 | 84 | ```php 85 | $api->getAccount("blockmatrix1"); 86 | ``` 87 | 88 | ### Get Code 89 | 90 | Fetch smart contract code 91 | 92 | ```php 93 | echo $api->getCode("eosio.token"); 94 | ``` 95 | 96 | ### Get Table Rows 97 | 98 | Fetch smart contract data from an account 99 | 100 | ```php 101 | echo $api->getTableRows("eosio", "eosio", "producers", ["limit" => 10]); 102 | ``` 103 | 104 | ### Get Currency Balance 105 | 106 | Fetch currency balance(s) for an account 107 | 108 | ```php 109 | echo $api->getCurrencyBalance("eosio.token", "eosdacserver"); 110 | ``` 111 | 112 | ### Get Currency Stats 113 | 114 | Fetch stats for a currency 115 | 116 | ```php 117 | echo $api->getCurrencyStats("eosio.token", "EOS"); 118 | ``` 119 | 120 | ### Get ABI 121 | 122 | Get an account ABI 123 | 124 | ```php 125 | echo $api->getAbi("eosio.token"); 126 | ``` 127 | 128 | ### Get Raw Code and ABI 129 | 130 | Get raw code and ABI 131 | 132 | ```php 133 | echo $api->getRawCodeAndAbi("eosio.token"); 134 | ``` 135 | 136 | ### Get Producers 137 | 138 | List the producers 139 | 140 | ```php 141 | echo $api->getProducers(10); 142 | ``` 143 | 144 | ### ABI JSON To Bin 145 | 146 | Serialize json to binary hex 147 | 148 | ```php 149 | echo $api->abiJsonToBin("eosio.token", "transfer", ["blockmatrix1", "blockmatrix1", "7.0000 EOS", "Testy McTest"]); 150 | ``` 151 | 152 | ### ABI Bin To JSON 153 | 154 | Serialize back binary hex to json 155 | 156 | ```php 157 | echo $api->abiBinToJson("eosio.token", "transfer", "10babbd94888683c10babbd94888683c701101000000000004454f53000000000c5465737479204d6354657374"); 158 | ``` 159 | 160 | ### Get Required Keys 161 | 162 | Get the required keys needed to sign a transaction 163 | 164 | ```php 165 | echo $api->getRequiredKeys( 166 | [ 167 | "expiration" => "2018-08-23T05.00.00", 168 | "ref_block_num" => 15078, 169 | "ref_block_prefix" => 1071971392, 170 | "max_net_usage_words" => 0, 171 | "delay_sec" => 0, 172 | "context_free_actions" => [], 173 | "actions" => [ 174 | [ 175 | "account" => "eosio.token", 176 | "name" => "transfer", 177 | "authorization" => [ 178 | [ 179 | "actor" => "user", 180 | "permission" => "active" 181 | ] 182 | ], 183 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 184 | ] 185 | ], 186 | "transaction_extensions" => [] 187 | ], 188 | [ 189 | "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4" 190 | ] 191 | ); 192 | ``` 193 | 194 | ### Push Transaction 195 | 196 | Push a transaction 197 | 198 | ```php 199 | echo $api->pushTransaction("2018-08-23T05:29:39", "15780", "90170226", 200 | [ 201 | "actions" => [ 202 | [ 203 | "account" => "eosio.token", 204 | "name" => "transfer", 205 | "authorization" => [ 206 | [ 207 | "actor" => "user", 208 | "permission" => "active" 209 | ] 210 | ], 211 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 212 | ] 213 | ], 214 | "signatures" => [ 215 | "SIG_K1_KaGHyi59BRqfaDUK6424TYEWcUhWxAG7BLCgYC8vwYNgaHgGLpduTUbNQEsfL8xLzboK8W9T2X69bNpqozTQVCbRSNJWFd" 216 | ] 217 | ] 218 | ); 219 | ``` 220 | 221 | ### Push Transactions 222 | 223 | Push transactions 224 | 225 | ```php 226 | echo $api->pushTransactions( 227 | [ 228 | [ 229 | "compression" => "none", 230 | "transaction" => [ 231 | "expiration" => "2018-08-23T06:27:26", 232 | "ref_block_num" => 22017, 233 | "ref_block_prefix" => 3920123292, 234 | "context_free_actions" => [], 235 | "actions" => [ 236 | [ 237 | "account" => "eosio.token", 238 | "name" => "transfer", 239 | "authorization" => [ 240 | [ 241 | "actor" => "user", 242 | "permission" => "active" 243 | ] 244 | ], 245 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 246 | ] 247 | ], 248 | "transaction_extensions" => [] 249 | ], 250 | "signatures" => [ 251 | "SIG_K1_JzN9DnpyhKfjoef3C2TZBTPA5b6ftwuEBnBpvzkueVXThJ34PFFpUFgqyayfXjeLRc15JmZmDiMYAFX99hUgX8vkGAYcnx" 252 | ] 253 | ], 254 | [ 255 | "compression" => "none", 256 | "transaction" => [ 257 | "expiration" => "2018-08-23T06:27:26", 258 | "ref_block_num" => 22017, 259 | "ref_block_prefix" => 3920123292, 260 | "context_free_actions" => [], 261 | "actions" => [ 262 | [ 263 | "account" => "eosio.token", 264 | "name" => "transfer", 265 | "authorization" => [ 266 | [ 267 | "actor" => "tester", 268 | "permission" => "active" 269 | ] 270 | ], 271 | "data" => "000000005c95b1ca00000000007015d6881300000000000004454f53000000000c7465737465722d3e75736572" 272 | ] 273 | ], 274 | "transaction_extensions" => [] 275 | ], 276 | "signatures" => [ 277 | "SIG_K1_KZ2M4AG59tptdRCpqbwzMQvBv1dce5btJCJiCVVy96fTGepApGXqJAwsi17g8AQdJjUQB4R62PprfdUdRYHGdBqK1z9Sx9" 278 | ] 279 | ] 280 | ] 281 | ); 282 | ``` 283 | 284 | ## Wallet APIs 285 | 286 | ### Create 287 | 288 | Creates a new wallet with the given name 289 | 290 | ```php 291 | echo $walapi->create("testwallet"); 292 | ``` 293 | 294 | ### Open 295 | 296 | Opens an existing wallet of the given name 297 | 298 | ```php 299 | echo $walapi->open("testwallet"); 300 | ``` 301 | 302 | ### Lock 303 | 304 | Locks an existing wallet of the given name 305 | 306 | ```php 307 | echo $walapi->lock("testwallet"); 308 | ``` 309 | 310 | ### Lock All 311 | 312 | Locks all existing wallets 313 | 314 | ```php 315 | echo $walapi->lockAll(); 316 | ``` 317 | 318 | ### Unlock 319 | 320 | Unlocks a wallet with the given name and password 321 | 322 | ```php 323 | echo $walapi->unlock(["testwallet", "PW5Jb8RAZP6CBjjMLPser3T8i8k9hZXZkMBJ8kb1p6f6hAg2n68jY"]); 324 | ``` 325 | 326 | ### Import Key 327 | 328 | Imports a private key to the wallet of the given name 329 | 330 | ```php 331 | echo $walapi->importKey(["testwallet", "5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR"]); 332 | ``` 333 | 334 | ### Remove Key 335 | 336 | Removes a key pair from the wallet of the given name 337 | 338 | ```php 339 | echo $walapi->removeKey(["testwallet", "PW5Jb8RAZP6CBjjMLPser3T8i8k9hZXZkMBJ8kb1p6f6hAg2n68jY", "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4"]); 340 | ``` 341 | 342 | ### Create Key 343 | 344 | Creates a key pair and import 345 | 346 | ```php 347 | echo $walapi->createKey(["testwallet", "K1"]); 348 | ``` 349 | 350 | ### List Wallets 351 | 352 | Lists all wallets 353 | 354 | ```php 355 | echo $walapi->listWallets(); 356 | ``` 357 | 358 | ### List Keys 359 | 360 | Lists all key pairs from the wallet of the given name and password 361 | 362 | ```php 363 | echo $walapi->listKeys(["testwallet", "PW5Jb8RAZP6CBjjMLPser3T8i8k9hZXZkMBJ8kb1p6f6hAg2n68jY"]); 364 | ``` 365 | 366 | ### Get Public Keys 367 | 368 | Lists all public keys across all wallets 369 | 370 | ```php 371 | echo $walapi->getPublicKeys(); 372 | ``` 373 | 374 | ### Set Timeout 375 | 376 | Sets wallet auto lock timeout (in seconds) 377 | 378 | ```php 379 | echo $walapi->setTimeout(60); 380 | ``` 381 | 382 | ### Sign Transaction 383 | 384 | Signs a transaction 385 | 386 | ```php 387 | echo $walapi->signTransaction( 388 | [ 389 | "expiration" => "2018-08-23T06:35:30", 390 | "ref_block_num" => 22985, 391 | "ref_block_prefix" => 3016594541, 392 | "max_net_usage_workds" => 0, 393 | "delay_sec" => 0, 394 | "context_free_actions" => [], 395 | "actions" => [ 396 | [ 397 | "account" => "eosio.token", 398 | "name" => "transfer", 399 | "authorization" => [ 400 | [ 401 | "actor" => "user", 402 | "permission" => "active" 403 | ] 404 | ], 405 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 406 | ] 407 | ], 408 | "transaction_extensions" => [] 409 | ], 410 | [ 411 | "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4" 412 | ], 413 | "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f" 414 | ); 415 | ``` 416 | 417 | ## EOS Wrapper APIs 418 | 419 | ### Prerequisites 420 | 421 | Need to set wallet name and password 422 | 423 | ```php 424 | $eos->setWalletInfo("testwallet", "PW5Jb8RAZP6CBjjMLPser3T8i8k9hZXZkMBJ8kb1p6f6hAg2n68jY"); 425 | ``` 426 | 427 | ### Push Transaction 428 | 429 | Push a transaction 430 | 431 | ```php 432 | echo $eos->pushTransaction( 433 | [ 434 | [ 435 | "account" => "eosio.token", 436 | "name" => "transfer", 437 | "authorization" => [ 438 | [ 439 | "actor" => "user", 440 | "permission" => "active" 441 | ] 442 | ], 443 | "data" => [ 444 | "from" => "user", 445 | "to" => "tester", 446 | "quantity" => "1.0000 EOS", 447 | "memo" => "memo" 448 | ] 449 | ] 450 | ] 451 | ); 452 | ``` 453 | 454 | ### Make Transaction 455 | 456 | Make a transaction (useful for pushTransactions) 457 | 458 | ```php 459 | $trx = $eos->makeTransaction( 460 | [ 461 | [ 462 | "account" => "eosio.token", 463 | "name" => "transfer", 464 | "authorization" => [ 465 | [ 466 | "actor" => "user", 467 | "permission" => "active" 468 | ] 469 | ], 470 | "data" => [ 471 | "from" => "user", 472 | "to" => "tester", 473 | "quantity" => "1.0000 EOS", 474 | "memo" => "memo" 475 | ] 476 | ] 477 | ] 478 | ); 479 | ``` 480 | 481 | ### Push Transactions 482 | 483 | Push transactions 484 | 485 | ```php 486 | $trx_ids = $eos->pushTransactions( 487 | [ 488 | $eos->makeTransaction( 489 | [ 490 | [ 491 | "account" => "eosio.token", 492 | "name" => "transfer", 493 | "authorization" => [ 494 | [ 495 | "actor" => "user", 496 | "permission" => "active" 497 | ] 498 | ], 499 | "data" => [ 500 | "from" => "user", 501 | "to" => "tester", 502 | "quantity" => "1.0000 EOS", 503 | "memo" => "memo" 504 | ] 505 | ] 506 | ] 507 | ), 508 | $eos->makeTransaction( 509 | [ 510 | [ 511 | "account" => "eosio.token", 512 | "name" => "transfer", 513 | "authorization" => [ 514 | [ 515 | "actor" => "tester", 516 | "permission" => "active" 517 | ] 518 | ], 519 | "data" => [ 520 | "from" => "tester", 521 | "to" => "user", 522 | "quantity" => "0.5000 EOS", 523 | "memo" => "memo" 524 | ] 525 | ] 526 | ] 527 | ) 528 | ] 529 | ); 530 | foreach ($trx_ids as $key => $value) { 531 | echo $trx_ids[$key]['transaction_id'] . PHP_EOL; 532 | } 533 | ``` 534 | 535 | ### Push Action 536 | 537 | Push an action 538 | 539 | ```php 540 | echo $eos->pushAction("eosio", "buyram", ["payer"=>"tester","receiver"=>"tester","quant"=>"1.0000 EOS"], ["actor"=>"tester","permission"=>"active"]); 541 | ``` 542 | 543 | ### Transfer 544 | 545 | Transfers token 546 | 547 | ```php 548 | echo $eos->transfer("user", "tester", "1.0000 EOS", "memo"); 549 | ``` 550 | 551 | ### Create Key Pair 552 | 553 | Creates a key pair and returns 554 | 555 | ```php 556 | $keyPair = $eos->createKeyPair("K1"); 557 | echo "$keyPair[0], $keyPair[1]"; 558 | ``` 559 | 560 | ## Tests 561 | 562 | To run the test suites, simply execute: 563 | 564 | ```php 565 | vendor/bin/phpunit 566 | ``` 567 | 568 | If you wanna get fancy and check code coverage: 569 | 570 | ```php 571 | vendor/bin/phpunit --coverage-html tests/coverage 572 | ``` 573 | 574 | If you're really bored, you might wanna run some static analysis: 575 | 576 | ```php 577 | vendor/bin/phpmetrics --report-html="tests/static" . 578 | ``` 579 | 580 | ## Contributing 581 | 582 | All contributions are welcome! Just fire up a PR, make sure your code style is PSR-2 compliant: 583 | 584 | ```php 585 | vendor/bin/php-cs-fixer fix --verbose 586 | ``` 587 | 588 | ## License 589 | 590 | Free for everyone! 591 | 592 | MIT License 593 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manamine/php-eos-rpc-sdk", 3 | "description": "PHP SDK for the EOS RPC API", 4 | "type": "library", 5 | "keywords": ["php","eos","rpc","sdk","api","crypto","blockchain"], 6 | "homepage": "https://github.com/alienzin/php-eos-rpc-sdk", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "DongJin Seo", 11 | "email": "alienzin@manamine.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "guzzlehttp/guzzle": "~6.0", 17 | "vlucas/phpdotenv": "^2.4", 18 | "php-curl-class/php-curl-class": "^8.1" 19 | }, 20 | "require-dev": { 21 | "mockery/mockery": "0.9.*", 22 | "phpunit/phpunit": "~5.0", 23 | "phpspec/phpspec": "~2.1", 24 | "friendsofphp/php-cs-fixer": "^2.3", 25 | "phpmetrics/phpmetrics": "^2.2" 26 | }, 27 | "autoload": { 28 | "psr-4": {"BlockMatrix\\EosRpc\\": ["src/"]} 29 | }, 30 | "autoload-dev": { 31 | "psr-4": {"BlockMatrix\\EosRpc\\": ["tests/"]} 32 | }, 33 | "scripts": { 34 | "post-root-package-install": [ 35 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/chain.php: -------------------------------------------------------------------------------- 1 | api(); 6 | 7 | echo $api->getInfo() . PHP_EOL; 8 | echo $api->getBlock("1337") . PHP_EOL; 9 | echo $api->getBlockHeaderState("0016e48707b181d93117b07451d9837526eba34a9a37125689fb5a73a5d28a38") . PHP_EOL; 10 | echo $api->getAccount("blockmatrix1") . PHP_EOL; 11 | echo $api->getCode("eosio.token") . PHP_EOL; 12 | echo $api->getTableRows("eosio", "eosio", "producers", ["limit" => 10]) . PHP_EOL; 13 | echo $api->getCurrencyBalance("eosio.token", "eosdacserver") . PHP_EOL; 14 | echo $api->getCurrencyStats("eosio.token", "EOS") . PHP_EOL; 15 | echo $api->getAbi("eosio.token") . PHP_EOL; 16 | echo $api->getRawCodeAndAbi("eosio.token") . PHP_EOL; 17 | echo $api->getProducers(10) . PHP_EOL; 18 | echo $api->abiJsonToBin("eosio.token", "transfer", ["blockmatrix1", "blockmatrix1", "7.0000 EOS", "Testy McTest"]) . PHP_EOL; 19 | echo $api->abiBinToJson("eosio.token", "transfer", "10babbd94888683c10babbd94888683c701101000000000004454f53000000000c5465737479204d6354657374") . PHP_EOL; 20 | echo $api->getRequiredKeys( 21 | [ 22 | "expiration" => "2018-08-23T05.00.00", 23 | "ref_block_num" => 15078, 24 | "ref_block_prefix" => 1071971392, 25 | "max_net_usage_words" => 0, 26 | "delay_sec" => 0, 27 | "context_free_actions" => [], 28 | "actions" => [ 29 | [ 30 | "account" => "eosio.token", 31 | "name" => "transfer", 32 | "authorization" => [ 33 | [ 34 | "actor" => "user", 35 | "permission" => "active" 36 | ] 37 | ], 38 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 39 | ] 40 | ], 41 | "transaction_extensions" => [] 42 | ], 43 | [ 44 | "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4" 45 | ] 46 | ); 47 | echo $api->pushTransaction("2018-08-23T05:29:39", "15780", "90170226", 48 | [ 49 | "actions" => [ 50 | [ 51 | "account" => "eosio.token", 52 | "name" => "transfer", 53 | "authorization" => [ 54 | [ 55 | "actor" => "user", 56 | "permission" => "active" 57 | ] 58 | ], 59 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 60 | ] 61 | ], 62 | "signatures" => [ 63 | "SIG_K1_KaGHyi59BRqfaDUK6424TYEWcUhWxAG7BLCgYC8vwYNgaHgGLpduTUbNQEsfL8xLzboK8W9T2X69bNpqozTQVCbRSNJWFd" 64 | ] 65 | ] 66 | ); 67 | echo $api->pushTransactions( 68 | [ 69 | [ 70 | "compression" => "none", 71 | "transaction" => [ 72 | "expiration" => "2018-08-23T06:27:26", 73 | "ref_block_num" => 22017, 74 | "ref_block_prefix" => 3920123292, 75 | "context_free_actions" => [], 76 | "actions" => [ 77 | [ 78 | "account" => "eosio.token", 79 | "name" => "transfer", 80 | "authorization" => [ 81 | [ 82 | "actor" => "user", 83 | "permission" => "active" 84 | ] 85 | ], 86 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 87 | ] 88 | ], 89 | "transaction_extensions" => [] 90 | ], 91 | "signatures" => [ 92 | "SIG_K1_JzN9DnpyhKfjoef3C2TZBTPA5b6ftwuEBnBpvzkueVXThJ34PFFpUFgqyayfXjeLRc15JmZmDiMYAFX99hUgX8vkGAYcnx" 93 | ] 94 | ], 95 | [ 96 | "compression" => "none", 97 | "transaction" => [ 98 | "expiration" => "2018-08-23T06:27:26", 99 | "ref_block_num" => 22017, 100 | "ref_block_prefix" => 3920123292, 101 | "context_free_actions" => [], 102 | "actions" => [ 103 | [ 104 | "account" => "eosio.token", 105 | "name" => "transfer", 106 | "authorization" => [ 107 | [ 108 | "actor" => "tester", 109 | "permission" => "active" 110 | ] 111 | ], 112 | "data" => "000000005c95b1ca00000000007015d6881300000000000004454f53000000000c7465737465722d3e75736572" 113 | ] 114 | ], 115 | "transaction_extensions" => [] 116 | ], 117 | "signatures" => [ 118 | "SIG_K1_KZ2M4AG59tptdRCpqbwzMQvBv1dce5btJCJiCVVy96fTGepApGXqJAwsi17g8AQdJjUQB4R62PprfdUdRYHGdBqK1z9Sx9" 119 | ] 120 | ] 121 | ] 122 | ); 123 | -------------------------------------------------------------------------------- /examples/eosrpc.php: -------------------------------------------------------------------------------- 1 | api(); 6 | $walapi = (new \BlockMatrix\EosRpc\WalletFactory)->api(); 7 | $eos = (new \BlockMatrix\EosRpc\EosRpc($chapi, $walapi)); 8 | 9 | 10 | echo ($walletPassword = trim($walapi->create("testwallet"), "\"")) . PHP_EOL; 11 | $eos->setWalletInfo("testwallet", $walletPassword); 12 | echo $walapi->importKey(["testwallet", "5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR"]) . PHP_EOL; 13 | echo $eos->pushTransaction( 14 | [ 15 | [ 16 | "account" => "eosio.token", 17 | "name" => "transfer", 18 | "authorization" => [ 19 | [ 20 | "actor" => "user", 21 | "permission" => "active" 22 | ] 23 | ], 24 | "data" => [ 25 | "from" => "user", 26 | "to" => "tester", 27 | "quantity" => "1.0000 EOS", 28 | "memo" => "memo" 29 | ] 30 | ] 31 | ] 32 | ); 33 | $trx_ids = $eos->pushTransactions( 34 | [ 35 | $eos->makeTransaction( 36 | [ 37 | [ 38 | "account" => "eosio.token", 39 | "name" => "transfer", 40 | "authorization" => [ 41 | [ 42 | "actor" => "user", 43 | "permission" => "active" 44 | ] 45 | ], 46 | "data" => [ 47 | "from" => "user", 48 | "to" => "tester", 49 | "quantity" => "1.0000 EOS", 50 | "memo" => "memo" 51 | ] 52 | ] 53 | ] 54 | ), 55 | $eos->makeTransaction( 56 | [ 57 | [ 58 | "account" => "eosio.token", 59 | "name" => "transfer", 60 | "authorization" => [ 61 | [ 62 | "actor" => "tester", 63 | "permission" => "active" 64 | ] 65 | ], 66 | "data" => [ 67 | "from" => "tester", 68 | "to" => "user", 69 | "quantity" => "0.5000 EOS", 70 | "memo" => "memo" 71 | ] 72 | ] 73 | ] 74 | ) 75 | ] 76 | ); 77 | foreach ($trx_ids as $key => $value) { 78 | echo $trx_ids[$key]['transaction_id'] . PHP_EOL; 79 | } 80 | echo $eos->pushAction("eosio", "buyram", 81 | [ 82 | "payer" => "tester", 83 | "receiver" => "tester", 84 | "quant" => "1.0000 EOS" 85 | ], 86 | [ 87 | "actor" => "tester", 88 | "permission" => "active" 89 | ] 90 | ) . PHP_EOL; 91 | echo $eos->pushAction("eosio", "sellram", 92 | [ 93 | "tester", 94 | 10240 95 | ], 96 | [ 97 | "actor" => "tester", 98 | "permission" => "active" 99 | ] 100 | ) . PHP_EOL; 101 | echo $eos->transfer("user", "tester", "1.0000 EOS", "memo") . PHP_EOL; 102 | $keyPair = $eos->createKeyPair("K1"); 103 | echo "publicKey : $keyPair[0]\nprivateKey : $keyPair[1]\n"; 104 | -------------------------------------------------------------------------------- /examples/wallet.php: -------------------------------------------------------------------------------- 1 | api(); 6 | 7 | 8 | echo ($walletPassword = trim($walapi->create("testwallet"), "\"")) . PHP_EOL; 9 | echo $walapi->open("testwallet") . PHP_EOL; 10 | echo $walapi->lock("testwallet") . PHP_EOL; 11 | echo $walapi->lockAll() . PHP_EOL; 12 | echo $walapi->unlock(["testwallet", $walletPassword]) . PHP_EOL; 13 | echo $walapi->importKey(["testwallet", "5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR"]) . PHP_EOL; 14 | echo $walapi->removeKey(["testwallet", $walletPassword, "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4"]) . PHP_EOL; 15 | echo $walapi->createKey("testwallet", "K1") . PHP_EOL; 16 | echo $walapi->listWallets() . PHP_EOL; 17 | echo $walapi->listKeys(["testwallet", $walletPassword]) . PHP_EOL; 18 | echo $walapi->getPublicKeys() . PHP_EOL; 19 | echo $walapi->setTimeout(60) . PHP_EOL; 20 | echo $walapi->signTransaction( 21 | [ 22 | "expiration" => "2018-08-23T06:35:30", 23 | "ref_block_num" => 22985, 24 | "ref_block_prefix" => 3016594541, 25 | "max_net_usage_workds" => 0, 26 | "delay_sec" => 0, 27 | "context_free_actions" => [], 28 | "actions" => [ 29 | [ 30 | "account" => "eosio.token", 31 | "name" => "transfer", 32 | "authorization" => [ 33 | [ 34 | "actor" => "user", 35 | "permission" => "active" 36 | ] 37 | ], 38 | "data" => "00000000007015d6000000005c95b1ca102700000000000004454f53000000000c757365722d3e746573746572" 39 | ] 40 | ], 41 | "transaction_extensions" => [] 42 | ], 43 | [ 44 | "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4" 45 | ], 46 | "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f" 47 | ); 48 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | ./src/ 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Adapter/Http/CurlAdapter.php: -------------------------------------------------------------------------------- 1 | client = $client; 28 | } 29 | 30 | /** 31 | * Perform GET request 32 | * 33 | * @param string $url The request URL 34 | * 35 | * @throws \Exception For failed requests 36 | * 37 | * @return string The GET response 38 | */ 39 | public function get(string $url): string 40 | { 41 | try { 42 | $this->client->get($url); 43 | } catch (\Throwable $t) { 44 | throw new HttpException("GET Request failed: {$t->getMessage()}"); 45 | } 46 | 47 | return json_encode($this->client->response, JSON_PRETTY_PRINT); 48 | } 49 | 50 | /** 51 | * Perform POST request 52 | * 53 | * mixed $params string or array 54 | * 55 | * @param string $url The request URL 56 | * @param mixed $params Additional POST params 57 | * 58 | * @throws \Exception For failed requests 59 | * 60 | * @return string The POST response 61 | */ 62 | public function post(string $url, $params): string 63 | { 64 | try { 65 | if (is_array($params)) 66 | $this->client->post($url, json_encode($params)); 67 | else 68 | $this->client->post($url, $params); 69 | } catch (\Throwable $t) { 70 | throw new HttpException("POST Request failed: {$t->getMessage()}"); 71 | } 72 | 73 | return json_encode($this->client->response, JSON_PRETTY_PRINT); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Adapter/Http/GuzzleAdapter.php: -------------------------------------------------------------------------------- 1 | client = $client; 28 | } 29 | 30 | /** 31 | * Perform GET request 32 | * 33 | * @param string $url The request URL 34 | * 35 | * @throws \Exception For failed requests 36 | * 37 | * @return string The GET response 38 | */ 39 | public function get(string $url): string 40 | { 41 | try { 42 | $response = $this->client->get($url, ['headers' => ['Accept' => 'application/json']]); 43 | } catch (\Throwable $t) { 44 | throw new HttpException("GET Request failed: {$t->getMessage()}"); 45 | } 46 | 47 | return (string) $response->getBody(); 48 | } 49 | 50 | /** 51 | * Perform POST request 52 | * 53 | * @param string $url The request URL 54 | * @param array $params Additional POST params 55 | * 56 | * @throws \Exception For failed requests 57 | * 58 | * @return string The POST response 59 | */ 60 | public function post(string $url, array $params): string 61 | { 62 | try { 63 | $response = $this->client->post($url, ['headers' => ['Accept' => 'application/json'], 'form_params' => $params]); 64 | } catch (\Throwable $t) { 65 | throw new HttpException("POST Request failed: {$t->getMessage()}"); 66 | } 67 | 68 | return (string) $response->getBody(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Adapter/Http/HttpInterface.php: -------------------------------------------------------------------------------- 1 | load(); 28 | } catch (\Dotenv\Exception\InvalidPathException $e) { 29 | throw new SettingsNotFoundException('Invalid path to settings config file'); 30 | } catch (\Throwable $t) { 31 | throw new SettingsException('Access to settings failed'); 32 | } 33 | } 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | public function rpcNode(): string 39 | { 40 | return (string) getenv('RPC_NODE_URL'); 41 | } 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function rpcKeosd(): string 47 | { 48 | return (string) getenv('RPC_KEOSD_URL'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Adapter/Settings/SettingsInterface.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 41 | $this->client = $client; 42 | } 43 | 44 | /** 45 | * Build the URI 46 | * 47 | * @param $endpoint 48 | * 49 | * @return string 50 | */ 51 | protected function buildUrl($endpoint): string 52 | { 53 | return $this->settings->rpcNode() . '/' . $this->version . $endpoint; 54 | } 55 | 56 | /** 57 | * Get latest information related to a node 58 | * 59 | * @return string 60 | */ 61 | public function getInfo(): string 62 | { 63 | return $this->client->get($this->buildUrl('/chain/get_info')); 64 | } 65 | 66 | /** 67 | * Get information related to a block 68 | * 69 | * mixed $id Block num or id 70 | * 71 | * @param mixed $id 72 | * @return string 73 | */ 74 | public function getBlock($id): string 75 | { 76 | return $this->client->post( 77 | $this->buildUrl('/chain/get_block'), 78 | ['block_num_or_id' => $id] 79 | ); 80 | } 81 | 82 | /** 83 | * Get actions related to an account 84 | * 85 | * string $account_name Account name to query 86 | * int $pos Position in action log 87 | * int $offset Offset in action log 88 | * 89 | * @param string $account_name 90 | * @param int $pos 91 | * @param int $offset 92 | * @return string 93 | */ 94 | public function getActions($account_name, $pos, $offset): string 95 | { 96 | return $this->client->post( 97 | $this->buildUrl('/history/get_actions'), 98 | ['account_name' => $account_name, 'pos' => $pos, 'offset' => $offset] 99 | ); 100 | } 101 | 102 | /** 103 | * Get information related to a block header state 104 | * 105 | * mixed $id Block num or id 106 | * 107 | * @param mixed $id 108 | * @return string 109 | */ 110 | public function getBlockHeaderState($id): string 111 | { 112 | return $this->client->post( 113 | $this->buildUrl('/chain/get_block_header_state'), 114 | ['block_num_or_id' => $id] 115 | ); 116 | } 117 | 118 | /** 119 | * Get information related to an account 120 | * 121 | * string $name Account name 122 | * 123 | * @return string 124 | */ 125 | public function getAccount(string $name): string 126 | { 127 | return $this->client->post( 128 | $this->buildUrl('/chain/get_account'), 129 | ['account_name' => $name] 130 | ); 131 | } 132 | 133 | /** 134 | * Get account abi 135 | * 136 | * string $name Account name 137 | * 138 | * @return string 139 | */ 140 | public function getAbi(string $name): string 141 | { 142 | return $this->client->post( 143 | $this->buildUrl('/chain/get_abi'), 144 | ['account_name' => $name] 145 | ); 146 | } 147 | 148 | /** 149 | * Fetch smart contract code 150 | * 151 | * string $name Name 152 | * 153 | * @return string 154 | */ 155 | public function getCode(string $name, ?bool $code_as_wasm = false): string 156 | { 157 | return $this->client->post( 158 | $this->buildUrl('/chain/get_code'), 159 | [ 160 | 'account_name' => $name 161 | ] + ($code_as_wasm ? ['code_as_wasm' => $code_as_wasm] : []) 162 | ); 163 | } 164 | 165 | /** 166 | * Get raw code and abi 167 | * 168 | * string $name Name 169 | * 170 | * @return string 171 | */ 172 | public function getRawCodeAndAbi(string $name): string 173 | { 174 | return $this->client->post( 175 | $this->buildUrl('/chain/get_raw_code_and_abi'), 176 | ['account_name' => $name] 177 | ); 178 | } 179 | 180 | /** 181 | * Fetch smart contract data from an account 182 | * 183 | * @param string $scope 184 | * @param string $code 185 | * @param string $table 186 | * @param array $extra An optional array of additional parameters to add, such as `limit` 187 | * 188 | * @return string 189 | */ 190 | public function getTableRows(string $scope, string $code, string $table, array $extra = []): string 191 | { 192 | return $this->client->post( 193 | $this->buildUrl('/chain/get_table_rows'), 194 | [ 195 | 'scope' => $scope, 196 | 'code' => $code, 197 | 'table' => $table, 198 | 'json' => true, 199 | ] + $extra 200 | ); 201 | } 202 | 203 | /** 204 | * Get the currency balance for a defined account 205 | * 206 | * @param string $code 207 | * @param string $account 208 | * @param null|string $symbol Optional filter currency symbol 209 | * 210 | * @return string 211 | */ 212 | public function getCurrencyBalance(string $code, string $account, ?string $symbol = null): string 213 | { 214 | return $this->client->post( 215 | $this->buildUrl('/chain/get_currency_balance'), 216 | [ 217 | 'code' => $code, 218 | 'account' => $account, 219 | ] + ($symbol ? ['symbol' => $symbol] : []) 220 | ); 221 | } 222 | 223 | /** 224 | * Get currency stats 225 | * 226 | * @param string $code 227 | * @param string $symbol 228 | * 229 | * @return string 230 | */ 231 | public function getCurrencyStats(string $code, string $symbol): string 232 | { 233 | return $this->client->post( 234 | $this->buildUrl('/chain/get_currency_stats'), 235 | [ 236 | 'code' => $code, 237 | 'symbol' => $symbol, 238 | ] 239 | ); 240 | } 241 | 242 | /** 243 | * Get registered producers 244 | * 245 | * @param int $limit Optional pagination limit 246 | * @param string|null $lowerBound Optional producer account name to control pagination 247 | * 248 | * @return string 249 | */ 250 | public function getProducers(int $limit = 10, ?string $lowerBound = null): string 251 | { 252 | return $this->client->post( 253 | $this->buildUrl('/chain/get_producers'), 254 | [ 255 | 'limit' => $limit, 256 | 'json' => true 257 | ] + ($lowerBound ? ['lower_bound' => $lowerBound] : []) 258 | ); 259 | } 260 | 261 | /** 262 | * Serialize json to binary hex 263 | * The resulting binary hex is usually used for the data field in push_transaction 264 | * 265 | * @param string $code 266 | * @param string $action 267 | * @param array $args 268 | * 269 | * @return string 270 | */ 271 | public function abiJsonToBin(string $code, string $action, array $args): string 272 | { 273 | return $this->client->post( 274 | $this->buildUrl('/chain/abi_json_to_bin'), 275 | [ 276 | 'code' => $code, 277 | 'action' => $action, 278 | 'args' => $args, 279 | ] 280 | ); 281 | } 282 | 283 | /** 284 | * Serialize back binary hex to json 285 | * 286 | * @param string $code 287 | * @param string $action 288 | * @param string $binArgs 289 | * 290 | * @return string 291 | */ 292 | public function abiBinToJson(string $code, string $action, string $binArgs): string 293 | { 294 | return $this->client->post( 295 | $this->buildUrl('/chain/abi_bin_to_json'), 296 | [ 297 | 'code' => $code, 298 | 'action' => $action, 299 | 'binargs' => $binArgs, 300 | ] 301 | ); 302 | } 303 | 304 | /** 305 | * Get the required keys needed to sign a transaction 306 | * 307 | * @param string $transaction 308 | * @param array $available_keys 309 | * 310 | * @return string 311 | */ 312 | public function getRequiredKeys(array $transaction, array $available_keys): string 313 | { 314 | return $this->client->post( 315 | $this->buildUrl('/chain/get_required_keys'), 316 | [ 317 | 'transaction' => $transaction, 318 | 'available_keys' => $available_keys, 319 | ] 320 | ); 321 | } 322 | 323 | /** 324 | * Push a transaction 325 | * 326 | * @param string $expiration 327 | * @param string $ref_block_num 328 | * @param string $ref_block_prefix 329 | * @param array $extra An optional array of additional parameters to add 330 | * 331 | * @return string 332 | */ 333 | public function pushTransaction(string $expiration, string $ref_block_num, string $ref_block_prefix, 334 | array $extra = []): string 335 | { 336 | return $this->client->post( 337 | $this->buildUrl('/chain/push_transaction'), 338 | [ 339 | 'compression' => 'none', 340 | 'transaction' => [ 341 | 'expiration' => $expiration, 342 | 'ref_block_num' => $ref_block_num, 343 | 'ref_block_prefix' => $ref_block_prefix, 344 | 'context_free_actions' => [], 345 | 'actions' => $extra['actions'], 346 | 'transaction_extensions' => [], 347 | ], 348 | 'signatures' => $extra['signatures'] 349 | ] 350 | ); 351 | } 352 | 353 | public function pushTransactions(array $body): string 354 | { 355 | return $this->client->post( 356 | $this->buildUrl('/chain/push_transactions'), 357 | $body 358 | ); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/ChainFactory.php: -------------------------------------------------------------------------------- 1 | chain = $chain; 42 | $this->wallet = $wallet; 43 | } 44 | 45 | /** 46 | * Sets wallet info 47 | * 48 | * @param string $walletName 49 | * @param string $walletPwd 50 | */ 51 | public function setWalletInfo(string $walletName, string $walletPwd) 52 | { 53 | $this->walletInfo[0] = $walletName; 54 | $this->walletInfo[1] = $walletPwd; 55 | } 56 | 57 | /** 58 | * Make a transaction with the given actions and return 59 | * 60 | * @param array $actions 61 | * 62 | * $actions format: 63 | * $actions[0] = [ 64 | * 'account' => $code, 65 | * 'name' => $action, 66 | * 'authorization' => [ 67 | * [ 68 | * 'actor' => $authority['actor'], 69 | * 'permission' => $authority['permission'] 70 | * ] 71 | * ], 72 | * 'data' => $args 73 | * ]; 74 | * 75 | * @return array Transaction 76 | * @throws \Exception 77 | */ 78 | public function makeTransaction(array $actions): array 79 | { 80 | // abi_json_to_bin 81 | foreach ($actions as $key => $value) { 82 | $actions[$key]['data'] = json_decode( 83 | $this->chain->abiJsonToBin($value['account'], $value['name'], $value['data']), 84 | true)['binargs']; 85 | } 86 | 87 | // get_info 88 | $info = json_decode($this->chain->getInfo(), true); 89 | $chainId = $info['chain_id']; 90 | $chainDate = new DateTimeImmutable($info['head_block_time'], new DateTimeZone('UTC')); 91 | $expiration = $chainDate->add(new DateInterval('PT1H')); 92 | $expiration = $expiration->format('Y-m-d\TH:i:s'); 93 | 94 | // get block 95 | $block = json_decode($this->chain->getBlock($info['last_irreversible_block_num']), true); 96 | $ref_block_num = $info['last_irreversible_block_num'] & 0xFFFF; 97 | $ref_block_prefix = $block['ref_block_prefix']; 98 | 99 | // unlock wallet 100 | $this->wallet->unlock($this->walletInfo); 101 | 102 | // get required keys 103 | $transaction = [ 104 | 'expiration' => $expiration, 105 | 'ref_block_num' => $ref_block_num, 106 | 'ref_block_prefix' => $ref_block_prefix, 107 | 'max_net_usage_words' => 0, 108 | 'delay_sec' => 0, 109 | 'context_free_actions' => [], 110 | 'actions' => $actions, 111 | 'transaction_extensions' => [] 112 | ]; 113 | $available_keys = json_decode($this->wallet->getPublicKeys(), true); 114 | $required_keys = json_decode($this->chain->getRequiredKeys($transaction, $available_keys), true)['required_keys']; 115 | 116 | // sign transaction 117 | $transaction = json_decode($this->wallet->signTransaction($transaction, $required_keys, $chainId), true); 118 | $signatures = $transaction['signatures']; 119 | 120 | // lock wallet 121 | $this->wallet->lock($this->walletInfo[0]); 122 | 123 | // make transaction 124 | $transaction = [ 125 | 'compression' => 'none', 126 | 'transaction' => [ 127 | 'expiration' => $expiration, 128 | 'ref_block_num' => $ref_block_num, 129 | 'ref_block_prefix' => $ref_block_prefix, 130 | 'context_free_actions' => [], 131 | 'actions' => $actions, 132 | 'transaction_extensions' => [], 133 | ], 134 | 'signatures' => $signatures 135 | ]; 136 | 137 | return $transaction; 138 | } 139 | 140 | /** 141 | * Push a transaction with the given actions 142 | * 143 | * @param array $actions 144 | * @param bool $trxIdOnly 145 | * 146 | * $actions format: 147 | * $actions[0] = [ 148 | * 'account' => $code, 149 | * 'name' => $action, 150 | * 'authorization' => [ 151 | * [ 152 | * 'actor' => $authority['actor'], 153 | * 'permission' => $authority['permission'] 154 | * ] 155 | * ], 156 | * 'data' => $args 157 | * ]; 158 | * 159 | * @return array|string Transaction Result or ID 160 | * @throws EosRpcException 161 | */ 162 | public function pushTransaction(array $actions, bool $trxIdOnly = true) 163 | { 164 | try { 165 | $transaction = $this->makeTransaction($actions); 166 | 167 | $expiration = $transaction['transaction']['expiration']; 168 | $ref_block_num = $transaction['transaction']['ref_block_num']; 169 | $ref_block_prefix = $transaction['transaction']['ref_block_prefix']; 170 | $extra = [ 171 | 'actions' => $transaction['transaction']['actions'], 172 | 'signatures' => $transaction['signatures'] 173 | ]; 174 | 175 | $result = json_decode( 176 | $this->chain->pushTransaction($expiration, $ref_block_num, $ref_block_prefix, $extra), 177 | true); 178 | 179 | if (array_key_exists('transaction_id', $result) === false) 180 | throw new EosRpcException(json_encode($result)); 181 | 182 | return $trxIdOnly ? $result['transaction_id'] : $result; 183 | } catch (HttpException $e) { 184 | echo $e->getMessage(); 185 | } 186 | } 187 | 188 | /** 189 | * Push transactions 190 | * 191 | * @param array $transactions 192 | * 193 | * $transactions format: 194 | * $transaction[0] = [ 195 | * 'compression' => 'none', 196 | * 'transaction' => [ 197 | * 'expiration' => $expiration, 198 | * 'ref_block_num' => $ref_block_num, 199 | * 'ref_block_prefix' => $ref_block_prefix, 200 | * 'context_free_actions' => [], 201 | * 'actions' => $actions, 202 | * 'transaction_extensions' => [], 203 | * ], 204 | * 'signatures' => $signatures 205 | * ]; 206 | * 207 | * @return array Result of transactions 208 | */ 209 | public function pushTransactions(array $transactions): array 210 | { 211 | return json_decode( 212 | $this->chain->pushTransactions($transactions), 213 | true); 214 | } 215 | 216 | /** 217 | * Push an action 218 | * 219 | * @param string $code 220 | * @param string $action 221 | * @param array $args Json format action arguments 222 | * @param array $authority['actor','permission'] 223 | * @param bool $trxIdOnly 224 | * 225 | * @return array|string Transaction Result or ID 226 | * @throws EosRpcException 227 | */ 228 | public function pushAction(string $code, string $action, array $args, array $authority, bool $trxIdOnly = true) 229 | { 230 | try { 231 | $actions[0] = [ 232 | 'account' => $code, 233 | 'name' => $action, 234 | 'authorization' => [ 235 | [ 236 | 'actor' => $authority['actor'], 237 | 'permission' => $authority['permission'] 238 | ] 239 | ], 240 | 'data' => $args 241 | ]; 242 | 243 | return $this->pushTransaction($actions, $trxIdOnly); 244 | } catch (EosRpcException $e) { 245 | throw $e; 246 | } 247 | } 248 | 249 | /** 250 | * Transfers token 251 | * 252 | * @param string $from 253 | * @param string $to 254 | * @param string $quantity 255 | * @param string $memo 256 | * @param string $contract 257 | * @param bool $trxIdOnly 258 | * 259 | * @return array|string Transaction Result or ID 260 | * @throws EosRpcException 261 | */ 262 | public function transfer(string $from, string $to, string $quantity, string $memo, 263 | string $contract = 'eosio.token', bool $trxIdOnly = true) 264 | { 265 | // push action $contract transfer '["$from", "$to", "$quantity", "$memo"]' 266 | 267 | try { 268 | $args = [ 269 | 'from' => $from, 270 | 'to' => $to, 271 | 'quantity' => $quantity, 272 | 'memo' => $memo 273 | ]; 274 | 275 | $authority = [ 276 | 'actor' => $from, 277 | 'permission' => 'active' 278 | ]; 279 | 280 | return $this->pushAction($contract, 'transfer', $args, $authority, $trxIdOnly); 281 | } catch (EosRpcException $e) { 282 | throw $e; 283 | } 284 | } 285 | 286 | /** 287 | * Creates a key pair and returns 288 | * 289 | * @param string $keyType 290 | * @param bool $noImport An optional param to import keys or not 291 | * 292 | * @return array ['publicKey','privateKey'] 293 | */ 294 | public function createKeyPair(string $keyType, bool $noImport = true): array 295 | { 296 | // unlock wallet 297 | $this->wallet->unlock($this->walletInfo); 298 | 299 | // create key 300 | $ret[0] = trim($this->wallet->createKey($this->walletInfo[0], $keyType), "\""); 301 | 302 | // list keys 303 | $keys = json_decode($this->wallet->listKeys($this->walletInfo), true); 304 | 305 | foreach($keys as $key => $value) { 306 | if ($value[0] == $ret[0]) { 307 | $ret[1] = $value[1]; 308 | break; 309 | } 310 | } 311 | 312 | // remove key which is created and imported 313 | if ($noImport) { 314 | $this->wallet->removeKey(array_merge($this->walletInfo, [$ret[0]])) . PHP_EOL; 315 | } 316 | 317 | return $ret; 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/Exception/EosRpcException.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 41 | $this->client = $client; 42 | } 43 | 44 | /** 45 | * Build the URI 46 | * 47 | * @param $endpoint 48 | * 49 | * @return string 50 | */ 51 | protected function buildUrl($endpoint): string 52 | { 53 | return $this->settings->rpcKeosd() . '/' . $this->version . $endpoint; 54 | } 55 | 56 | /** 57 | * Creates a new wallet with the given name 58 | * 59 | * @param string $name 60 | * 61 | * @return string Wallet password 62 | */ 63 | public function create(string $name = 'default'): string 64 | { 65 | return $this->client->post( 66 | $this->buildUrl('/wallet/create'), 67 | "\"$name\"" 68 | ); 69 | } 70 | 71 | /** 72 | * Opens an existing wallet of the given name 73 | * 74 | * @param string $name 75 | * 76 | * @return string 77 | */ 78 | public function open(string $name = 'default'): string 79 | { 80 | return $this->client->post( 81 | $this->buildUrl('/wallet/open'), 82 | "\"$name\"" 83 | ); 84 | } 85 | 86 | /** 87 | * Locks an existing wallet of the given name 88 | * 89 | * @return string 90 | */ 91 | public function lock(string $name = 'defaut'): string 92 | { 93 | return $this->client->post( 94 | $this->buildUrl('/wallet/lock'), 95 | "\"$name\"" 96 | ); 97 | } 98 | 99 | /** 100 | * Locks all existing wallets 101 | * 102 | * @return string 103 | */ 104 | public function lockAll(): string 105 | { 106 | return $this->client->get($this->buildUrl('/wallet/lock_all')); 107 | } 108 | 109 | /** 110 | * Unlocks a wallet with the given name and password 111 | * 112 | * @param array $args['name','password'] 113 | * 114 | * @return string 115 | */ 116 | public function unlock(array $args): string 117 | { 118 | return $this->client->post( 119 | $this->buildUrl('/wallet/unlock'), 120 | $args 121 | ); 122 | } 123 | 124 | /** 125 | * Imports a private key to the wallet of the given name 126 | * 127 | * @param array $args['name','privateKey'] 128 | * 129 | * @return string 130 | */ 131 | public function importKey(array $args): string 132 | { 133 | return $this->client->post( 134 | $this->buildUrl('/wallet/import_key'), 135 | $args 136 | ); 137 | } 138 | 139 | /** 140 | * Removes a key pair from the wallet of the given name 141 | * 142 | * @param array $args['name','password','publicKey'] 143 | * 144 | * @return string 145 | */ 146 | public function removeKey(array $args): string 147 | { 148 | return $this->client->post( 149 | $this->buildUrl('/wallet/remove_key'), 150 | $args 151 | ); 152 | } 153 | 154 | /** 155 | * Creates a key pair and import 156 | * 157 | * @param string $name 158 | * @param string $keyType 159 | * 160 | * @return string 161 | */ 162 | public function createKey(string $name, string $keyType): string 163 | { 164 | return $this->client->post( 165 | $this->buildUrl('/wallet/create_key'), 166 | [ 167 | $name, 168 | $keyType 169 | ] 170 | ); 171 | } 172 | 173 | /** 174 | * Lists all wallets 175 | * 176 | * @return string 177 | */ 178 | public function listWallets(): string 179 | { 180 | return $this->client->get($this->buildUrl('/wallet/list_wallets')); 181 | } 182 | 183 | /** 184 | * Lists all key pairs from the wallet of the given name and password 185 | * 186 | * @param array $args['name','password'] 187 | * 188 | * @return string 189 | */ 190 | public function listKeys(array $args): string 191 | { 192 | return $this->client->post( 193 | $this->buildUrl('/wallet/list_keys'), 194 | $args 195 | ); 196 | } 197 | 198 | /** 199 | * Lists all public keys across all wallets 200 | * 201 | * @return string 202 | */ 203 | public function getPublicKeys(): string 204 | { 205 | return $this->client->get($this->buildUrl('/wallet/get_public_keys')); 206 | } 207 | 208 | /** 209 | * Sets wallet auto lock timeout (in seconds) 210 | * 211 | * @param int time 212 | * 213 | * @return string 214 | */ 215 | public function setTimeout(int $time): string 216 | { 217 | return $this->client->post( 218 | $this->buildUrl('/wallet/set_timeout'), 219 | $time 220 | ); 221 | } 222 | 223 | /** 224 | * Signs a transaction 225 | * 226 | * @param array $txn 227 | * @param array $keys 228 | * @param string $id ChainId 229 | * 230 | * @return string 231 | */ 232 | public function signTransaction(array $txn, array $keys, string $id): string 233 | { 234 | return $this->client->post( 235 | $this->buildUrl('/wallet/sign_transaction'), 236 | [ 237 | $txn, 238 | $keys, 239 | $id 240 | ] 241 | ); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/WalletFactory.php: -------------------------------------------------------------------------------- 1 | shouldReceive('load') 24 | ->shouldReceive('rpcNode') 25 | ->andReturn('https://eosapi.blockmatrix.network/') 26 | ->getMock(); 27 | } 28 | 29 | protected function getPsrValidResponse() 30 | { 31 | return m::mock(\GuzzleHttp\Psr7\Response::class) 32 | ->shouldReceive('getBody') 33 | ->andReturn('{"success":true}') 34 | ->getMock(); 35 | } 36 | 37 | public function testFetchApi() 38 | { 39 | $settings = m::mock(Dotenv::class) 40 | ->shouldReceive('load') 41 | ->getMock(); 42 | 43 | $http = m::mock(Client::class); 44 | $api = new ChainController(new DotenvAdapter($settings), new GuzzleAdapter($http)); 45 | 46 | $this->assertInstanceOf(ChainController::class, $api); 47 | } 48 | 49 | public function testGetWorks() 50 | { 51 | $settings = $this->getSettingsMock(); 52 | $http = m::mock(Client::class) 53 | ->shouldReceive('get') 54 | ->andReturn($this->getPsrValidResponse()) 55 | ->getMock(); 56 | 57 | $api = new ChainController(new DotenvAdapter($settings), new GuzzleAdapter($http)); 58 | $result = $api->getInfo(); 59 | 60 | $this->assertJson($result); 61 | } 62 | 63 | /** 64 | * @expectedException BlockMatrix\EosRpc\Exception\HttpException 65 | */ 66 | public function testHttpGetException() 67 | { 68 | $settings = $this->getSettingsMock(); 69 | $http = m::mock(Client::class) 70 | ->shouldReceive('get') 71 | ->andThrow(new HttpException('cant connect to mothership')) 72 | ->getMock(); 73 | 74 | $api = new ChainController(new DotenvAdapter($settings), new GuzzleAdapter($http)); 75 | $response = $api->getInfo(); 76 | } 77 | 78 | /** 79 | * @expectedException BlockMatrix\EosRpc\Exception\SettingsNotFoundException 80 | */ 81 | public function testSettingsPathException() 82 | { 83 | $settings = m::mock(Dotenv::class) 84 | ->shouldReceive('load') 85 | ->andThrow(new \Dotenv\Exception\InvalidPathException) 86 | ->getMock(); 87 | 88 | $http = m::mock(Client::class); 89 | 90 | $api = new ChainController(new DotenvAdapter($settings), new GuzzleAdapter($http)); 91 | } 92 | 93 | /** 94 | * @expectedException BlockMatrix\EosRpc\Exception\SettingsException 95 | */ 96 | public function testSettingsException() 97 | { 98 | $settings = m::mock(Dotenv::class) 99 | ->shouldReceive('load') 100 | ->andThrow(new \Exception) 101 | ->getMock(); 102 | 103 | $http = m::mock(Client::class); 104 | 105 | $api = new ChainController(new DotenvAdapter($settings), new GuzzleAdapter($http)); 106 | } 107 | 108 | public function testPostWorks() 109 | { 110 | $settings = $this->getSettingsMock(); 111 | $http = m::mock(Curl::class) 112 | ->shouldReceive('post') 113 | ->andReturn(['valid' => 'response']) 114 | ->shouldReceive('close') 115 | ->getMock(); 116 | 117 | $api = new ChainController(new DotenvAdapter($settings), new CurlAdapter($http)); 118 | $result = $api->getBlock('1337'); 119 | 120 | $this->assertJson($result); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/FactoryTest.php: -------------------------------------------------------------------------------- 1 | api('.env.example'); 11 | $this->assertInstanceOf(ChainController::class, $api); 12 | } 13 | } 14 | --------------------------------------------------------------------------------