├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── _config.yml ├── abi.json ├── composer.json ├── examples ├── account.php ├── amount.php ├── balance.php ├── contract.php ├── contract_balance.php ├── custom-nodes.php ├── find-transaction.php ├── hex.php ├── is-connection.php ├── option-trx.php ├── send-transaction.php ├── transactionBuilder.php └── universal.php ├── phpunit.xml.dist ├── src ├── Concerns │ ├── ManagesTronscan.php │ └── ManagesUniversal.php ├── Exception │ ├── ErrorException.php │ ├── NotFoundException.php │ ├── TRC20Exception.php │ └── TronException.php ├── Provider │ ├── HttpProvider.php │ └── HttpProviderInterface.php ├── Support │ ├── Base58.php │ ├── Base58Check.php │ ├── BigInteger.php │ ├── Crypto.php │ ├── Hash.php │ ├── Keccak.php │ ├── Secp.php │ └── Utils.php ├── TRC20Contract.php ├── TransactionBuilder.php ├── Tron.php ├── TronAddress.php ├── TronAwareTrait.php ├── TronInterface.php ├── TronManager.php └── trc20.json └── tests └── TronTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/**/workspace.xml 3 | .idea/php.xml 4 | .idea/vcs.xml 5 | .idea/**/tasks.xml 6 | .idea/**/usage.statistics.xml 7 | .idea/**/dictionaries 8 | 9 | # Generated files 10 | .idea/**/contentModel.xml 11 | 12 | # Sensitive or high-churn files 13 | .idea/**/dataSources/ 14 | .idea/**/dataSources.ids 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | .idea/**/dbnavigator.xml 20 | 21 | # Gradle 22 | .idea/**/gradle.xml 23 | .idea/**/libraries 24 | 25 | # Gradle and Maven with auto-import 26 | # When using Gradle or Maven with auto-import, you should exclude module files, 27 | # since they will be recreated, and may cause churn. Uncomment if using 28 | # auto-import. 29 | # .idea/modules.xml 30 | # .idea/*.iml 31 | # .idea/modules 32 | 33 | # CMake 34 | cmake-build-*/ 35 | 36 | # Mongo Explorer plugin 37 | .idea/**/mongoSettings.xml 38 | 39 | # File-based project format 40 | *.iws 41 | 42 | # IntelliJ 43 | out/ 44 | 45 | # mpeltonen/sbt-idea plugin 46 | .idea_modules/ 47 | 48 | # JIRA plugin 49 | atlassian-ide-plugin.xml 50 | 51 | # Cursive Clojure plugin 52 | .idea/replstate.xml 53 | 54 | # Crashlytics plugin (for Android Studio and IntelliJ) 55 | com_crashlytics_export_strings.xml 56 | crashlytics.properties 57 | crashlytics-build.properties 58 | fabric.properties 59 | 60 | # Editor-based Rest Client 61 | .idea/httpRequests 62 | 63 | examples/index.php 64 | composer.lock 65 | examples/index2.php 66 | vendor/ 67 | .idea/inspectionProfiles/ 68 | .idea/misc.xml 69 | .idea/modules.xml 70 | .idea/tron-api.iml 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.4 5 | 6 | matrix: 7 | fast_finish: true 8 | 9 | cache: 10 | directories: 11 | - $HOME/.composer/cache 12 | 13 | before_install: 14 | - travis_retry composer self-update 15 | 16 | install: 17 | - travis_retry composer install --no-interaction --prefer-dist 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 iEXBase 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 | # TRON API 2 | A PHP API for interacting with the Tron Protocol 3 | 4 | [![Latest Stable Version](https://poser.pugx.org/iexbase/tron-api/version)](https://packagist.org/packages/iexbase/tron-api) 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 6 | [![Build Status](https://api.travis-ci.com/iexbase/tron-api.svg?branch=master)](https://travis-ci.com/iexbase/tron-api) 7 | [![Contributors](https://img.shields.io/github/contributors/iexbase/tron-api.svg)](https://github.com/iexbase/tron-api/graphs/contributors) 8 | [![Total Downloads](https://img.shields.io/packagist/dt/iexbase/tron-api.svg?style=flat-square)](https://packagist.org/packages/iexbase/tron-api) 9 | 10 | ## Install 11 | 12 | ```bash 13 | > composer require iexbase/tron-api --ignore-platform-reqs 14 | ``` 15 | ## Requirements 16 | 17 | The following versions of PHP are supported by this version. 18 | 19 | * PHP 7.4 20 | 21 | ## Example Usage 22 | 23 | ```php 24 | use IEXBase\TronAPI\Tron; 25 | 26 | $fullNode = new \IEXBase\TronAPI\Provider\HttpProvider('https://api.trongrid.io'); 27 | $solidityNode = new \IEXBase\TronAPI\Provider\HttpProvider('https://api.trongrid.io'); 28 | $eventServer = new \IEXBase\TronAPI\Provider\HttpProvider('https://api.trongrid.io'); 29 | 30 | try { 31 | $tron = new \IEXBase\TronAPI\Tron($fullNode, $solidityNode, $eventServer); 32 | } catch (\IEXBase\TronAPI\Exception\TronException $e) { 33 | exit($e->getMessage()); 34 | } 35 | 36 | 37 | $this->setAddress('..'); 38 | //Balance 39 | $tron->getBalance(null, true); 40 | 41 | // Transfer Trx 42 | var_dump($tron->send('to', 1.5)); 43 | 44 | //Generate Address 45 | var_dump($tron->createAccount()); 46 | 47 | //Get Last Blocks 48 | var_dump($tron->getLatestBlocks(2)); 49 | 50 | //Change account name (only once) 51 | var_dump($tron->changeAccountName('address', 'NewName')); 52 | 53 | 54 | // Contract 55 | $tron->contract('Contract Address'); 56 | 57 | 58 | 59 | ``` 60 | 61 | ## Testing 62 | 63 | ``` bash 64 | $ vendor/bin/phpunit 65 | ``` 66 | 67 | ## Donations 68 | **Tron(TRX)**: TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY 69 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /abi.json: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"number","type":"uint256"}],"name":"fibonacciNotify","outputs":[{"name":"result","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"number","type":"uint256"}],"name":"fibonacci","outputs":[{"name":"result","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"input","type":"uint256"},{"indexed":false,"name":"result","type":"uint256"}],"name":"Notify","type":"event"}] 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iexbase/tron-api", 3 | "description": "A PHP API for interacting with Tron (Trx)", 4 | "license": "MIT", 5 | "type": "library", 6 | "homepage": "https://github.com/iexbase/tron-api", 7 | "authors": [ 8 | { 9 | "name": "Shamsudin Serderov", 10 | "email": "steein.shamsudin@gmail.com" 11 | } 12 | ], 13 | "keywords": [ 14 | "iexbase", 15 | "tron-lib", 16 | "tron-php", 17 | "tron-api", 18 | "tron-rest-api" 19 | ], 20 | 21 | "require": { 22 | "php": ">=8.0", 23 | "comely-io/data-types": "^1.0", 24 | "guzzlehttp/guzzle": "^7.2", 25 | "iexbase/web3.php": "^2.0.1", 26 | "kornrunner/secp256k1": "^0.2", 27 | "simplito/elliptic-php": "^1.0", 28 | "ext-json": "*", 29 | "ext-bcmath": "*" 30 | }, 31 | 32 | "require-dev": { 33 | "phpunit/phpunit": "^6.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "IEXBase\\TronAPI\\": "src" 38 | } 39 | }, 40 | 41 | "autoload-dev": { 42 | "psr-4": { 43 | "IEXBase\\TronAPI\\Test\\": "tests" 44 | } 45 | }, 46 | 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | 51 | "minimum-stability": "dev", 52 | "prefer-stable": true 53 | } 54 | -------------------------------------------------------------------------------- /examples/account.php: -------------------------------------------------------------------------------- 1 | generateAddress(); // or createAddress() 8 | $isValid = $tron->isAddress($generateAddress->getAddress()); 9 | 10 | 11 | echo 'Address hex: '. $generateAddress->getAddress(); 12 | echo 'Address base58: '. $generateAddress->getAddress(true); 13 | echo 'Private key: '. $generateAddress->getPrivateKey(); 14 | echo 'Public key: '. $generateAddress->getPublicKey(); 15 | echo 'Is Validate: '. $isValid; 16 | 17 | echo 'Raw data: '.$generateAddress->getRawData(); 18 | 19 | } catch (\IEXBase\TronAPI\Exception\TronException $e) { 20 | echo $e->getMessage(); 21 | } 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/amount.php: -------------------------------------------------------------------------------- 1 | toTron(1.15); //1150000 13 | $to = $tron->fromTron(11500000); //11.5000000 14 | -------------------------------------------------------------------------------- /examples/balance.php: -------------------------------------------------------------------------------- 1 | getMessage()); 12 | } 13 | 14 | 15 | $tron->setAddress('address'); 16 | $balance = $tron->getBalance(null, true); -------------------------------------------------------------------------------- /examples/contract.php: -------------------------------------------------------------------------------- 1 | getMessage(); 14 | } 15 | 16 | 17 | try { 18 | $tron = new Tron($fullNode, $solidityNode, $eventServer, null, true); 19 | $contract = $tron->contract('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'); // Tether USDT https://tronscan.org/#/token20/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t 20 | 21 | // Data 22 | echo $contract->name(); 23 | echo $contract->symbol(); 24 | echo $contract->balanceOf(); 25 | echo $contract->totalSupply(); 26 | //echo $contract->transfer('to', 'amount', 'from'); 27 | 28 | 29 | } catch (\IEXBase\TronAPI\Exception\TronException $e) { 30 | echo $e->getMessage(); 31 | } -------------------------------------------------------------------------------- /examples/contract_balance.php: -------------------------------------------------------------------------------- 1 | getMessage()); 10 | } 11 | 12 | 13 | $balance=$tron->getTransactionBuilder()->contractbalance($tron->getAddress); 14 | foreach($balance as $key =>$item) 15 | { 16 | echo $item["name"]. " (".$item["symbol"].") => " . $item["balance"] . "\n"; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /examples/custom-nodes.php: -------------------------------------------------------------------------------- 1 | getMessage()); 17 | } -------------------------------------------------------------------------------- /examples/find-transaction.php: -------------------------------------------------------------------------------- 1 | getMessage()); 12 | } 13 | 14 | $detail = $tron->getTransaction('TxId'); 15 | var_dump($detail); -------------------------------------------------------------------------------- /examples/hex.php: -------------------------------------------------------------------------------- 1 | toHex('TT67rPNwgmpeimvHUMVzFfKsjL9GZ1wGw8'); 7 | //result: 41BBC8C05F1B09839E72DB044A6AA57E2A5D414A10 8 | 9 | $tron->fromHex('41BBC8C05F1B09839E72DB044A6AA57E2A5D414A10'); 10 | //result: TT67rPNwgmpeimvHUMVzFfKsjL9GZ1wGw8 -------------------------------------------------------------------------------- /examples/is-connection.php: -------------------------------------------------------------------------------- 1 | getMessage()); 12 | } 13 | 14 | $tron->isConnected(); 15 | -------------------------------------------------------------------------------- /examples/option-trx.php: -------------------------------------------------------------------------------- 1 | getMessage()); 12 | } 13 | 14 | //option 1 15 | $tron->sendTransaction('to',0.1, 'hello'); 16 | 17 | //option 2 18 | $tron->send('to',0.1); 19 | 20 | //option 3 21 | $tron->sendTrx('to',0.1); 22 | -------------------------------------------------------------------------------- /examples/send-transaction.php: -------------------------------------------------------------------------------- 1 | getMessage()); 12 | } 13 | 14 | $tron->setAddress('address'); 15 | $tron->setPrivateKey('privateKey'); 16 | 17 | try { 18 | $transfer = $tron->send( 'ToAddress', 1); 19 | } catch (\IEXBase\TronAPI\Exception\TronException $e) { 20 | die($e->getMessage()); 21 | } 22 | 23 | var_dump($transfer); -------------------------------------------------------------------------------- /examples/transactionBuilder.php: -------------------------------------------------------------------------------- 1 | getMessage()); 10 | } 11 | 12 | 13 | try { 14 | $transaction = $tron->getTransactionBuilder()->sendTrx('to', 2,'fromAddress'); 15 | $signedTransaction = $tron->signTransaction($transaction); 16 | $response = $tron->sendRawTransaction($signedTransaction); 17 | } catch (\IEXBase\TronAPI\Exception\TronException $e) { 18 | die($e->getMessage()); 19 | } 20 | -------------------------------------------------------------------------------- /examples/universal.php: -------------------------------------------------------------------------------- 1 | setPrivateKey('...'); 8 | 9 | 10 | /** 11 | * check multi balances 12 | * 13 | * $address = [ 14 | * ['address', 'isFromTron'], 15 | * ['address', 'isFromTron'], 16 | * ] 17 | */ 18 | 19 | //address one -> TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY 20 | $addresses = [ 21 | ['address one', true], 22 | ['address two', true], 23 | ['address three', false], 24 | ]; 25 | 26 | //isValid (tron address) - default false 27 | $check = $tron->balances($addresses); 28 | var_dump($check); 29 | 30 | 31 | /** 32 | * send one to many 33 | * 34 | * $address = [ 35 | * ['to address', 'amount float'], 36 | * ['to address', 'amount float'], 37 | * ] 38 | * 39 | * toAddress format: TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY 40 | */ 41 | 42 | 43 | $toArray = [ 44 | ['TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY', 0.1], 45 | ['TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY', 0.2], 46 | ['other address', 0.001] 47 | ]; 48 | 49 | //default: $this->setPrivateKey(); 50 | $send = $tron->sendOneToMany('from_address', $toArray, 'private_key alt'); 51 | var_dump($send); 52 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Concerns/ManagesTronscan.php: -------------------------------------------------------------------------------- 1 | manager->request('api/transaction', $options); 23 | } 24 | } -------------------------------------------------------------------------------- /src/Concerns/ManagesUniversal.php: -------------------------------------------------------------------------------- 1 | [], 16 | 'one_to_many' => [] 17 | ]; 18 | 19 | /** 20 | * Check multiple balances 21 | * 22 | * @param array $accounts 23 | * @param bool $isValid 24 | * @return array 25 | * @throws ErrorException 26 | */ 27 | public function balances(array $accounts, $isValid = false): array 28 | { 29 | if(!is_array($accounts)) { 30 | throw new ErrorException('Data must be an array'); 31 | } 32 | 33 | if(count($accounts) > 20) { 34 | throw new ErrorException('Once you can check 20 accounts'); 35 | } 36 | 37 | foreach ($accounts as $item) 38 | { 39 | if($isValid && $this->validateAddress($item[0])['result'] == false) { 40 | throw new ErrorException($item[0].' invalid address'); 41 | } 42 | 43 | array_push($this->attribute['balances'], [ 44 | 'address' => $item[0], 45 | 'balance' => $this->getBalance($item[0], $item[1]) 46 | ]); 47 | } 48 | 49 | return $this->attribute['balances']; 50 | } 51 | 52 | /** 53 | * We send funds to several addresses at once. 54 | * 55 | * @param string $from 56 | * @param array $to 57 | * @param null $private_key 58 | * @param bool $isValid 59 | * @return array 60 | * @throws ErrorException 61 | */ 62 | public function sendOneToMany(array $to, $private_key = null, bool $isValid = false, string $from = null): array 63 | { 64 | if(!is_null($private_key)) { 65 | $this->privateKey = $private_key; 66 | } 67 | 68 | if(count($to) > 10) { 69 | throw new ErrorException('Allowed to send to "10" accounts'); 70 | } 71 | 72 | foreach ($to as $item) 73 | { 74 | if($isValid && $this->validateAddress($item[0])['result'] == false) { 75 | throw new ErrorException($item[0].' invalid address'); 76 | } 77 | 78 | array_push($this->attribute['one_to_many'], 79 | $this->send($item[0], $item[1], $from) 80 | ); 81 | } 82 | 83 | return $this->attribute['one_to_many']; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Exception/ErrorException.php: -------------------------------------------------------------------------------- 1 | host = $host; 75 | $this->timeout = $timeout; 76 | $this->statusPage = $statusPage; 77 | $this->headers = $headers; 78 | 79 | $this->httpClient = new Client([ 80 | 'base_uri' => $host, 81 | 'timeout' => $timeout, 82 | 'auth' => $user && [$user, $password] 83 | ]); 84 | } 85 | 86 | /** 87 | * Enter a new page 88 | * 89 | * @param string $page 90 | */ 91 | public function setStatusPage(string $page = '/'): void 92 | { 93 | $this->statusPage = $page; 94 | } 95 | 96 | /** 97 | * Check connection 98 | * 99 | * @return bool 100 | * @throws TronException 101 | */ 102 | public function isConnected() : bool 103 | { 104 | $response = $this->request($this->statusPage); 105 | 106 | if(array_key_exists('blockID', $response)) { 107 | return true; 108 | } elseif(array_key_exists('status', $response)) { 109 | return true; 110 | } 111 | return false; 112 | } 113 | 114 | /** 115 | * Getting a host 116 | * 117 | * @return string 118 | */ 119 | public function getHost(): string 120 | { 121 | return $this->host; 122 | } 123 | 124 | /** 125 | * Getting timeout 126 | * 127 | * @return int 128 | */ 129 | public function getTimeout(): int 130 | { 131 | return $this->timeout; 132 | } 133 | 134 | /** 135 | * We send requests to the server 136 | * 137 | * @param $url 138 | * @param array $payload 139 | * @param string $method 140 | * @return array|mixed 141 | * @throws TronException 142 | */ 143 | public function request($url, array $payload = [], string $method = 'get'): array 144 | { 145 | $method = strtoupper($method); 146 | 147 | if(!in_array($method, ['GET', 'POST'])) { 148 | throw new TronException('The method is not defined'); 149 | } 150 | 151 | $options = [ 152 | 'headers' => $this->headers, 153 | 'body' => json_encode($payload) 154 | ]; 155 | 156 | $request = new Request($method, $url, $options['headers'], $options['body']); 157 | $rawResponse = $this->httpClient->send($request, $options); 158 | 159 | return $this->decodeBody( 160 | $rawResponse->getBody(), 161 | $rawResponse->getStatusCode() 162 | ); 163 | } 164 | 165 | /** 166 | * Convert the original answer to an array 167 | * 168 | * @param StreamInterface $stream 169 | * @param int $status 170 | * @return array|mixed 171 | */ 172 | protected function decodeBody(StreamInterface $stream, int $status): array 173 | { 174 | $decodedBody = json_decode($stream->getContents(),true); 175 | 176 | if((string)$stream == 'OK') { 177 | $decodedBody = [ 178 | 'status' => 1 179 | ]; 180 | }elseif ($decodedBody == null or !is_array($decodedBody)) { 181 | $decodedBody = []; 182 | } 183 | 184 | if($status == 404) { 185 | throw new NotFoundException('Page not found'); 186 | } 187 | 188 | return $decodedBody; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Provider/HttpProviderInterface.php: -------------------------------------------------------------------------------- 1 | value = $this->initValue($value); 33 | $this->mutable = $mutable; 34 | } 35 | 36 | /** 37 | * Gets the value of the big integer. 38 | * 39 | * @return string 40 | */ 41 | public function getValue(): string 42 | { 43 | return gmp_strval($this->value); 44 | } 45 | 46 | /** 47 | * Sets the value. 48 | * 49 | * @param string $value The value to set. 50 | * @return BigInteger 51 | */ 52 | public function setValue(string $value): BigInteger 53 | { 54 | if (!$this->isMutable()) { 55 | throw new RuntimeException('Cannot set the value since the number is immutable.'); 56 | } 57 | 58 | $this->value = $this->initValue($value); 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Converts the value to an absolute number. 65 | * 66 | * @return BigInteger 67 | */ 68 | public function abs(): BigInteger 69 | { 70 | $value = gmp_abs($this->value); 71 | 72 | return $this->assignValue($value); 73 | } 74 | 75 | /** 76 | * Adds the given value to this value. 77 | * 78 | * @param string $value The value to add. 79 | * @return BigInteger 80 | */ 81 | public function add(string $value): BigInteger 82 | { 83 | $gmp = $this->initValue($value); 84 | 85 | $calculatedValue = gmp_add($this->value, $gmp); 86 | 87 | return $this->assignValue($calculatedValue); 88 | } 89 | 90 | /** 91 | * Compares this number and the given number. 92 | * 93 | * @param string $value The value to compare. 94 | * @return int Returns -1 is the number is less than this number. 0 if equal and 1 when greater. 95 | */ 96 | public function cmp($value): int 97 | { 98 | $value = $this->initValue($value); 99 | 100 | $result = gmp_cmp($this->value, $value); 101 | 102 | // It could happen that gmp_cmp returns a value greater than one (e.g. gmp_cmp('123', '-123')). That's why 103 | // we do an additional check to make sure to return the correct value. 104 | 105 | if ($result > 0) { 106 | return 1; 107 | } elseif ($result < 0) { 108 | return -1; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | /** 115 | * Divides this value by the given value. 116 | * 117 | * @param string $value The value to divide by. 118 | * @return BigInteger 119 | */ 120 | public function divide(string $value): BigInteger 121 | { 122 | $gmp = $this->initValue($value); 123 | 124 | $calculatedValue = gmp_div_q($this->value, $gmp, GMP_ROUND_ZERO); 125 | 126 | return $this->assignValue($calculatedValue); 127 | } 128 | 129 | /** 130 | * Calculates factorial of this value. 131 | * 132 | * @return BigInteger 133 | */ 134 | public function factorial(): BigInteger 135 | { 136 | $calculatedValue = gmp_fact($this->getValue()); 137 | 138 | return $this->assignValue($calculatedValue); 139 | } 140 | 141 | /** 142 | * Performs a modulo operation with the given number. 143 | * 144 | * @param string $value The value to perform a modulo operation with. 145 | * @return BigInteger 146 | */ 147 | public function mod(string $value): BigInteger 148 | { 149 | $gmp = $this->initValue($value); 150 | 151 | $calculatedValue = gmp_mod($this->value, $gmp); 152 | 153 | return $this->assignValue($calculatedValue); 154 | } 155 | 156 | /** 157 | * Multiplies the given value with this value. 158 | * 159 | * @param string $value The value to multiply with. 160 | * @return BigInteger 161 | */ 162 | public function multiply(string $value): BigInteger 163 | { 164 | $gmp = $this->initValue($value); 165 | 166 | $calculatedValue = gmp_mul($this->value, $gmp); 167 | 168 | return $this->assignValue($calculatedValue); 169 | } 170 | 171 | /** 172 | * Negates the value. 173 | * 174 | * @return BigInteger 175 | */ 176 | public function negate(): BigInteger 177 | { 178 | $calculatedValue = gmp_neg($this->value); 179 | 180 | return $this->assignValue($calculatedValue); 181 | } 182 | 183 | /** 184 | * Performs a power operation with the given number. 185 | * 186 | * @param int $value The value to perform a power operation with. 187 | * @return BigInteger 188 | */ 189 | public function pow(int $value): BigInteger 190 | { 191 | $calculatedValue = gmp_pow($this->value, $value); 192 | 193 | return $this->assignValue($calculatedValue); 194 | } 195 | 196 | /** 197 | * Subtracts the given value from this value. 198 | * 199 | * @param string $value The value to subtract. 200 | * @return BigInteger 201 | */ 202 | public function subtract(string $value): BigInteger 203 | { 204 | $gmp = $this->initValue($value); 205 | 206 | $calculatedValue = gmp_sub($this->value, $gmp); 207 | 208 | return $this->assignValue($calculatedValue); 209 | } 210 | 211 | /** 212 | * Checks if the big integr is the prime number. 213 | * 214 | * @param float $probabilityFactor A normalized factor between 0 and 1 used for checking the probability. 215 | * @return bool Returns true if the number is a prime number false if not. 216 | */ 217 | public function isPrimeNumber(float $probabilityFactor = 1.0): bool 218 | { 219 | $reps = (int)floor(($probabilityFactor * 5.0) + 5.0); 220 | 221 | if ($reps < 5 || $reps > 10) { 222 | throw new InvalidArgumentException('The provided probability number should be 5 to 10.'); 223 | } 224 | 225 | return gmp_prob_prime($this->value, $reps) !== 0; 226 | } 227 | 228 | /** 229 | * Checks if this object is mutable. 230 | * 231 | * @return bool 232 | */ 233 | public function isMutable(): bool 234 | { 235 | return $this->mutable; 236 | } 237 | 238 | /** 239 | * Converts this class to a string. 240 | * 241 | * @return string 242 | */ 243 | public function toString(): string 244 | { 245 | return $this->getValue(); 246 | } 247 | 248 | /** 249 | * Converts this class to a string. 250 | * 251 | * @return string 252 | */ 253 | public function __toString(): string 254 | { 255 | return $this->toString(); 256 | } 257 | 258 | /** 259 | * A helper method to assign the given value. 260 | * 261 | * @param GMP $value The value to assign. 262 | * @return BigInteger 263 | */ 264 | private function assignValue(GMP $value): BigInteger 265 | { 266 | $rawValue = gmp_strval($value); 267 | 268 | if ($this->isMutable()) { 269 | $this->value = gmp_init($rawValue); 270 | 271 | return $this; 272 | } 273 | 274 | return new BigInteger($rawValue, false); 275 | } 276 | 277 | /** 278 | * Creates a new GMP object. 279 | * 280 | * @param string $value The value to initialize with. 281 | * @return GMP 282 | * @throws InvalidArgumentException Thrown when the value is invalid. 283 | */ 284 | private function initValue(string $value): GMP 285 | { 286 | $result = @gmp_init($value); 287 | 288 | if ($result === false) { 289 | throw new InvalidArgumentException('The provided number is invalid.'); 290 | } 291 | 292 | return $result; 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/Support/Crypto.php: -------------------------------------------------------------------------------- 1 | 256) { 15 | die("Invalid Base: " . $base); 16 | } 17 | bcscale(0); 18 | $value = ""; 19 | if (!$digits) { 20 | $digits = self::digits($base); 21 | } 22 | while ($dec > $base - 1) { 23 | $rest = bcmod($dec, $base); 24 | $dec = bcdiv($dec, $base); 25 | $value = $digits[$rest] . $value; 26 | } 27 | $value = $digits[intval($dec)] . $value; 28 | return (string)$value; 29 | } else { 30 | die('Please install BCMATH'); 31 | } 32 | } 33 | 34 | public static function base2dec($value, $base, $digits = false) 35 | { 36 | if (extension_loaded('bcmath')) { 37 | if ($base < 2 || $base > 256) { 38 | die("Invalid Base: " . $base); 39 | } 40 | bcscale(0); 41 | if ($base < 37) { 42 | $value = strtolower($value); 43 | } 44 | if (!$digits) { 45 | $digits = self::digits($base); 46 | } 47 | $size = strlen($value); 48 | $dec = "0"; 49 | for ($loop = 0; $loop < $size; $loop++) { 50 | $element = strpos($digits, $value[$loop]); 51 | $power = bcpow($base, $size - $loop - 1); 52 | $dec = bcadd($dec, bcmul($element, $power)); 53 | } 54 | return (string)$dec; 55 | } else { 56 | die('Please install BCMATH'); 57 | } 58 | } 59 | 60 | public static function digits($base) 61 | { 62 | if ($base > 64) { 63 | $digits = ""; 64 | for ($loop = 0; $loop < 256; $loop++) { 65 | $digits .= chr($loop); 66 | } 67 | } else { 68 | $digits = "0123456789abcdefghijklmnopqrstuvwxyz"; 69 | $digits .= "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; 70 | } 71 | $digits = substr($digits, 0, $base); 72 | return (string)$digits; 73 | } 74 | 75 | public static function bin2bc($num) 76 | { 77 | return self::base2dec($num, 256); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Support/Hash.php: -------------------------------------------------------------------------------- 1 | > 31)) & (0xFFFFFFFF), 39 | $bc[($i + 4) % 5][1] ^ (($bc[($i + 1) % 5][1] << 1) | ($bc[($i + 1) % 5][0] >> 31)) & (0xFFFFFFFF) 40 | ]; 41 | 42 | for ($j = 0; $j < 25; $j += 5) { 43 | $st[$j + $i] = [ 44 | $st[$j + $i][0] ^ $t[0], 45 | $st[$j + $i][1] ^ $t[1] 46 | ]; 47 | } 48 | } 49 | 50 | // Rho Pi 51 | $t = $st[1]; 52 | for ($i = 0; $i < 24; $i++) { 53 | $j = self::$keccakf_piln[$i]; 54 | 55 | $bc[0] = $st[$j]; 56 | 57 | $n = self::$keccakf_rotc[$i]; 58 | $hi = $t[0]; 59 | $lo = $t[1]; 60 | if ($n >= 32) { 61 | $n -= 32; 62 | $hi = $t[1]; 63 | $lo = $t[0]; 64 | } 65 | 66 | $st[$j] =[ 67 | (($hi << $n) | ($lo >> (32 - $n))) & (0xFFFFFFFF), 68 | (($lo << $n) | ($hi >> (32 - $n))) & (0xFFFFFFFF) 69 | ]; 70 | 71 | $t = $bc[0]; 72 | } 73 | 74 | // Chi 75 | for ($j = 0; $j < 25; $j += 5) { 76 | for ($i = 0; $i < 5; $i++) { 77 | $bc[$i] = $st[$j + $i]; 78 | } 79 | for ($i = 0; $i < 5; $i++) { 80 | $st[$j + $i] = [ 81 | $st[$j + $i][0] ^ ~$bc[($i + 1) % 5][0] & $bc[($i + 2) % 5][0], 82 | $st[$j + $i][1] ^ ~$bc[($i + 1) % 5][1] & $bc[($i + 2) % 5][1] 83 | ]; 84 | } 85 | } 86 | 87 | // Iota 88 | $st[0] = [ 89 | $st[0][0] ^ $keccakf_rndc[$round][0], 90 | $st[0][1] ^ $keccakf_rndc[$round][1] 91 | ]; 92 | } 93 | } 94 | 95 | private static function keccak64($in_raw, int $capacity, int $outputlength, $suffix, bool $raw_output): string { 96 | $capacity /= 8; 97 | 98 | $inlen = mb_strlen($in_raw, self::ENCODING); 99 | 100 | $rsiz = 200 - 2 * $capacity; 101 | $rsizw = $rsiz / 8; 102 | 103 | $st = []; 104 | for ($i = 0; $i < 25; $i++) { 105 | $st[] = [0, 0]; 106 | } 107 | 108 | for ($in_t = 0; $inlen >= $rsiz; $inlen -= $rsiz, $in_t += $rsiz) { 109 | for ($i = 0; $i < $rsizw; $i++) { 110 | $t = unpack('V*', mb_substr($in_raw, $i * 8 + $in_t, 8, self::ENCODING)); 111 | 112 | $st[$i] = [ 113 | $st[$i][0] ^ $t[2], 114 | $st[$i][1] ^ $t[1] 115 | ]; 116 | } 117 | 118 | self::keccakf64($st, self::KECCAK_ROUNDS); 119 | } 120 | 121 | $temp = mb_substr($in_raw, $in_t, $inlen, self::ENCODING); 122 | $temp = str_pad($temp, $rsiz, "\x0", STR_PAD_RIGHT); 123 | 124 | $temp[$inlen] = chr($suffix); 125 | $temp[$rsiz - 1] = chr(ord($temp[$rsiz - 1]) | 0x80); 126 | 127 | for ($i = 0; $i < $rsizw; $i++) { 128 | $t = unpack('V*', mb_substr($temp, $i * 8, 8, self::ENCODING)); 129 | 130 | $st[$i] = [ 131 | $st[$i][0] ^ $t[2], 132 | $st[$i][1] ^ $t[1] 133 | ]; 134 | } 135 | 136 | self::keccakf64($st, self::KECCAK_ROUNDS); 137 | 138 | $out = ''; 139 | for ($i = 0; $i < 25; $i++) { 140 | $out .= $t = pack('V*', $st[$i][1], $st[$i][0]); 141 | } 142 | $r = mb_substr($out, 0, $outputlength / 8, self::ENCODING); 143 | 144 | return $raw_output ? $r : bin2hex($r); 145 | } 146 | 147 | private static function keccakf32(&$st, $rounds): void { 148 | $keccakf_rndc = [ 149 | [0x0000, 0x0000, 0x0000, 0x0001], [0x0000, 0x0000, 0x0000, 0x8082], [0x8000, 0x0000, 0x0000, 0x0808a], [0x8000, 0x0000, 0x8000, 0x8000], 150 | [0x0000, 0x0000, 0x0000, 0x808b], [0x0000, 0x0000, 0x8000, 0x0001], [0x8000, 0x0000, 0x8000, 0x08081], [0x8000, 0x0000, 0x0000, 0x8009], 151 | [0x0000, 0x0000, 0x0000, 0x008a], [0x0000, 0x0000, 0x0000, 0x0088], [0x0000, 0x0000, 0x8000, 0x08009], [0x0000, 0x0000, 0x8000, 0x000a], 152 | [0x0000, 0x0000, 0x8000, 0x808b], [0x8000, 0x0000, 0x0000, 0x008b], [0x8000, 0x0000, 0x0000, 0x08089], [0x8000, 0x0000, 0x0000, 0x8003], 153 | [0x8000, 0x0000, 0x0000, 0x8002], [0x8000, 0x0000, 0x0000, 0x0080], [0x0000, 0x0000, 0x0000, 0x0800a], [0x8000, 0x0000, 0x8000, 0x000a], 154 | [0x8000, 0x0000, 0x8000, 0x8081], [0x8000, 0x0000, 0x0000, 0x8080], [0x0000, 0x0000, 0x8000, 0x00001], [0x8000, 0x0000, 0x8000, 0x8008] 155 | ]; 156 | 157 | $bc = []; 158 | for ($round = 0; $round < $rounds; $round++) { 159 | 160 | // Theta 161 | for ($i = 0; $i < 5; $i++) { 162 | $bc[$i] = [ 163 | $st[$i][0] ^ $st[$i + 5][0] ^ $st[$i + 10][0] ^ $st[$i + 15][0] ^ $st[$i + 20][0], 164 | $st[$i][1] ^ $st[$i + 5][1] ^ $st[$i + 10][1] ^ $st[$i + 15][1] ^ $st[$i + 20][1], 165 | $st[$i][2] ^ $st[$i + 5][2] ^ $st[$i + 10][2] ^ $st[$i + 15][2] ^ $st[$i + 20][2], 166 | $st[$i][3] ^ $st[$i + 5][3] ^ $st[$i + 10][3] ^ $st[$i + 15][3] ^ $st[$i + 20][3] 167 | ]; 168 | } 169 | 170 | for ($i = 0; $i < 5; $i++) { 171 | $t = [ 172 | $bc[($i + 4) % 5][0] ^ ((($bc[($i + 1) % 5][0] << 1) | ($bc[($i + 1) % 5][1] >> 15)) & (0xFFFF)), 173 | $bc[($i + 4) % 5][1] ^ ((($bc[($i + 1) % 5][1] << 1) | ($bc[($i + 1) % 5][2] >> 15)) & (0xFFFF)), 174 | $bc[($i + 4) % 5][2] ^ ((($bc[($i + 1) % 5][2] << 1) | ($bc[($i + 1) % 5][3] >> 15)) & (0xFFFF)), 175 | $bc[($i + 4) % 5][3] ^ ((($bc[($i + 1) % 5][3] << 1) | ($bc[($i + 1) % 5][0] >> 15)) & (0xFFFF)) 176 | ]; 177 | 178 | for ($j = 0; $j < 25; $j += 5) { 179 | $st[$j + $i] = [ 180 | $st[$j + $i][0] ^ $t[0], 181 | $st[$j + $i][1] ^ $t[1], 182 | $st[$j + $i][2] ^ $t[2], 183 | $st[$j + $i][3] ^ $t[3] 184 | ]; 185 | } 186 | } 187 | 188 | // Rho Pi 189 | $t = $st[1]; 190 | for ($i = 0; $i < 24; $i++) { 191 | $j = self::$keccakf_piln[$i]; 192 | $bc[0] = $st[$j]; 193 | 194 | 195 | $n = self::$keccakf_rotc[$i] >> 4; 196 | $m = self::$keccakf_rotc[$i] % 16; 197 | 198 | $st[$j] = [ 199 | ((($t[(0+$n) %4] << $m) | ($t[(1+$n) %4] >> (16-$m))) & (0xFFFF)), 200 | ((($t[(1+$n) %4] << $m) | ($t[(2+$n) %4] >> (16-$m))) & (0xFFFF)), 201 | ((($t[(2+$n) %4] << $m) | ($t[(3+$n) %4] >> (16-$m))) & (0xFFFF)), 202 | ((($t[(3+$n) %4] << $m) | ($t[(0+$n) %4] >> (16-$m))) & (0xFFFF)) 203 | ]; 204 | 205 | $t = $bc[0]; 206 | } 207 | 208 | // Chi 209 | for ($j = 0; $j < 25; $j += 5) { 210 | for ($i = 0; $i < 5; $i++) { 211 | $bc[$i] = $st[$j + $i]; 212 | } 213 | for ($i = 0; $i < 5; $i++) { 214 | $st[$j + $i] = [ 215 | $st[$j + $i][0] ^ ~$bc[($i + 1) % 5][0] & $bc[($i + 2) % 5][0], 216 | $st[$j + $i][1] ^ ~$bc[($i + 1) % 5][1] & $bc[($i + 2) % 5][1], 217 | $st[$j + $i][2] ^ ~$bc[($i + 1) % 5][2] & $bc[($i + 2) % 5][2], 218 | $st[$j + $i][3] ^ ~$bc[($i + 1) % 5][3] & $bc[($i + 2) % 5][3] 219 | ]; 220 | } 221 | } 222 | 223 | // Iota 224 | $st[0] = [ 225 | $st[0][0] ^ $keccakf_rndc[$round][0], 226 | $st[0][1] ^ $keccakf_rndc[$round][1], 227 | $st[0][2] ^ $keccakf_rndc[$round][2], 228 | $st[0][3] ^ $keccakf_rndc[$round][3] 229 | ]; 230 | } 231 | } 232 | 233 | private static function keccak32($in_raw, int $capacity, int $outputlength, $suffix, bool $raw_output): string { 234 | $capacity /= 8; 235 | 236 | $inlen = mb_strlen($in_raw, self::ENCODING); 237 | 238 | $rsiz = 200 - 2 * $capacity; 239 | $rsizw = $rsiz / 8; 240 | 241 | $st = []; 242 | for ($i = 0; $i < 25; $i++) { 243 | $st[] = [0, 0, 0, 0]; 244 | } 245 | 246 | for ($in_t = 0; $inlen >= $rsiz; $inlen -= $rsiz, $in_t += $rsiz) { 247 | for ($i = 0; $i < $rsizw; $i++) { 248 | $t = unpack('v*', mb_substr($in_raw, $i * 8 + $in_t, 8, self::ENCODING)); 249 | 250 | $st[$i] = [ 251 | $st[$i][0] ^ $t[4], 252 | $st[$i][1] ^ $t[3], 253 | $st[$i][2] ^ $t[2], 254 | $st[$i][3] ^ $t[1] 255 | ]; 256 | } 257 | 258 | self::keccakf32($st, self::KECCAK_ROUNDS); 259 | } 260 | 261 | $temp = mb_substr($in_raw, $in_t, $inlen, self::ENCODING); 262 | $temp = str_pad($temp, $rsiz, "\x0", STR_PAD_RIGHT); 263 | 264 | $temp[$inlen] = chr($suffix); 265 | $temp[$rsiz - 1] = chr((int) $temp[$rsiz - 1] | 0x80); 266 | 267 | for ($i = 0; $i < $rsizw; $i++) { 268 | $t = unpack('v*', mb_substr($temp, $i * 8, 8, self::ENCODING)); 269 | 270 | $st[$i] = [ 271 | $st[$i][0] ^ $t[4], 272 | $st[$i][1] ^ $t[3], 273 | $st[$i][2] ^ $t[2], 274 | $st[$i][3] ^ $t[1] 275 | ]; 276 | } 277 | 278 | self::keccakf32($st, self::KECCAK_ROUNDS); 279 | 280 | $out = ''; 281 | for ($i = 0; $i < 25; $i++) { 282 | $out .= $t = pack('v*', $st[$i][3],$st[$i][2], $st[$i][1], $st[$i][0]); 283 | } 284 | $r = mb_substr($out, 0, $outputlength / 8, self::ENCODING); 285 | 286 | return $raw_output ? $r: bin2hex($r); 287 | } 288 | 289 | private static function keccak($in_raw, int $capacity, int $outputlength, $suffix, bool $raw_output): string { 290 | return self::$x64 291 | ? self::keccak64($in_raw, $capacity, $outputlength, $suffix, $raw_output) 292 | : self::keccak32($in_raw, $capacity, $outputlength, $suffix, $raw_output); 293 | } 294 | 295 | public static function hash($in, int $mdlen, bool $raw_output = false): string { 296 | if (!in_array($mdlen, [224, 256, 384, 512], true)) { 297 | throw new Exception('Unsupported Keccak Hash output size.'); 298 | } 299 | 300 | return self::keccak($in, $mdlen, $mdlen, self::LFSR, $raw_output); 301 | } 302 | 303 | public static function shake($in, int $security_level, int $outlen, bool $raw_output = false): string { 304 | if (!in_array($security_level, [128, 256], true)) { 305 | throw new Exception('Unsupported Keccak Shake security level.'); 306 | } 307 | 308 | return self::keccak($in, $security_level, $outlen, 0x1f, $raw_output); 309 | } 310 | 311 | } -------------------------------------------------------------------------------- /src/Support/Secp.php: -------------------------------------------------------------------------------- 1 | sign($message, $privateKey, ['canonical' => false]); 18 | 19 | return $sign->toHex() . bin2hex(implode('', array_map('chr', [$sign->getRecoveryParam()]))); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Support/Utils.php: -------------------------------------------------------------------------------- 1 | 7 | * @license https://github.com/iexbase/tron-api/blob/master/LICENSE (MIT License) 8 | * @version 1.3.4 9 | * @link https://github.com/iexbase/tron-api 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | declare(strict_types=1); 16 | 17 | namespace IEXBase\TronAPI; 18 | 19 | use Comely\DataTypes\BcNumber; 20 | use IEXBase\TronAPI\Exception\TRC20Exception; 21 | use IEXBase\TronAPI\Exception\TronException; 22 | 23 | /** 24 | * Class TRC20Contract 25 | * @package TronAPI 26 | */ 27 | class TRC20Contract 28 | { 29 | const TRX_TO_SUN = 1000000; 30 | 31 | /*** 32 | * Maximum decimal supported by the Token 33 | * 34 | * @var integer|null 35 | */ 36 | private ?int $_decimals = null; 37 | 38 | /*** 39 | * Token Name 40 | * 41 | * @var string|null 42 | */ 43 | private ?string $_name = null; 44 | 45 | /*** 46 | * Token Symbol 47 | * 48 | * @var string|null 49 | */ 50 | private ?string $_symbol = null; 51 | 52 | /** 53 | * The smart contract which issued TRC20 Token 54 | * 55 | * @var string 56 | */ 57 | private string $contractAddress; 58 | 59 | /** 60 | * ABI Data 61 | * 62 | * @var string|null 63 | */ 64 | private $abiData; 65 | 66 | /** 67 | * Fee Limit 68 | * 69 | * @var integer 70 | */ 71 | private int $feeLimit = 10; 72 | 73 | /** 74 | * Base Tron object 75 | * 76 | * @var Tron 77 | */ 78 | protected Tron $_tron; 79 | 80 | /** 81 | * Total Supply 82 | * 83 | * @var string|null 84 | */ 85 | private ?string $_totalSupply = null; 86 | 87 | /** 88 | * Create Trc20 Contract 89 | * 90 | * @param Tron $tron 91 | * @param string $contractAddress 92 | * @param string|null $abi 93 | */ 94 | public function __construct(Tron $tron, string $contractAddress, string $abi = null) 95 | { 96 | $this->_tron = $tron; 97 | 98 | // If abi is absent, then it takes by default 99 | if(is_null($abi)) { 100 | $abi = file_get_contents(__DIR__.'/trc20.json'); 101 | } 102 | 103 | $this->abiData = json_decode($abi, true); 104 | $this->contractAddress = $contractAddress; 105 | } 106 | 107 | /** 108 | * Debug Info 109 | * 110 | * @return array 111 | * @throws TronException 112 | */ 113 | public function __debugInfo(): array 114 | { 115 | return $this->array(); 116 | } 117 | 118 | /** 119 | * Clears cached values 120 | * 121 | * @return void 122 | */ 123 | public function clearCached(): void 124 | { 125 | $this->_name = null; 126 | $this->_symbol = null; 127 | $this->_decimals = null; 128 | $this->_totalSupply = null; 129 | } 130 | 131 | /** 132 | * All data 133 | * 134 | * @throws TronException 135 | */ 136 | public function array(): array 137 | { 138 | return [ 139 | 'name' => $this->name(), 140 | 'symbol' => $this->symbol(), 141 | 'decimals' => $this->decimals(), 142 | 'totalSupply' => $this->totalSupply(true) 143 | ]; 144 | } 145 | 146 | /** 147 | * Get token name 148 | * 149 | * @return string 150 | * @throws TronException 151 | */ 152 | public function name(): string 153 | { 154 | if ($this->_name) { 155 | return $this->_name; 156 | } 157 | 158 | $result = $this->trigger('name', null, []); 159 | $name = $result[0] ?? null; 160 | 161 | if (!is_string($name)) { 162 | throw new TRC20Exception('Failed to retrieve TRC20 token name'); 163 | } 164 | 165 | $this->_name = $this->cleanStr($name); 166 | return $this->_name; 167 | } 168 | 169 | /** 170 | * Get symbol name 171 | * 172 | * @return string 173 | * @throws TronException 174 | */ 175 | public function symbol(): string 176 | { 177 | if ($this->_symbol) { 178 | return $this->_symbol; 179 | } 180 | $result = $this->trigger('symbol', null, []); 181 | $code = $result[0] ?? null; 182 | 183 | if (!is_string($code)) { 184 | throw new TRC20Exception('Failed to retrieve TRRC20 token symbol'); 185 | } 186 | 187 | $this->_symbol = $this->cleanStr($code); 188 | return $this->_symbol; 189 | } 190 | 191 | /** 192 | * The total number of tokens issued on the main network 193 | * 194 | * @param bool $scaled 195 | * @return string 196 | * @throws Exception\TronException 197 | * @throws TRC20Exception 198 | */ 199 | public function totalSupply(bool $scaled = true): string 200 | { 201 | if (!$this->_totalSupply) { 202 | 203 | $result = $this->trigger('totalSupply', null, []); 204 | $totalSupply = $result[0]->toString() ?? null; 205 | 206 | if (!is_string($totalSupply) || !preg_match('/^[0-9]+$/', $totalSupply)) { 207 | throw new TRC20Exception('Failed to retrieve TRC20 token totalSupply'); 208 | } 209 | 210 | $this->_totalSupply = $totalSupply; 211 | } 212 | 213 | return $scaled ? $this->decimalValue($this->_totalSupply, $this->decimals()) : $this->_totalSupply; 214 | } 215 | 216 | /** 217 | * Maximum decimal supported by the Token 218 | * 219 | * @throws TRC20Exception 220 | * @throws TronException 221 | */ 222 | public function decimals(): int 223 | { 224 | if ($this->_decimals) { 225 | return $this->_decimals; 226 | } 227 | 228 | $result = $this->trigger('decimals', null, []); 229 | $scale = intval($result[0]->toString() ?? null); 230 | 231 | if (is_null($scale)) { 232 | throw new TRC20Exception('Failed to retrieve TRC20 token decimals/scale value'); 233 | } 234 | 235 | $this->_decimals = $scale; 236 | return $this->_decimals; 237 | } 238 | 239 | /** 240 | * Balance TRC20 contract 241 | * 242 | * @param string|null $address 243 | * @param bool $scaled 244 | * @return string 245 | * @throws TRC20Exception 246 | * @throws TronException 247 | */ 248 | public function balanceOf(string $address = null, bool $scaled = true): string 249 | { 250 | if(is_null($address)) 251 | $address = $this->_tron->address['base58']; 252 | 253 | $addr = str_pad($this->_tron->address2HexString($address), 64, "0", STR_PAD_LEFT); 254 | $result = $this->trigger('balanceOf', $address, [$addr]); 255 | $balance = $result[0]->toString(); 256 | 257 | if (!is_string($balance) || !preg_match('/^[0-9]+$/', $balance)) { 258 | throw new TRC20Exception( 259 | sprintf('Failed to retrieve TRC20 token balance of address "%s"', $addr) 260 | ); 261 | } 262 | 263 | return $scaled ? $this->decimalValue($balance, $this->decimals()) : $balance; 264 | } 265 | 266 | /** 267 | * Send TRC20 contract 268 | * 269 | * @param string $to 270 | * @param string $amount 271 | * @param string|null $from 272 | * @return array 273 | * @throws TRC20Exception 274 | * @throws TronException 275 | */ 276 | public function transfer(string $to, string $amount, string $from = null): array 277 | { 278 | if($from == null) { 279 | $from = $this->_tron->address['base58']; 280 | } 281 | 282 | $feeLimitInSun = bcmul((string)$this->feeLimit, (string)self::TRX_TO_SUN); 283 | 284 | if (!is_numeric($this->feeLimit) OR $this->feeLimit <= 0) { 285 | throw new TRC20Exception('fee_limit is required.'); 286 | } else if($this->feeLimit > 1000) { 287 | throw new TRC20Exception('fee_limit must not be greater than 1000 TRX.'); 288 | } 289 | 290 | $tokenAmount = bcmul($amount, bcpow("10", (string)$this->decimals(), 0), 0); 291 | 292 | $transfer = $this->_tron->getTransactionBuilder() 293 | ->triggerSmartContract( 294 | $this->abiData, 295 | $this->_tron->address2HexString($this->contractAddress), 296 | 'transfer', 297 | [$this->_tron->address2HexString($to), $tokenAmount], 298 | $feeLimitInSun, 299 | $this->_tron->address2HexString($from) 300 | ); 301 | 302 | $signedTransaction = $this->_tron->signTransaction($transfer); 303 | $response = $this->_tron->sendRawTransaction($signedTransaction); 304 | 305 | return array_merge($response, $signedTransaction); 306 | } 307 | 308 | /** 309 | * TRC20 All transactions 310 | * 311 | * @param string $address 312 | * @param int $limit 313 | * @return array 314 | * 315 | * @throws TronException 316 | */ 317 | public function getTransactions(string $address, int $limit = 100): array 318 | { 319 | return $this->_tron->getManager() 320 | ->request("v1/accounts/{$address}/transactions/trc20?limit={$limit}&contract_address={$this->contractAddress}", [], 'get'); 321 | } 322 | 323 | /** 324 | * Get transaction info by contract address 325 | * 326 | * @throws TronException 327 | */ 328 | public function getTransactionInfoByContract(array $options = []): array 329 | { 330 | return $this->_tron->getManager() 331 | ->request("v1/contracts/{$this->contractAddress}/transactions?".http_build_query($options), [],'get'); 332 | } 333 | 334 | /** 335 | * Get TRC20 token holder balances 336 | * 337 | * @throws TronException 338 | */ 339 | public function getTRC20TokenHolderBalance(array $options = []): array 340 | { 341 | return $this->_tron->getManager() 342 | ->request("v1/contracts/{$this->contractAddress}/tokens?".http_build_query($options), [],'get'); 343 | } 344 | 345 | /** 346 | * Find transaction 347 | * 348 | * @param string $transaction_id 349 | * @return array 350 | * @throws TronException 351 | */ 352 | public function getTransaction(string $transaction_id): array 353 | { 354 | return $this->_tron->getManager() 355 | ->request('/wallet/gettransactioninfobyid', ['value' => $transaction_id], 'post'); 356 | } 357 | 358 | /** 359 | * Config trigger 360 | * 361 | * @param $function 362 | * @param null $address 363 | * @param array $params 364 | * @return mixed 365 | * @throws TronException 366 | */ 367 | private function trigger($function, $address = null, array $params = []) 368 | { 369 | $owner_address = is_null($address) ? '410000000000000000000000000000000000000000' : $this->_tron->address2HexString($address); 370 | 371 | return $this->_tron->getTransactionBuilder() 372 | ->triggerConstantContract($this->abiData, $this->_tron->address2HexString($this->contractAddress), $function, $params, $owner_address); 373 | } 374 | 375 | /** 376 | * @param string $int 377 | * @param int $scale 378 | * @return string 379 | */ 380 | protected function decimalValue(string $int, int $scale = 18): string 381 | { 382 | return (new BcNumber($int))->divide(pow(10, $scale), $scale)->value(); 383 | } 384 | 385 | /** 386 | * @param string $str 387 | * @return string 388 | */ 389 | public function cleanStr(string $str): string 390 | { 391 | return preg_replace('/[^\w.-]/', '', trim($str)); 392 | } 393 | 394 | /** 395 | * Set fee limit 396 | * 397 | * @param int $fee_limit 398 | * @return TRC20Contract 399 | */ 400 | public function setFeeLimit(int $fee_limit) : TRC20Contract 401 | { 402 | $this->feeLimit = $fee_limit; 403 | return $this; 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/TransactionBuilder.php: -------------------------------------------------------------------------------- 1 | tron = $tron; 27 | } 28 | 29 | /** 30 | * Creates a transaction of transfer. 31 | * If the recipient address does not exist, a corresponding account will be created on the blockchain. 32 | * 33 | * @param string $to 34 | * @param float $amount 35 | * @param string|null $from 36 | * @param string|null $message 37 | * @return array 38 | * @throws TronException 39 | */ 40 | public function sendTrx(string $to, float $amount, string $from = null, string $message = null) 41 | { 42 | if ($amount < 0) { 43 | throw new TronException('Invalid amount provided'); 44 | } 45 | 46 | if(is_null($from)) { 47 | $from = $this->tron->address['hex']; 48 | } 49 | 50 | $to = $this->tron->address2HexString($to); 51 | $from = $this->tron->address2HexString($from); 52 | 53 | if ($from === $to) { 54 | throw new TronException('Cannot transfer TRX to the same account'); 55 | } 56 | 57 | $options = [ 58 | 'to_address' => $to, 59 | 'owner_address' => $from, 60 | 'amount' => $this->tron->toTron($amount), 61 | ]; 62 | 63 | if(!is_null($message)) { 64 | $params['extra_data'] = $this->tron->stringUtf8toHex($message); 65 | } 66 | 67 | return $this->tron->getManager()->request('wallet/createtransaction', $options); 68 | } 69 | 70 | /** 71 | * Transfer Token 72 | * 73 | * @param string $to 74 | * @param int $amount 75 | * @param string $tokenID 76 | * @param string|null $from 77 | * @return array 78 | * @throws TronException 79 | */ 80 | public function sendToken(string $to, int $amount, string $tokenID, string $from) 81 | { 82 | if (!is_integer($amount) or $amount <= 0) { 83 | throw new TronException('Invalid amount provided'); 84 | } 85 | 86 | if (!is_string($tokenID)) { 87 | throw new TronException('Invalid token ID provided'); 88 | } 89 | 90 | if ($to === $from) { 91 | throw new TronException('Cannot transfer tokens to the same account'); 92 | } 93 | 94 | $transfer = $this->tron->getManager()->request('wallet/transferasset', [ 95 | 'owner_address' => $this->tron->address2HexString($from), 96 | 'to_address' => $this->tron->address2HexString($to), 97 | 'asset_name' => $this->tron->stringUtf8toHex($tokenID), 98 | 'amount' => intval($amount) 99 | ]); 100 | 101 | if (array_key_exists('Error', $transfer)) { 102 | throw new TronException($transfer['Error']); 103 | } 104 | return $transfer; 105 | } 106 | 107 | /** 108 | * Purchase a Token 109 | * 110 | * @param $issuerAddress 111 | * @param $tokenID 112 | * @param $amount 113 | * @param $buyer 114 | * @return array 115 | * @throws TronException 116 | */ 117 | public function purchaseToken($issuerAddress, $tokenID, $amount, $buyer) 118 | { 119 | if (!is_string($tokenID)) { 120 | throw new TronException('Invalid token ID provided'); 121 | } 122 | 123 | if (!is_integer($amount) and $amount <= 0) { 124 | throw new TronException('Invalid amount provided'); 125 | } 126 | 127 | $purchase = $this->tron->getManager()->request('wallet/participateassetissue', [ 128 | 'to_address' => $this->tron->address2HexString($issuerAddress), 129 | 'owner_address' => $this->tron->address2HexString($buyer), 130 | 'asset_name' => $this->tron->stringUtf8toHex($tokenID), 131 | 'amount' => $this->tron->toTron($amount) 132 | ]); 133 | 134 | if (array_key_exists('Error', $purchase)) { 135 | throw new TronException($purchase['Error']); 136 | } 137 | return $purchase; 138 | } 139 | 140 | /** 141 | * createToken 142 | * 143 | * @param array $options 144 | * @param null $issuerAddress 145 | * @return array 146 | * @throws TronException 147 | */ 148 | public function createToken($options = [], $issuerAddress = null) 149 | { 150 | $startDate = new \DateTime(); 151 | $startTimeStamp = $startDate->getTimestamp() * 1000; 152 | 153 | // Create default parameters in case of their absence 154 | if(!$options['totalSupply']) $options['totalSupply'] = 0; 155 | if(!$options['trxRatio']) $options['trxRatio'] = 1; 156 | if(!$options['tokenRatio']) $options['tokenRatio'] = 1; 157 | if(!$options['freeBandwidth']) $options['freeBandwidth'] = 0; 158 | if(!$options['freeBandwidthLimit']) $options['freeBandwidthLimit'] = 0; 159 | if(!$options['frozenAmount']) $options['frozenAmount'] = 0; 160 | if(!$options['frozenDuration']) $options['frozenDuration'] = 0; 161 | 162 | if (is_null($issuerAddress)) { 163 | $issuerAddress = $this->tron->address['hex']; 164 | } 165 | 166 | if(!$options['name'] or !is_string($options['name'])) { 167 | throw new TronException('Invalid token name provided'); 168 | } 169 | 170 | if(!$options['abbreviation'] or !is_string($options['abbreviation'])) { 171 | throw new TronException('Invalid token abbreviation provided'); 172 | } 173 | 174 | if(!is_integer($options['totalSupply']) or $options['totalSupply'] <= 0) { 175 | throw new TronException('Invalid supply amount provided'); 176 | } 177 | 178 | if(!is_integer($options['trxRatio']) or $options['trxRatio'] <= 0) { 179 | throw new TronException('TRX ratio must be a positive integer'); 180 | } 181 | 182 | if(!is_integer($options['saleStart']) or $options['saleStart'] <= $startTimeStamp) { 183 | throw new TronException('Invalid sale start timestamp provided'); 184 | } 185 | 186 | if(!is_integer($options['saleEnd']) or $options['saleEnd'] <= $options['saleStart']) { 187 | throw new TronException('Invalid sale end timestamp provided'); 188 | } 189 | 190 | if(!$options['description'] or !is_string($options['description'])) { 191 | throw new TronException('Invalid token description provided'); 192 | } 193 | 194 | if(!is_string($options['url']) || !filter_var($options['url'], FILTER_VALIDATE_URL)) { 195 | throw new TronException('Invalid token url provided'); 196 | } 197 | 198 | if(!is_integer($options['freeBandwidth']) || $options['freeBandwidth'] < 0) { 199 | throw new TronException('Invalid free bandwidth amount provided'); 200 | } 201 | 202 | if(!is_integer($options['freeBandwidthLimit']) || $options['freeBandwidthLimit '] < 0 || 203 | ($options['freeBandwidth'] && !$options['freeBandwidthLimit']) 204 | ) { 205 | throw new TronException('Invalid free bandwidth limit provided'); 206 | } 207 | 208 | if(!is_integer($options['frozenAmount']) || $options['frozenAmount '] < 0 || 209 | (!$options['frozenDuration'] && $options['frozenAmount']) 210 | ) { 211 | throw new TronException('Invalid frozen supply provided'); 212 | } 213 | 214 | if(!is_integer($options['frozenDuration']) || $options['frozenDuration '] < 0 || 215 | ($options['frozenDuration'] && !$options['frozenAmount']) 216 | ) { 217 | throw new TronException('Invalid frozen duration provided'); 218 | } 219 | 220 | $data = [ 221 | 'owner_address' => $this->tron->address2HexString($issuerAddress), 222 | 'name' => $this->tron->stringUtf8toHex($options['name']), 223 | 'abbr' => $this->tron->stringUtf8toHex($options['abbreviation']), 224 | 'description' => $this->tron->stringUtf8toHex($options['description']), 225 | 'url' => $this->tron->stringUtf8toHex($options['url']), 226 | 'total_supply' => intval($options['totalSupply']), 227 | 'trx_num' => intval($options['trxRatio']), 228 | 'num' => intval($options['tokenRatio']), 229 | 'start_time' => intval($options['saleStart']), 230 | 'end_time' => intval($options['saleEnd']), 231 | 'free_asset_net_limit' => intval($options['freeBandwidth']), 232 | 'public_free_asset_net_limit' => intval($options['freeBandwidthLimit']), 233 | 'frozen_supply' => [ 234 | 'frozen_amount' => intval($options['frozenAmount']), 235 | 'frozen_days' => intval($options['frozenDuration']), 236 | ] 237 | ]; 238 | 239 | if($options['precision'] && !is_nan(intval($options['precision']))) { 240 | $data['precision'] = intval($options['precision']); 241 | } 242 | 243 | if($options['voteScore'] && !is_nan(intval($options['voteScore']))) { 244 | $data['vote_score'] = intval($options['voteScore']); 245 | } 246 | 247 | return $this->tron->getManager()->request('wallet/createassetissue', $data); 248 | } 249 | 250 | /** 251 | * Freezes an amount of TRX. 252 | * Will give bandwidth OR Energy and TRON Power(voting rights) to the owner of the frozen tokens. 253 | * 254 | * @param float $amount 255 | * @param int $duration 256 | * @param string $resource 257 | * @param string|null $address 258 | * @return array 259 | * @throws TronException 260 | */ 261 | public function freezeBalance(float $amount = 0, int $duration = 3, string $resource = 'BANDWIDTH', string $address = null) 262 | { 263 | if(empty($address)) 264 | throw new TronException('Address not specified'); 265 | 266 | if (!in_array($resource, ['BANDWIDTH', 'ENERGY'])) { 267 | throw new TronException('Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"'); 268 | } 269 | 270 | if (!is_float($amount)) { 271 | throw new TronException('Invalid amount provided'); 272 | } 273 | 274 | if(!is_integer($duration) and $duration < 3) { 275 | throw new TronException('Invalid duration provided, minimum of 3 days'); 276 | } 277 | 278 | return $this->tron->getManager()->request('wallet/freezebalance', [ 279 | 'owner_address' => $this->tron->address2HexString($address), 280 | 'frozen_balance' => $this->tron->toTron($amount), 281 | 'frozen_duration' => $duration, 282 | 'resource' => $resource 283 | ]); 284 | } 285 | 286 | /** 287 | * Unfreeze TRX that has passed the minimum freeze duration. 288 | * Unfreezing will remove bandwidth and TRON Power. 289 | * 290 | * @param string $resource 291 | * @param string $owner_address 292 | * @return array 293 | * @throws TronException 294 | */ 295 | public function unfreezeBalance(string $resource = 'BANDWIDTH', string $owner_address = null) 296 | { 297 | if(is_null($owner_address)) { 298 | throw new TronException('Owner Address not specified'); 299 | } 300 | 301 | if (!in_array($resource, ['BANDWIDTH', 'ENERGY'])) { 302 | throw new TronException('Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"'); 303 | } 304 | 305 | return $this->tron->getManager()->request('wallet/unfreezebalance', [ 306 | 'owner_address' => $this->tron->address2HexString($owner_address), 307 | 'resource' => $resource 308 | ]); 309 | } 310 | 311 | /** 312 | * Withdraw Super Representative rewards, useable every 24 hours. 313 | * 314 | * @param string $owner_address 315 | * @return array 316 | * @throws TronException 317 | */ 318 | public function withdrawBlockRewards($owner_address = null) 319 | { 320 | $withdraw = $this->tron->getManager()->request('wallet/withdrawbalance', [ 321 | 'owner_address' => $this->tron->address2HexString($owner_address) 322 | ]); 323 | 324 | if (array_key_exists('Error', $withdraw)) { 325 | throw new TronException($withdraw['Error']); 326 | } 327 | return $withdraw; 328 | } 329 | 330 | /** 331 | * Update a Token's information 332 | * 333 | * @param string $description 334 | * @param string $url 335 | * @param int $freeBandwidth 336 | * @param int $freeBandwidthLimit 337 | * @param $address 338 | * @return array 339 | * @throws TronException 340 | */ 341 | public function updateToken(string $description, string $url, int $freeBandwidth = 0, int $freeBandwidthLimit = 0, $address = null) 342 | { 343 | if(is_null($address)) { 344 | throw new TronException('Owner Address not specified'); 345 | } 346 | 347 | if (!is_integer($freeBandwidth) || $freeBandwidth < 0) { 348 | throw new TronException('Invalid free bandwidth amount provided'); 349 | } 350 | 351 | if (!is_integer($freeBandwidthLimit) || $freeBandwidthLimit < 0 && ($freeBandwidth && !$freeBandwidthLimit)) { 352 | throw new TronException('Invalid free bandwidth limit provided'); 353 | } 354 | 355 | return $this->tron->getManager()->request('wallet/updateasset', [ 356 | 'owner_address' => $this->tron->address2HexString($address), 357 | 'description' => $this->tron->stringUtf8toHex($description), 358 | 'url' => $this->tron->stringUtf8toHex($url), 359 | 'new_limit' => intval($freeBandwidth), 360 | 'new_public_limit' => intval($freeBandwidthLimit) 361 | ]); 362 | } 363 | 364 | /** 365 | * updateEnergyLimit 366 | * 367 | * @param string $contractAddress 368 | * @param int $originEnergyLimit 369 | * @param string $ownerAddress 370 | * @return array 371 | * @throws TronException 372 | */ 373 | public function updateEnergyLimit(string $contractAddress, int $originEnergyLimit, string $ownerAddress) 374 | { 375 | $contractAddress = $this->tron->address2HexString($contractAddress); 376 | $ownerAddress = $this->tron->address2HexString($ownerAddress); 377 | 378 | if($originEnergyLimit < 0 || $originEnergyLimit > 10000000) { 379 | throw new TronException('Invalid originEnergyLimit provided'); 380 | } 381 | 382 | return $this->tron->getManager()->request('wallet/updateenergylimit', [ 383 | 'owner_address' => $this->tron->address2HexString($ownerAddress), 384 | 'contract_address' => $this->tron->address2HexString($contractAddress), 385 | 'origin_energy_limit' => $originEnergyLimit 386 | ]); 387 | } 388 | 389 | /** 390 | * updateSetting 391 | * 392 | * @param string $contractAddress 393 | * @param int $userFeePercentage 394 | * @param string $ownerAddress 395 | * @return array 396 | * @throws TronException 397 | */ 398 | public function updateSetting(string $contractAddress, int $userFeePercentage, string $ownerAddress) 399 | { 400 | $contractAddress = $this->tron->address2HexString($contractAddress); 401 | $ownerAddress = $this->tron->address2HexString($ownerAddress); 402 | 403 | if($userFeePercentage < 0 || $userFeePercentage > 1000) { 404 | throw new TronException('Invalid userFeePercentage provided'); 405 | } 406 | 407 | return $this->tron->getManager()->request('wallet/updatesetting', [ 408 | 'owner_address' => $this->tron->address2HexString($ownerAddress), 409 | 'contract_address' => $this->tron->address2HexString($contractAddress), 410 | 'consume_user_resource_percent' => $userFeePercentage 411 | ]); 412 | } 413 | /** 414 | * Contract Balance 415 | * @param string $address $tron->toHex('Txxxxx'); 416 | * 417 | * @return array 418 | */ 419 | public function contractbalance($adres) 420 | { 421 | $trc20=array(); 422 | $abi=json_decode('{"entrys": [{"constant": true,"name": "name","outputs": [{"type": "string"}],"type": "Function","stateMutability": "View"},{"name": "approve","inputs": [{"name": "_spender","type": "address"},{"name": "_value","type": "uint256"}],"outputs": [{"type": "bool"}],"type": "Function","stateMutability": "Nonpayable"},{"name": "setCanApproveCall","inputs": [{"name": "_val","type": "bool"}],"type": "Function","stateMutability": "Nonpayable"},{"constant": true,"name": "totalSupply","outputs": [{"type": "uint256"}],"type": "Function","stateMutability": "View"},{"name": "transferFrom","inputs": [{"name": "_from","type": "address"},{"name": "_to","type": "address"},{"name": "_value","type": "uint256"}],"outputs": [{"type": "bool"}],"type": "Function","stateMutability": "Nonpayable"},{"constant": true,"name": "decimals","outputs": [{"type": "uint8"}],"type": "Function","stateMutability": "View"},{"name": "setCanBurn","inputs": [{"name": "_val","type": "bool"}],"type": "Function","stateMutability": "Nonpayable"},{"name": "burn","inputs": [{"name": "_value","type": "uint256"}],"outputs": [{"name": "success","type": "bool"}],"type": "Function","stateMutability": "Nonpayable"},{"constant": true,"name": "balanceOf","inputs": [{"name": "_owner","type": "address"}],"outputs": [{"type": "uint256"}],"type": "Function","stateMutability": "View"},{"constant": true,"name": "symbol","outputs": [{"type": "string"}],"type": "Function","stateMutability": "View"},{"name": "transfer","inputs": [{"name": "_to","type": "address"},{"name": "_value","type": "uint256"}],"outputs": [{"type": "bool"}],"type": "Function","stateMutability": "Nonpayable"},{"constant": true,"name": "canBurn","outputs": [{"type": "bool"}],"type": "Function","stateMutability": "View"},{"name": "approveAndCall","inputs": [{"name": "_spender","type": "address"},{"name": "_value","type": "uint256"},{"name": "_extraData","type": "bytes"}],"outputs": [{"name": "success","type": "bool"}],"type": "Function","stateMutability": "Nonpayable"},{"constant": true,"name": "allowance","inputs": [{"name": "_owner","type": "address"},{"name": "_spender","type": "address"}],"outputs": [{"type": "uint256"}],"type": "Function","stateMutability": "View"},{"name": "transferOwnership","inputs": [{"name": "_newOwner","type": "address"}],"type": "Function","stateMutability": "Nonpayable"},{"constant": true,"name": "canApproveCall","outputs": [{"type": "bool"}],"type": "Function","stateMutability": "View"},{"type": "Constructor","stateMutability": "Nonpayable"},{"name": "Transfer","inputs": [{"indexed": true,"name": "_from","type": "address"},{"indexed": true,"name": "_to","type": "address"},{"name": "_value","type": "uint256"}],"type": "Event"},{"name": "Approval","inputs": [{"indexed": true,"name": "_owner","type": "address"},{"indexed": true,"name": "_spender","type": "address"},{"name": "_value","type": "uint256"}],"type": "Event"},{"name": "Burn","inputs": [{"indexed": true,"name": "_from","type": "address"},{"name": "_value","type": "uint256"}],"type": "Event"}]}',true); 423 | $feeLimit=1000000; 424 | $func="balanceOf"; 425 | $jsonData = json_decode(file_get_contents("https://apilist.tronscan.org/api/token_trc20?sort=issue_time&limit=100&start=0"),true); 426 | foreach($jsonData["trc20_tokens"] as $key =>$item) 427 | { 428 | $owner=$item["contract_address"]; 429 | $params=array("0"=>$this->tron->toHex($adres)); 430 | $result = $this->tron->getTransactionBuilder()->triggerSmartContract( 431 | $abi['entrys'], 432 | $this->tron->toHex($owner), 433 | $func, 434 | $params, 435 | $feeLimit, 436 | $this->tron->toHex($adres), 437 | 0, 438 | 0); 439 | $balance_hex=$result["0"]; 440 | $balance=0+(float)number_format($balance_hex->value/pow(10,$item["decimals"]),$item["decimals"],".",""); 441 | if($balance>0) 442 | { 443 | $trc20[]=array( 444 | "name"=>$item["name"], 445 | "symbol"=>$item["symbol"], 446 | "balance"=>$balance, 447 | "value"=>$balance_hex->value, 448 | "decimals"=>$item["decimals"], 449 | ); 450 | } 451 | } 452 | return $trc20; 453 | } 454 | 455 | /** 456 | * Triggers smart contract 457 | * 458 | * @param mixed $abi 459 | * @param string $contract $tron->toHex('Txxxxx'); 460 | * @param string $function 461 | * @param array $params array("0"=>$value); 462 | * @param integer $feeLimit 463 | * @param string $address $tron->toHex('Txxxxx'); 464 | * @param int $callValue 465 | * @param int $bandwidthLimit 466 | * 467 | * @return mixed 468 | * @throws TronException 469 | */ 470 | public function triggerSmartContract($abi, 471 | $contract, 472 | $function, 473 | $params, 474 | $feeLimit, 475 | $address, 476 | $callValue = 0, 477 | $bandwidthLimit = 0) 478 | { 479 | $func_abi = []; 480 | foreach($abi as $key =>$item) { 481 | if(isset($item['name']) && $item['name'] === $function) { 482 | $func_abi = $item; 483 | break; 484 | } 485 | } 486 | 487 | if(count($func_abi) === 0) 488 | throw new TronException("Function $function not defined in ABI"); 489 | 490 | if(!is_array($params)) 491 | throw new TronException("Function params must be an array"); 492 | 493 | if(count($func_abi['inputs']) !== count($params)) 494 | throw new TronException("Count of params and abi inputs must be identical"); 495 | 496 | if($feeLimit > 1000000000) 497 | throw new TronException('fee_limit must not be greater than 1000000000'); 498 | 499 | 500 | $inputs = array_map(function($item){ return $item['type']; },$func_abi['inputs']); 501 | $signature = $func_abi['name'].'('; 502 | if(count($inputs) > 0) 503 | $signature .= implode(',',$inputs); 504 | $signature .= ')'; 505 | 506 | $eth_abi = new Ethabi([ 507 | 'address' => new Address, 508 | 'bool' => new Boolean, 509 | 'bytes' => new Bytes, 510 | 'dynamicBytes' => new DynamicBytes, 511 | 'int' => new Integer, 512 | 'string' => new Str, 513 | 'uint' => new Uinteger, 514 | ]); 515 | $parameters = substr($eth_abi->encodeParameters($func_abi, $params),2); 516 | 517 | $result = $this->tron->getManager()->request('wallet/triggersmartcontract', [ 518 | 'contract_address' => $contract, 519 | 'function_selector' => $signature, 520 | 'parameter' => $parameters, 521 | 'owner_address' => $address, 522 | 'fee_limit' => $feeLimit, 523 | 'call_value' => $callValue, 524 | 'consume_user_resource_percent' => $bandwidthLimit, 525 | ]); 526 | 527 | if(!isset($result['result'])){ 528 | throw new TronException('No result field in response. Raw response:'.print_r($result,true)); 529 | } 530 | if(isset($result['result']['result'])) { 531 | if(count($func_abi['outputs']) >= 0 && isset($result['constant_result'])) { 532 | return $eth_abi->decodeParameters($func_abi, $result['constant_result'][0]); 533 | } 534 | return $result['transaction']; 535 | } 536 | $message = isset($result['result']['message']) ? 537 | $this->tron->hexString2Utf8($result['result']['message']) : ''; 538 | 539 | throw new TronException('Failed to execute. Error:'.$message); 540 | } 541 | 542 | /** 543 | * Triggers constant contract 544 | * 545 | * @param mixed $abi 546 | * @param string $contract $tron->toHex('Txxxxx'); 547 | * @param string $function 548 | * @param array $params array("0"=>$value); 549 | * @param string $address $tron->toHex('Txxxxx'); 550 | * 551 | * @return mixed 552 | * @throws TronException 553 | */ 554 | public function triggerConstantContract($abi, 555 | $contract, 556 | $function, 557 | $params = [], 558 | $address = '410000000000000000000000000000000000000000') 559 | { 560 | $func_abi = []; 561 | foreach($abi as $key =>$item) { 562 | if(isset($item['name']) && $item['name'] === $function) { 563 | $func_abi = $item + ['inputs' => []]; 564 | break; 565 | } 566 | } 567 | 568 | if(count($func_abi) === 0) 569 | throw new TronException("Function $function not defined in ABI"); 570 | 571 | if(!is_array($params)) 572 | throw new TronException("Function params must be an array"); 573 | 574 | if(count($func_abi['inputs']) !== count($params)) 575 | throw new TronException("Count of params and abi inputs must be identical"); 576 | 577 | 578 | $inputs = array_map(function($item){ return $item['type']; },$func_abi['inputs']); 579 | $signature = $func_abi['name'].'('; 580 | if(count($inputs) > 0) 581 | $signature .= implode(',',$inputs); 582 | $signature .= ')'; 583 | 584 | $eth_abi = new Ethabi([ 585 | 'address' => new Address, 586 | 'bool' => new Boolean, 587 | 'bytes' => new Bytes, 588 | 'dynamicBytes' => new DynamicBytes, 589 | 'int' => new Integer, 590 | 'string' => new Str, 591 | 'uint' => new Uinteger, 592 | ]); 593 | $parameters = substr($eth_abi->encodeParameters($func_abi, $params),2); 594 | 595 | $result = $this->tron->getManager()->request('wallet/triggerconstantcontract', [ 596 | 'contract_address' => $contract, 597 | 'function_selector' => $signature, 598 | 'parameter' => $parameters, 599 | 'owner_address' => $address, 600 | ]); 601 | 602 | if(!isset($result['result'])){ 603 | throw new TronException('No result field in response. Raw response:'.print_r($result,true)); 604 | } 605 | if(isset($result['result']['result'])) { 606 | if(count($func_abi['outputs']) >= 0 && isset($result['constant_result'])) { 607 | return $eth_abi->decodeParameters($func_abi, $result['constant_result'][0]); 608 | } 609 | return $result['transaction']; 610 | } 611 | $message = isset($result['result']['message']) ? 612 | $this->tron->hexString2Utf8($result['result']['message']) : ''; 613 | 614 | throw new TronException('Failed to execute. Error:'.$message); 615 | } 616 | } 617 | -------------------------------------------------------------------------------- /src/Tron.php: -------------------------------------------------------------------------------- 1 | 7 | * @license https://github.com/iexbase/tron-api/blob/master/LICENSE (MIT License) 8 | * @version 1.3.4 9 | * @link https://github.com/iexbase/tron-api 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | declare(strict_types=1); 16 | 17 | namespace IEXBase\TronAPI; 18 | 19 | use Elliptic\EC; 20 | use IEXBase\TronAPI\Exception\TRC20Exception; 21 | use IEXBase\TronAPI\Support\Base58; 22 | use IEXBase\TronAPI\Support\Base58Check; 23 | use IEXBase\TronAPI\Support\Crypto; 24 | use IEXBase\TronAPI\Support\Hash; 25 | use IEXBase\TronAPI\Support\Keccak; 26 | use IEXBase\TronAPI\Support\Utils; 27 | use IEXBase\TronAPI\Provider\HttpProviderInterface; 28 | use IEXBase\TronAPI\Exception\TronException; 29 | 30 | /** 31 | * A PHP API for interacting with the Tron (TRX) 32 | * 33 | * @package TronAPI 34 | * @author Shamsudin Serderov 35 | * @since 1.0.0 36 | */ 37 | class Tron implements TronInterface 38 | { 39 | use TronAwareTrait, 40 | Concerns\ManagesUniversal, 41 | Concerns\ManagesTronscan; 42 | 43 | const ADDRESS_SIZE = 34; 44 | const ADDRESS_PREFIX = "41"; 45 | const ADDRESS_PREFIX_BYTE = 0x41; 46 | 47 | /** 48 | * Default Address: 49 | * Example: 50 | * - base58: T**** 51 | * - hex: 41**** 52 | * 53 | * @var array 54 | */ 55 | public $address = [ 56 | 'base58' => null, 57 | 'hex' => null 58 | ]; 59 | 60 | /** 61 | * Private key 62 | * 63 | * @var string 64 | */ 65 | protected $privateKey; 66 | 67 | /** 68 | * Default block 69 | * 70 | * @var string|integer|bool 71 | */ 72 | protected $defaultBlock = 'latest'; 73 | 74 | /** 75 | * Transaction Builder 76 | * 77 | * @var TransactionBuilder 78 | */ 79 | protected $transactionBuilder; 80 | 81 | /** 82 | * Transaction Builder 83 | * 84 | * @var TransactionBuilder 85 | */ 86 | protected $trc20Contract; 87 | 88 | /** 89 | * Provider manager 90 | * 91 | * @var TronManager 92 | */ 93 | protected $manager; 94 | 95 | /** 96 | * Object Result 97 | * 98 | * @var bool 99 | */ 100 | protected $isObject = false; 101 | 102 | /** 103 | * Create a new Tron object 104 | * 105 | * @param HttpProviderInterface $fullNode 106 | * @param HttpProviderInterface $solidityNode 107 | * @param HttpProviderInterface|null $eventServer 108 | * @param HttpProviderInterface|null $signServer 109 | * @param HttpProviderInterface|null $explorer 110 | * @param string $privateKey 111 | 112 | * @throws TronException 113 | */ 114 | public function __construct(?HttpProviderInterface $fullNode = null, 115 | ?HttpProviderInterface $solidityNode = null, 116 | ?HttpProviderInterface $eventServer = null, 117 | ?HttpProviderInterface $signServer = null, 118 | ?HttpProviderInterface $explorer = null, 119 | ?string $privateKey = null) 120 | { 121 | if(!is_null($privateKey)) { 122 | $this->setPrivateKey($privateKey); 123 | } 124 | 125 | $this->setManager(new TronManager($this, [ 126 | 'fullNode' => $fullNode, 127 | 'solidityNode' => $solidityNode, 128 | 'eventServer' => $eventServer, 129 | 'signServer' => $signServer, 130 | ])); 131 | 132 | $this->transactionBuilder = new TransactionBuilder($this); 133 | } 134 | 135 | /** 136 | * Create a new tron instance if the value isn't one already. 137 | * 138 | * @param HttpProviderInterface|null $fullNode 139 | * @param HttpProviderInterface|null $solidityNode 140 | * @param HttpProviderInterface|null $eventServer 141 | * @param HttpProviderInterface|null $signServer 142 | * @param string|null $privateKey 143 | * @return static 144 | * @throws TronException 145 | */ 146 | public static function make(?HttpProviderInterface $fullNode = null, 147 | ?HttpProviderInterface $solidityNode = null, 148 | ?HttpProviderInterface $eventServer = null, 149 | ?HttpProviderInterface $signServer = null, 150 | string $privateKey = null) { 151 | return new static($fullNode, $solidityNode, $eventServer, $signServer, $privateKey); 152 | } 153 | 154 | /** 155 | * Фасад для Laravel 156 | * 157 | * @return Tron 158 | */ 159 | public function getFacade(): Tron { 160 | return $this; 161 | } 162 | 163 | /** 164 | * Enter the link to the manager nodes 165 | * 166 | * @param $providers 167 | */ 168 | public function setManager($providers) { 169 | $this->manager = $providers; 170 | } 171 | 172 | /** 173 | * Get provider manager 174 | * 175 | * @return TronManager 176 | */ 177 | public function getManager(): TronManager { 178 | return $this->manager; 179 | } 180 | 181 | 182 | /** 183 | * Contract module 184 | * 185 | * @param string $contractAddress 186 | * @param string|null $abi 187 | * @return TRC20Contract 188 | */ 189 | public function contract(string $contractAddress, string $abi = null) 190 | { 191 | return new TRC20Contract($this, $contractAddress, $abi); 192 | } 193 | 194 | /** 195 | * Set is object 196 | * 197 | * @param bool $value 198 | * @return Tron 199 | */ 200 | public function setIsObject(bool $value) 201 | { 202 | $this->isObject = boolval($value); 203 | return $this; 204 | } 205 | 206 | /** 207 | * Get Transaction Builder 208 | * 209 | * @return TransactionBuilder 210 | */ 211 | public function getTransactionBuilder(): TransactionBuilder 212 | { 213 | return $this->transactionBuilder; 214 | } 215 | 216 | /** 217 | * Check connected provider 218 | * 219 | * @param $provider 220 | * @return bool 221 | */ 222 | public function isValidProvider($provider): bool 223 | { 224 | return ($provider instanceof HttpProviderInterface); 225 | } 226 | 227 | /** 228 | * Enter the default block 229 | * 230 | * @param bool $blockID 231 | * @return void 232 | * @throws TronException 233 | */ 234 | public function setDefaultBlock($blockID = false): void 235 | { 236 | if($blockID === false || $blockID == 'latest' || $blockID == 'earliest' || $blockID === 0) { 237 | $this->defaultBlock = $blockID; 238 | return; 239 | } 240 | 241 | if(!is_integer($blockID)) { 242 | throw new TronException('Invalid block ID provided'); 243 | } 244 | 245 | $this->defaultBlock = abs($blockID); 246 | } 247 | 248 | /** 249 | * Get default block 250 | * 251 | * @return string|integer|bool 252 | */ 253 | public function getDefaultBlock() 254 | { 255 | return $this->defaultBlock; 256 | } 257 | 258 | /** 259 | * Enter your private account key 260 | * 261 | * @param string $privateKey 262 | */ 263 | public function setPrivateKey(string $privateKey): void 264 | { 265 | $this->privateKey = $privateKey; 266 | } 267 | 268 | /** 269 | * Enter your account address 270 | * 271 | * @param string $address 272 | */ 273 | public function setAddress(string $address): void 274 | { 275 | $_toHex = $this->address2HexString($address); 276 | $_fromHex = $this->hexString2Address($address); 277 | 278 | $this->address = [ 279 | 'hex' => $_toHex, 280 | 'base58' => $_fromHex 281 | ]; 282 | } 283 | 284 | /** 285 | * Get account address 286 | * 287 | * @return array 288 | */ 289 | public function getAddress(): array 290 | { 291 | return $this->address; 292 | } 293 | 294 | /** 295 | * Get customized provider data 296 | * 297 | * @return array 298 | */ 299 | public function providers(): array 300 | { 301 | return $this->manager->getProviders(); 302 | } 303 | 304 | /** 305 | * Check Connection Providers 306 | * 307 | * @return array 308 | */ 309 | public function isConnected(): array 310 | { 311 | return $this->manager->isConnected(); 312 | } 313 | 314 | /** 315 | * Last block number 316 | * 317 | * @return array 318 | * @throws TronException 319 | */ 320 | public function getCurrentBlock(): array 321 | { 322 | return $this->manager->request('wallet/getnowblock'); 323 | } 324 | 325 | /** 326 | * Will return all events matching the filters. 327 | * 328 | * @param $contractAddress 329 | * @param int $sinceTimestamp 330 | * @param string|null $eventName 331 | * @param int $blockNumber 332 | * @return array 333 | * @throws TronException 334 | */ 335 | public function getEventResult($contractAddress, int $sinceTimestamp = 0, string $eventName = null, int $blockNumber = 0) 336 | { 337 | if (!$this->isValidProvider($this->manager->eventServer())) { 338 | throw new TronException('No event server configured'); 339 | } 340 | 341 | $routeParams = []; 342 | if($eventName && !$contractAddress) { 343 | throw new TronException('Usage of event name filtering requires a contract address'); 344 | } 345 | 346 | if($blockNumber && !$eventName) 347 | throw new TronException('Usage of block number filtering requires an event name'); 348 | 349 | if($contractAddress) { 350 | array_push($routeParams, $contractAddress); 351 | } 352 | if($eventName) { 353 | array_push($routeParams, $eventName); 354 | } 355 | if($blockNumber) { 356 | array_push($routeParams, $blockNumber); 357 | } 358 | 359 | $routeParams = implode('/', $routeParams); 360 | return $this->manager->request("event/contract/{$routeParams}?since={$sinceTimestamp}"); 361 | } 362 | 363 | 364 | /** 365 | * Will return all events within a transactionID. 366 | * 367 | * @param string $transactionID 368 | * @return array 369 | * @throws TronException 370 | */ 371 | public function getEventByTransactionID(string $transactionID) 372 | { 373 | if (!$this->isValidProvider($this->manager->eventServer())) { 374 | throw new TronException('No event server configured'); 375 | } 376 | return $this->manager->request("event/transaction/{$transactionID}"); 377 | } 378 | 379 | /** 380 | * Get block details using HashString or blockNumber 381 | * 382 | * @param null $block 383 | * @return array 384 | * @throws TronException 385 | */ 386 | public function getBlock($block = null): array 387 | { 388 | $block = (is_null($block) ? $this->defaultBlock : $block); 389 | 390 | if($block === false) { 391 | throw new TronException('No block identifier provided'); 392 | } 393 | 394 | if($block == 'earliest') { 395 | $block = 0; 396 | } 397 | 398 | if($block == 'latest') { 399 | return $this->getCurrentBlock(); 400 | } 401 | 402 | if(Utils::isHex($block)) { 403 | return $this->getBlockByHash($block); 404 | } 405 | return $this->getBlockByNumber($block); 406 | } 407 | 408 | /** 409 | * Query block by ID 410 | * 411 | * @param $hashBlock 412 | * @return array 413 | * @throws TronException 414 | */ 415 | public function getBlockByHash(string $hashBlock): array 416 | { 417 | return $this->manager->request('wallet/getblockbyid', [ 418 | 'value' => $hashBlock 419 | ]); 420 | } 421 | 422 | /** 423 | * Query block by height 424 | * 425 | * @param $blockID 426 | * @return array 427 | * @throws TronException 428 | */ 429 | public function getBlockByNumber(int $blockID): array 430 | { 431 | if(!is_integer($blockID) || $blockID < 0) { 432 | throw new TronException('Invalid block number provided'); 433 | } 434 | 435 | $response = $this->manager->request('wallet/getblockbynum', [ 436 | 'num' => intval($blockID) 437 | ]); 438 | 439 | if (empty($response)) { 440 | throw new TronException('Block not found'); 441 | } 442 | return $response; 443 | } 444 | 445 | /** 446 | * Total number of transactions in a block 447 | * 448 | * @param $block 449 | * @return int 450 | * @throws TronException 451 | */ 452 | public function getBlockTransactionCount($block): int 453 | { 454 | $transaction = $this->getBlock($block)['transactions']; 455 | if(!$transaction) { 456 | return 0; 457 | } 458 | 459 | return count($transaction); 460 | } 461 | 462 | /** 463 | * Get transaction details from Block 464 | * 465 | * @param null $block 466 | * @param int $index 467 | * @return array | string 468 | * @throws TronException 469 | */ 470 | public function getTransactionFromBlock($block = null, $index = 0) 471 | { 472 | if(!is_integer($index) || $index < 0) { 473 | throw new TronException('Invalid transaction index provided'); 474 | } 475 | 476 | $transactions = $this->getBlock($block)['transactions']; 477 | if(!$transactions || count($transactions) < $index) { 478 | throw new TronException('Transaction not found in block'); 479 | } 480 | 481 | return $transactions[$index]; 482 | } 483 | 484 | /** 485 | * Query transaction based on id 486 | * 487 | * @param $transactionID 488 | * @return array 489 | * @throws TronException 490 | */ 491 | public function getTransaction(string $transactionID): array 492 | { 493 | $response = $this->manager->request('wallet/gettransactionbyid', [ 494 | 'value' => $transactionID 495 | ]); 496 | 497 | if(!$response) { 498 | throw new TronException('Transaction not found'); 499 | } 500 | 501 | return $response; 502 | } 503 | 504 | /** 505 | * Query transaction fee based on id 506 | * 507 | * @param $transactionID 508 | * @return array 509 | * @throws TronException 510 | */ 511 | public function getTransactionInfo(string $transactionID): array 512 | { 513 | return $this->manager->request('walletsolidity/gettransactioninfobyid', [ 514 | 'value' => $transactionID 515 | ]); 516 | } 517 | 518 | /** 519 | * Query the list of transactions received by an address 520 | * 521 | * @param string $address 522 | * @param int $limit 523 | * @param int $offset 524 | * @return array 525 | * @throws TronException 526 | */ 527 | public function getTransactionsToAddress(string $address, int $limit = 30, int $offset = 0) 528 | { 529 | return $this->getTransactionsRelated($address,'to', $limit, $offset); 530 | } 531 | 532 | /** 533 | * Query the list of transactions sent by an address 534 | * 535 | * @param string $address 536 | * @param int $limit 537 | * @param int $offset 538 | * @return array 539 | * @throws TronException 540 | */ 541 | public function getTransactionsFromAddress(string $address, int $limit = 30, int $offset = 0) 542 | { 543 | return $this->getTransactionsRelated($address,'from', $limit, $offset); 544 | } 545 | 546 | /** 547 | * Query information about an account 548 | * 549 | * @param $address 550 | * @return array 551 | * @throws TronException 552 | */ 553 | public function getAccount(string $address = null): array 554 | { 555 | $address = (!is_null($address) ? $this->toHex($address) : $this->address['hex']); 556 | 557 | return $this->manager->request('walletsolidity/getaccount', [ 558 | 'address' => $address 559 | ]); 560 | } 561 | 562 | /** 563 | * Getting a balance 564 | * 565 | * @param string $address 566 | * @param bool $fromTron 567 | * @return float 568 | * @throws TronException 569 | */ 570 | public function getBalance(string $address = null, bool $fromTron = false): float 571 | { 572 | $account = $this->getAccount($address); 573 | 574 | if(!array_key_exists('balance', $account)) { 575 | return 0; 576 | } 577 | 578 | return ($fromTron == true ? 579 | $this->fromTron($account['balance']) : 580 | $account['balance']); 581 | } 582 | 583 | 584 | /** 585 | * Get token balance 586 | * 587 | * @param string $address 588 | * @param int $tokenId 589 | * @param bool $fromTron 590 | * @return array|int 591 | * @throws TronException 592 | */ 593 | public function getTokenBalance(int $tokenId, string $address, bool $fromTron = false) 594 | { 595 | $account = $this->getAccount($address); 596 | 597 | if(isset($account['assetV2']) and !empty($account['assetV2']) ) 598 | { 599 | $value = array_filter($account['assetV2'], function($item) use ($tokenId) { 600 | return $item['key'] == $tokenId; 601 | }); 602 | 603 | if(empty($value)) { 604 | throw new TronException('Token id not found'); 605 | } 606 | 607 | $first = array_shift($value); 608 | return ($fromTron == true ? $this->fromTron($first['value']) : $first['value']); 609 | } 610 | 611 | return 0; 612 | } 613 | 614 | /** 615 | * Query bandwidth information. 616 | * 617 | * @param $address 618 | * @return array 619 | * @throws TronException 620 | */ 621 | public function getBandwidth(string $address = null) 622 | { 623 | $address = (!is_null($address) ? $this->toHex($address) : $this->address['hex']); 624 | return $this->manager->request('wallet/getaccountnet', [ 625 | 'address' => $address 626 | ]); 627 | } 628 | 629 | /** 630 | * Getting data in the "from","to" directions 631 | * 632 | * @param string $address 633 | * @param string $direction 634 | * @param int $limit 635 | * @param int $offset 636 | * @return array 637 | * @throws TronException 638 | */ 639 | public function getTransactionsRelated(string $address, string $direction = 'to', int $limit = 30, int $offset = 0) 640 | { 641 | if(!in_array($direction, ['to', 'from'])) { 642 | throw new TronException('Invalid direction provided: Expected "to", "from"'); 643 | } 644 | 645 | if(!is_integer($limit) || $limit < 0 || ($offset && $limit < 1)) { 646 | throw new TronException('Invalid limit provided'); 647 | } 648 | 649 | if(!is_integer($offset) || $offset < 0) { 650 | throw new TronException('Invalid offset provided'); 651 | } 652 | 653 | $response = $this->manager->request(sprintf('walletextension/gettransactions%sthis', $direction), [ 654 | 'account' => ['address' => $this->toHex($address)], 655 | 'limit' => $limit, 656 | 'offset' => $offset 657 | ]); 658 | 659 | return array_merge($response, ['direction' => $direction]); 660 | } 661 | 662 | /** 663 | * Count all transactions on the network 664 | * 665 | * @return integer 666 | * @throws TronException 667 | */ 668 | public function getTransactionCount(): int 669 | { 670 | $response = $this->manager->request('wallet/totaltransaction'); 671 | return $response['num']; 672 | } 673 | 674 | /** 675 | * Send transaction to Blockchain 676 | * 677 | * @param string $to 678 | * @param float $amount 679 | * @param string|null $message 680 | * @param string|null $from 681 | * 682 | * @return array 683 | * @throws TronException 684 | */ 685 | public function sendTransaction(string $to, float $amount, string $from = null, string $message = null): array 686 | { 687 | if (is_null($from)) { 688 | $from = $this->address['hex']; 689 | } 690 | 691 | $transaction = $this->transactionBuilder->sendTrx($to, $amount, $from, $message); 692 | $signedTransaction = $this->signTransaction($transaction); 693 | 694 | 695 | $response = $this->sendRawTransaction($signedTransaction); 696 | return array_merge($response, $signedTransaction); 697 | } 698 | 699 | /** 700 | * Send token transaction to Blockchain 701 | * 702 | * @param string $to 703 | * @param float $amount 704 | * @param int $tokenID 705 | * @param string $from 706 | * 707 | * @return array 708 | * @throws TronException 709 | */ 710 | public function sendTokenTransaction(string $to, float $amount, int $tokenID = null, string $from = null): array 711 | { 712 | if (is_null($from)) { 713 | $from = $this->address['hex']; 714 | } 715 | 716 | $transaction = $this->transactionBuilder->sendToken($to, $this->toTron($amount), (string)$tokenID, $from); 717 | $signedTransaction = $this->signTransaction($transaction); 718 | 719 | $response = $this->sendRawTransaction($signedTransaction); 720 | 721 | return array_merge($response, $signedTransaction); 722 | } 723 | 724 | /** 725 | * Sign the transaction, the api has the risk of leaking the private key, 726 | * please make sure to call the api in a secure environment 727 | * 728 | * @param $transaction 729 | * @param string|null $message 730 | * @return array 731 | * @throws TronException 732 | */ 733 | public function signTransaction($transaction, string $message = null): array 734 | { 735 | if(!$this->privateKey) { 736 | throw new TronException('Missing private key'); 737 | } 738 | 739 | if(!is_array($transaction)) { 740 | throw new TronException('Invalid transaction provided'); 741 | } 742 | 743 | if(isset($transaction['Error'])) 744 | throw new TronException($transaction['Error']); 745 | 746 | 747 | if(isset($transaction['signature'])) { 748 | throw new TronException('Transaction is already signed'); 749 | } 750 | 751 | if(!is_null($message)) { 752 | $transaction['raw_data']['data'] = $this->stringUtf8toHex($message); 753 | } 754 | 755 | 756 | $signature = Support\Secp::sign($transaction['txID'], $this->privateKey); 757 | $transaction['signature'] = [$signature]; 758 | 759 | return $transaction; 760 | } 761 | 762 | /** 763 | * Broadcast the signed transaction 764 | * 765 | * @param $signedTransaction 766 | * @return array 767 | * @throws TronException 768 | */ 769 | public function sendRawTransaction($signedTransaction): array 770 | { 771 | if(!is_array($signedTransaction)) { 772 | throw new TronException('Invalid transaction provided'); 773 | } 774 | 775 | if(!array_key_exists('signature', $signedTransaction) || !is_array($signedTransaction['signature'])) { 776 | throw new TronException('Transaction is not signed'); 777 | } 778 | 779 | return $this->manager->request('wallet/broadcasttransaction', 780 | $signedTransaction); 781 | } 782 | 783 | /** 784 | * Modify account name 785 | * Note: Username is allowed to edit only once. 786 | * 787 | * @param $address 788 | * @param $account_name 789 | * @return array 790 | * @throws TronException 791 | */ 792 | public function changeAccountName(string $address = null, string $account_name) 793 | { 794 | $address = (!is_null($address) ? $address : $this->address['hex']); 795 | 796 | $transaction = $this->manager->request('wallet/updateaccount', [ 797 | 'account_name' => $this->stringUtf8toHex($account_name), 798 | 'owner_address' => $this->toHex($address) 799 | ]); 800 | 801 | $signedTransaction = $this->signTransaction($transaction); 802 | $response = $this->sendRawTransaction($signedTransaction); 803 | 804 | return $response; 805 | } 806 | 807 | /** 808 | * Send funds to the Tron account (option 2) 809 | * 810 | * @param array $args 811 | * @return array 812 | * @throws TronException 813 | */ 814 | public function send(...$args): array { 815 | return $this->sendTransaction(...$args); 816 | } 817 | 818 | /** 819 | * Send funds to the Tron account (option 3) 820 | * 821 | * @param array $args 822 | * @return array 823 | * @throws TronException 824 | */ 825 | public function sendTrx(...$args): array { 826 | return $this->sendTransaction(...$args); 827 | } 828 | 829 | /** 830 | * Creating a new token based on Tron 831 | * 832 | * @param array token { 833 | * "owner_address": "41e552f6487585c2b58bc2c9bb4492bc1f17132cd0", 834 | * "name": "0x6173736574497373756531353330383934333132313538", 835 | * "abbr": "0x6162627231353330383934333132313538", 836 | * "total_supply": 4321, 837 | * "trx_num": 1, 838 | * "num": 1, 839 | * "start_time": 1530894315158, 840 | * "end_time": 1533894312158, 841 | * "description": "007570646174654e616d6531353330363038383733343633", 842 | * "url": "007570646174654e616d6531353330363038383733343633", 843 | * "free_asset_net_limit": 10000, 844 | * "public_free_asset_net_limit": 10000, 845 | * "frozen_supply": { "frozen_amount": 1, "frozen_days": 2 } 846 | * 847 | * @return array 848 | * @throws TronException 849 | */ 850 | public function createToken($token = []) 851 | { 852 | return $this->manager->request('wallet/createassetissue', [ 853 | 'owner_address' => $this->toHex($token['owner_address']), 854 | 'name' => $this->stringUtf8toHex($token['name']), 855 | 'abbr' => $this->stringUtf8toHex($token['abbr']), 856 | 'description' => $this->stringUtf8toHex($token['description']), 857 | 'url' => $this->stringUtf8toHex($token['url']), 858 | 'total_supply' => $token['total_supply'], 859 | 'trx_num' => $token['trx_num'], 860 | 'num' => $token['num'], 861 | 'start_time' => $token['start_time'], 862 | 'end_time' => $token['end_time'], 863 | 'free_asset_net_limit' => $token['free_asset_net_limit'], 864 | 'public_free_asset_net_limit' => $token['public_free_asset_net_limit'], 865 | 'frozen_supply' => $token['frozen_supply'] 866 | ]); 867 | } 868 | 869 | /** 870 | * Create an account. 871 | * Uses an already activated account to create a new account 872 | * 873 | * @param $address 874 | * @param $newAccountAddress 875 | * @return array 876 | * @throws TronException 877 | */ 878 | public function registerAccount(string $address, string $newAccountAddress): array 879 | { 880 | return $this->manager->request('wallet/createaccount', [ 881 | 'owner_address' => $this->toHex($address), 882 | 'account_address' => $this->toHex($newAccountAddress) 883 | ]); 884 | } 885 | 886 | /** 887 | * Apply to become a super representative 888 | * 889 | * @param $address 890 | * @param $url 891 | * @return array 892 | * @throws TronException 893 | */ 894 | public function applyForSuperRepresentative(string $address, string $url) 895 | { 896 | return $this->manager->request('wallet/createwitness', [ 897 | 'owner_address' => $this->toHex($address), 898 | 'url' => $this->stringUtf8toHex($url) 899 | ]); 900 | } 901 | 902 | /** 903 | * Transfer Token 904 | * 905 | * @param $to 906 | * @param $amount 907 | * @param $tokenID 908 | * @param $from 909 | * @return array 910 | * @throws TronException 911 | */ 912 | public function sendToken(string $to, int $amount, string $tokenID, string $from = null) 913 | { 914 | if($from == null) { 915 | $from = $this->address['hex']; 916 | } 917 | 918 | $transfer = $this->transactionBuilder->sendToken($to, $amount, $tokenID, $from); 919 | $signedTransaction = $this->signTransaction($transfer); 920 | $response = $this->sendRawTransaction($signedTransaction); 921 | 922 | return array_merge($response, $signedTransaction); 923 | } 924 | 925 | /** 926 | * Purchase a Token 927 | * @param $issuerAddress 928 | * @param $tokenID 929 | * @param $amount 930 | * @param null $buyer 931 | * @return array 932 | * @throws TronException 933 | */ 934 | public function purchaseToken($issuerAddress, $tokenID, $amount, $buyer = null) 935 | { 936 | if($buyer == null) { 937 | $buyer = $this->address['hex']; 938 | } 939 | 940 | $purchase = $this->transactionBuilder->purchaseToken($issuerAddress, $tokenID, $amount, $buyer); 941 | $signedTransaction = $this->signTransaction($purchase); 942 | $response = $this->sendRawTransaction($signedTransaction); 943 | 944 | return array_merge($response, $signedTransaction); 945 | } 946 | 947 | /** 948 | * Freezes an amount of TRX. 949 | * Will give bandwidth OR Energy and TRON Power(voting rights) to the owner of the frozen tokens. 950 | * 951 | * @param float $amount 952 | * @param int $duration 953 | * @param string $resource 954 | * @param string $owner_address 955 | * @return array 956 | * @throws TronException 957 | */ 958 | public function freezeBalance(float $amount = 0, int $duration = 3, string $resource = 'BANDWIDTH', string $owner_address = null) 959 | { 960 | if($owner_address == null) { 961 | $owner_address = $this->address['hex']; 962 | } 963 | 964 | $freeze = $this->transactionBuilder->freezeBalance($amount, $duration, $resource, $owner_address); 965 | $signedTransaction = $this->signTransaction($freeze); 966 | $response = $this->sendRawTransaction($signedTransaction); 967 | 968 | return array_merge($response, $signedTransaction); 969 | } 970 | 971 | /** 972 | * Unfreeze TRX that has passed the minimum freeze duration. 973 | * Unfreezing will remove bandwidth and TRON Power. 974 | * 975 | * @param string $resource 976 | * @param string $owner_address 977 | * @return array 978 | * @throws TronException 979 | */ 980 | public function unfreezeBalance(string $resource = 'BANDWIDTH', string $owner_address = null) 981 | { 982 | if($owner_address == null) { 983 | $owner_address = $this->address['hex']; 984 | } 985 | 986 | $unfreeze = $this->transactionBuilder->unfreezeBalance($resource, $owner_address); 987 | $signedTransaction = $this->signTransaction($unfreeze); 988 | $response = $this->sendRawTransaction($signedTransaction); 989 | 990 | return array_merge($response, $signedTransaction); 991 | } 992 | 993 | /** 994 | * Withdraw Super Representative rewards, useable every 24 hours. 995 | * 996 | * @param string $owner_address 997 | * @return array 998 | * @throws TronException 999 | */ 1000 | public function withdrawBlockRewards(string $owner_address = null) 1001 | { 1002 | if($owner_address == null) { 1003 | $owner_address = $this->address['hex']; 1004 | } 1005 | 1006 | $withdraw = $this->transactionBuilder->withdrawBlockRewards($owner_address); 1007 | $signedTransaction = $this->signTransaction($withdraw); 1008 | $response = $this->sendRawTransaction($signedTransaction); 1009 | 1010 | return array_merge($response, $signedTransaction); 1011 | } 1012 | 1013 | /** 1014 | * Update a Token's information 1015 | * 1016 | * @param string $description 1017 | * @param string $url 1018 | * @param int $freeBandwidth 1019 | * @param int $freeBandwidthLimit 1020 | * @param $owner_address 1021 | * @return array 1022 | * @throws TronException 1023 | */ 1024 | public function updateToken(string $description, 1025 | string $url, 1026 | int $freeBandwidth = 0, 1027 | int $freeBandwidthLimit = 0, 1028 | string $owner_address = null) 1029 | { 1030 | if($owner_address == null) { 1031 | $owner_address = $this->address['hex']; 1032 | } 1033 | 1034 | $withdraw = $this->transactionBuilder->updateToken($description, $url, $freeBandwidth, $freeBandwidthLimit, $owner_address); 1035 | $signedTransaction = $this->signTransaction($withdraw); 1036 | $response = $this->sendRawTransaction($signedTransaction); 1037 | 1038 | return array_merge($response, $signedTransaction); 1039 | } 1040 | 1041 | /** 1042 | * Node list 1043 | * 1044 | * @return array 1045 | * @throws TronException 1046 | */ 1047 | public function listNodes(): array 1048 | { 1049 | $nodes = $this->manager->request('wallet/listnodes'); 1050 | return array_map(function($item) { 1051 | $address = $item['address']; 1052 | return sprintf('%s:%s', $this->toUtf8($address['host']), $address['port']); 1053 | }, $nodes['nodes']); 1054 | } 1055 | 1056 | 1057 | /** 1058 | * List the tokens issued by an account. 1059 | * 1060 | * @param string $address 1061 | * @return array 1062 | * @throws TronException 1063 | */ 1064 | public function getTokensIssuedByAddress(string $address = null) 1065 | { 1066 | $address = (!is_null($address) ? $this->toHex($address) : $this->address['hex']); 1067 | return $this->manager->request('wallet/getassetissuebyaccount',[ 1068 | 'address' => $address 1069 | ]); 1070 | } 1071 | 1072 | /** 1073 | * Query token by name. 1074 | * 1075 | * @param $tokenID 1076 | * @return array 1077 | * @throws TronException 1078 | */ 1079 | public function getTokenFromID($tokenID = null) 1080 | { 1081 | return $this->manager->request('wallet/getassetissuebyname', [ 1082 | 'value' => $this->stringUtf8toHex($tokenID) 1083 | ]); 1084 | } 1085 | 1086 | /** 1087 | * Query a range of blocks by block height 1088 | * 1089 | * @param int $start 1090 | * @param int $end 1091 | * @return array 1092 | * @throws TronException 1093 | */ 1094 | public function getBlockRange(int $start = 0, int $end = 30) 1095 | { 1096 | if(!is_integer($start) || $start < 0) { 1097 | throw new TronException('Invalid start of range provided'); 1098 | } 1099 | 1100 | if(!is_integer($end) || $end <= $start) { 1101 | throw new TronException('Invalid end of range provided'); 1102 | } 1103 | 1104 | return $this->manager->request('wallet/getblockbylimitnext', [ 1105 | 'startNum' => intval($start), 1106 | 'endNum' => intval($end) + 1 1107 | ])['block']; 1108 | } 1109 | 1110 | /** 1111 | * Query the latest blocks 1112 | * 1113 | * @param int $limit 1114 | * @return array 1115 | * @throws TronException 1116 | */ 1117 | public function getLatestBlocks(int $limit = 1): array 1118 | { 1119 | if(!is_integer($limit) || $limit <= 0) { 1120 | throw new TronException('Invalid limit provided'); 1121 | } 1122 | 1123 | return $this->manager->request('wallet/getblockbylatestnum', [ 1124 | 'num' => $limit 1125 | ])['block']; 1126 | } 1127 | 1128 | /** 1129 | * Query the list of Super Representatives 1130 | * 1131 | * @return array 1132 | * @throws TronException 1133 | */ 1134 | public function listSuperRepresentatives(): array 1135 | { 1136 | return $this->manager->request('wallet/listwitnesses')['witnesses']; 1137 | } 1138 | 1139 | /** 1140 | * Query the list of Tokens with pagination 1141 | * 1142 | * @param int $limit 1143 | * @param int $offset 1144 | * @return array 1145 | * @throws TronException 1146 | */ 1147 | public function listTokens(int $limit = 0, int $offset = 0) 1148 | { 1149 | if(!is_integer($limit) || $limit < 0 || ($offset && $limit < 1)) { 1150 | throw new TronException('Invalid limit provided'); 1151 | } 1152 | 1153 | if(!is_integer($offset) || $offset < 0) { 1154 | throw new TronException('Invalid offset provided'); 1155 | } 1156 | 1157 | if(!$limit) { 1158 | return $this->manager->request('wallet/getassetissuelist')['assetIssue']; 1159 | } 1160 | 1161 | return $this->manager->request('wallet/getpaginatedassetissuelist', [ 1162 | 'offset' => intval($offset), 1163 | 'limit' => intval($limit) 1164 | ])['assetIssue']; 1165 | } 1166 | 1167 | /** 1168 | * Get the time of the next Super Representative vote 1169 | * 1170 | * @return float 1171 | * @throws TronException 1172 | */ 1173 | public function timeUntilNextVoteCycle(): float 1174 | { 1175 | $num = $this->manager->request('wallet/getnextmaintenancetime')['num']; 1176 | 1177 | if($num == -1) { 1178 | throw new TronException('Failed to get time until next vote cycle'); 1179 | } 1180 | 1181 | return floor($num / 1000); 1182 | } 1183 | 1184 | /** 1185 | * Validate address 1186 | * 1187 | * @param string $address 1188 | * @param bool $hex 1189 | * @return array 1190 | * @throws TronException 1191 | */ 1192 | public function validateAddress(string $address = null, bool $hex = false): array 1193 | { 1194 | $address = (!is_null($address) ? $address : $this->address['hex']); 1195 | if($hex) { 1196 | $address = $this->toHex($address); 1197 | } 1198 | return $this->manager->request('wallet/validateaddress', [ 1199 | 'address' => $address 1200 | ]); 1201 | } 1202 | 1203 | /** 1204 | * Validate Tron Address (Locale) 1205 | * 1206 | * @param string|null $address 1207 | * @return bool 1208 | */ 1209 | public function isAddress(string $address = null): bool 1210 | { 1211 | if(strlen($address) !== self::ADDRESS_SIZE) 1212 | return false; 1213 | 1214 | $address = Base58Check::decode($address, 0, 0, false); 1215 | $utf8 = hex2bin($address); 1216 | 1217 | if(strlen($utf8) !== 25) return false; 1218 | if(strpos($utf8 , chr(self::ADDRESS_PREFIX_BYTE)) !== 0) return false; 1219 | 1220 | $checkSum = substr($utf8, 21); 1221 | $address = substr($utf8, 0, 21); 1222 | 1223 | $hash0 = Hash::SHA256($address); 1224 | $hash1 = Hash::SHA256($hash0); 1225 | $checkSum1 = substr($hash1, 0, 4); 1226 | 1227 | if ($checkSum === $checkSum1) 1228 | return true; 1229 | return false; 1230 | } 1231 | 1232 | /** 1233 | * Deploys a contract 1234 | * 1235 | * @param $abi 1236 | * @param $bytecode 1237 | * @param $feeLimit 1238 | * @param $address 1239 | * @param int $callValue 1240 | * @param int $bandwidthLimit 1241 | * @return array 1242 | * @throws TronException 1243 | */ 1244 | public function deployContract($abi, $bytecode, $feeLimit, $address, $callValue = 0, $bandwidthLimit = 0) 1245 | { 1246 | $payable = array_filter(json_decode($abi, true), function($v) 1247 | { 1248 | if($v['type'] == 'constructor' && $v['payable']) { 1249 | return $v['payable']; 1250 | } 1251 | return null; 1252 | }); 1253 | 1254 | if($feeLimit > 1000000000) { 1255 | throw new TronException('fee_limit must not be greater than 1000000000'); 1256 | } 1257 | 1258 | if($payable && $callValue == 0) { 1259 | throw new TronException('call_value must be greater than 0 if contract is type payable'); 1260 | } 1261 | 1262 | if(!$payable && $callValue > 0) { 1263 | throw new TronException('call_value can only equal to 0 if contract type isn‘t payable'); 1264 | } 1265 | 1266 | return $this->manager->request('wallet/deploycontract', [ 1267 | 'owner_address' => $this->toHex($address), 1268 | 'fee_limit' => $feeLimit, 1269 | 'call_value' => $callValue, 1270 | 'consume_user_resource_percent' => $bandwidthLimit, 1271 | 'abi' => $abi, 1272 | 'bytecode' => $bytecode 1273 | ]); 1274 | } 1275 | 1276 | /** 1277 | * Get a list of exchanges 1278 | * 1279 | * @return array 1280 | * @throws TronException 1281 | */ 1282 | public function listExchanges() 1283 | { 1284 | return $this->manager->request('/wallet/listexchanges', []); 1285 | } 1286 | 1287 | /** 1288 | * Query the resource information of the account 1289 | * 1290 | * @param string $address 1291 | * @return array 1292 | * @throws TronException 1293 | */ 1294 | public function getAccountResources(string $address = null) 1295 | { 1296 | $address = (!is_null($address) ? $address : $this->address['hex']); 1297 | 1298 | return $this->manager->request('/wallet/getaccountresource', [ 1299 | 'address' => $this->toHex($address) 1300 | ]); 1301 | } 1302 | 1303 | /** 1304 | * Create a new account 1305 | * 1306 | * @return TronAddress 1307 | * @throws TronException 1308 | */ 1309 | public function createAccount(): TronAddress 1310 | { 1311 | return $this->generateAddress(); 1312 | } 1313 | 1314 | public function getAddressHex(string $pubKeyBin): string 1315 | { 1316 | if (strlen($pubKeyBin) == 65) { 1317 | $pubKeyBin = substr($pubKeyBin, 1); 1318 | } 1319 | 1320 | $hash = Keccak::hash($pubKeyBin, 256); 1321 | 1322 | return self::ADDRESS_PREFIX . substr($hash, 24); 1323 | } 1324 | 1325 | public function getBase58CheckAddress(string $addressBin): string 1326 | { 1327 | $hash0 = Hash::SHA256($addressBin); 1328 | $hash1 = Hash::SHA256($hash0); 1329 | $checksum = substr($hash1, 0, 4); 1330 | $checksum = $addressBin . $checksum; 1331 | 1332 | return Base58::encode(Crypto::bin2bc($checksum)); 1333 | } 1334 | 1335 | /** 1336 | * Generate new address 1337 | * 1338 | * @return TronAddress 1339 | * @throws TronException 1340 | */ 1341 | public function generateAddress(): TronAddress 1342 | { 1343 | $ec = new EC('secp256k1'); 1344 | 1345 | // Generate keys 1346 | $key = $ec->genKeyPair(); 1347 | $priv = $ec->keyFromPrivate($key->priv); 1348 | $pubKeyHex = $priv->getPublic(false, "hex"); 1349 | 1350 | $pubKeyBin = hex2bin($pubKeyHex); 1351 | $addressHex = $this->getAddressHex($pubKeyBin); 1352 | $addressBin = hex2bin($addressHex); 1353 | $addressBase58 = $this->getBase58CheckAddress($addressBin); 1354 | 1355 | return new TronAddress([ 1356 | 'private_key' => $priv->getPrivate('hex'), 1357 | 'public_key' => $pubKeyHex, 1358 | 'address_hex' => $addressHex, 1359 | 'address_base58' => $addressBase58 1360 | ]); 1361 | } 1362 | 1363 | /** 1364 | * Helper function that will convert HEX to UTF8 1365 | * 1366 | * @param $str 1367 | * @return string 1368 | */ 1369 | public function toUtf8($str): string { 1370 | return pack('H*', $str); 1371 | } 1372 | 1373 | /** 1374 | * Query token by id. 1375 | * 1376 | * @param string $token_id 1377 | * @return array 1378 | * @throws TronException 1379 | */ 1380 | public function getTokenByID(string $token_id): array 1381 | { 1382 | if(!is_string($token_id)) 1383 | throw new TronException('Invalid token ID provided'); 1384 | 1385 | return $this->manager->request('/wallet/getassetissuebyid', [ 1386 | 'value' => $token_id 1387 | ]); 1388 | } 1389 | } 1390 | -------------------------------------------------------------------------------- /src/TronAddress.php: -------------------------------------------------------------------------------- 1 | response = $data; 23 | 24 | // Проверяем ключи, перед выводом результатов 25 | if(!$this->array_keys_exist($this->response, ['address_hex', 'private_key', 'public_key'])) { 26 | throw new TronException('Incorrectly generated address'); 27 | } 28 | } 29 | 30 | /** 31 | * Получение адреса 32 | * 33 | * @param bool $is_base58 34 | * @return string 35 | */ 36 | public function getAddress(bool $is_base58 = false): string 37 | { 38 | return $this->response[($is_base58 == false) ? 'address_hex' : 'address_base58']; 39 | } 40 | 41 | /** 42 | * Получение публичного ключа 43 | * 44 | * @return string 45 | */ 46 | public function getPublicKey(): string 47 | { 48 | return $this->response['public_key']; 49 | } 50 | 51 | /** 52 | * Получение приватного ключа 53 | * 54 | * @return string 55 | */ 56 | public function getPrivateKey(): string 57 | { 58 | return $this->response['private_key']; 59 | } 60 | 61 | /** 62 | * Получение результатов в массике 63 | * 64 | * @return array 65 | */ 66 | public function getRawData(): array 67 | { 68 | return $this->response; 69 | } 70 | 71 | /** 72 | * Проверка нескольких ключей 73 | * 74 | * @param array $array 75 | * @param array $keys 76 | * @return bool 77 | */ 78 | private function array_keys_exist(array $array, array $keys = []): bool 79 | { 80 | $count = 0; 81 | if (!is_array($keys)) { 82 | $keys = func_get_args(); 83 | array_shift($keys); 84 | } 85 | foreach ($keys as $key) { 86 | if (isset( $array[$key]) || array_key_exists($key, $array)) { 87 | $count ++; 88 | } 89 | } 90 | 91 | return count($keys) === $count; 92 | } 93 | } -------------------------------------------------------------------------------- /src/TronAwareTrait.php: -------------------------------------------------------------------------------- 1 | hexString2Address($string); 18 | } 19 | 20 | return $this->hexString2Utf8($string); 21 | } 22 | 23 | /** 24 | * Convert to Hex 25 | * 26 | * @param $str 27 | * @return string 28 | */ 29 | public function toHex($str) 30 | { 31 | if(mb_strlen($str) == 34 && mb_substr($str, 0, 1) === 'T') { 32 | return $this->address2HexString($str); 33 | }; 34 | 35 | return $this->stringUtf8toHex($str); 36 | } 37 | 38 | /** 39 | * Check the address before converting to Hex 40 | * 41 | * @param $sHexAddress 42 | * @return string 43 | */ 44 | public function address2HexString($sHexAddress) 45 | { 46 | if(strlen($sHexAddress) == 42 && mb_strpos($sHexAddress, '41') == 0) { 47 | return $sHexAddress; 48 | } 49 | return Base58Check::decode($sHexAddress,0,3); 50 | } 51 | 52 | /** 53 | * Check Hex address before converting to Base58 54 | * 55 | * @param $sHexString 56 | * @return string 57 | */ 58 | public function hexString2Address($sHexString) 59 | { 60 | if(!ctype_xdigit($sHexString)) { 61 | return $sHexString; 62 | } 63 | 64 | if(strlen($sHexString) < 2 || (strlen($sHexString) & 1) != 0) { 65 | return ''; 66 | } 67 | 68 | return Base58Check::encode($sHexString,0,false); 69 | } 70 | 71 | /** 72 | * Convert string to hex 73 | * 74 | * @param $sUtf8 75 | * @return string 76 | */ 77 | public function stringUtf8toHex($sUtf8) 78 | { 79 | return bin2hex($sUtf8); 80 | } 81 | 82 | /** 83 | * Convert hex to string 84 | * 85 | * @param $sHexString 86 | * @return string 87 | */ 88 | public function hexString2Utf8($sHexString) 89 | { 90 | return hex2bin($sHexString); 91 | } 92 | 93 | /** 94 | * Convert to great value 95 | * 96 | * @param $str 97 | * @return BigInteger 98 | */ 99 | public function toBigNumber($str) { 100 | return new BigInteger($str); 101 | } 102 | 103 | /** 104 | * Convert trx to float 105 | * 106 | * @param $amount 107 | * @return float 108 | */ 109 | public function fromTron($amount): float { 110 | return (float) bcdiv((string)$amount, (string)1e6, 8); 111 | } 112 | 113 | /** 114 | * Convert float to trx format 115 | * 116 | * @param $double 117 | * @return int 118 | */ 119 | public function toTron($double): int { 120 | return (int) bcmul((string)$double, (string)1e6,0); 121 | } 122 | 123 | /** 124 | * Convert to SHA3 125 | * 126 | * @param $string 127 | * @param bool $prefix 128 | * @return string 129 | * @throws \Exception 130 | */ 131 | public function sha3($string, $prefix = true) 132 | { 133 | return ($prefix ? '0x' : ''). Keccak::hash($string, 256); 134 | } 135 | } -------------------------------------------------------------------------------- /src/TronInterface.php: -------------------------------------------------------------------------------- 1 | 'https://api.trongrid.io', 17 | 'solidityNode' => 'https://api.trongrid.io', 18 | 'eventServer' => 'https://api.trongrid.io', 19 | 'explorer' => 'https://apilist.tronscan.org', 20 | 'signServer' => '' 21 | ]; 22 | 23 | /** 24 | * Providers 25 | * 26 | * @var array 27 | */ 28 | protected array $providers = [ 29 | 'fullNode' => [], 30 | 'solidityNode' => [], 31 | 'eventServer' => [], 32 | 'explorer' => [], 33 | 'signServer' => [] 34 | ]; 35 | 36 | /** 37 | * Status Page 38 | * 39 | * @var array 40 | */ 41 | protected array $statusPage = [ 42 | 'fullNode' => 'wallet/getnowblock', 43 | 'solidityNode' => 'walletsolidity/getnowblock', 44 | 'eventServer' => 'healthcheck', 45 | 'explorer' => 'api/system/status' 46 | ]; 47 | 48 | /** 49 | * @param $tron 50 | * @param $providers 51 | * @throws Exception\TronException 52 | */ 53 | public function __construct($tron, $providers) 54 | { 55 | $this->providers = $providers; 56 | 57 | foreach ($providers as $key => $value) 58 | { 59 | //Do not skip the supplier is empty 60 | if ($value == null) { 61 | $this->providers[$key] = new HttpProvider( 62 | $this->defaultNodes[$key] 63 | ); 64 | }; 65 | 66 | if(is_string($providers[$key])) 67 | $this->providers[$key] = new HttpProvider($value); 68 | 69 | if(in_array($key, ['signServer'])) 70 | continue; 71 | 72 | $this->providers[$key]->setStatusPage($this->statusPage[$key]); 73 | } 74 | } 75 | 76 | /** 77 | * List of providers 78 | * 79 | * @return array 80 | */ 81 | public function getProviders() { 82 | return $this->providers; 83 | } 84 | 85 | /** 86 | * Full Node 87 | * 88 | * @throws TronException 89 | * @return HttpProviderInterface 90 | */ 91 | public function fullNode() : HttpProviderInterface 92 | { 93 | if (!array_key_exists('fullNode', $this->providers)) { 94 | throw new TronException('Full node is not activated.'); 95 | } 96 | 97 | return $this->providers['fullNode']; 98 | } 99 | 100 | /** 101 | * Solidity Node 102 | * 103 | * @throws TronException 104 | * @return HttpProviderInterface 105 | */ 106 | public function solidityNode() : HttpProviderInterface 107 | { 108 | if (!array_key_exists('solidityNode', $this->providers)) { 109 | throw new TronException('Solidity node is not activated.'); 110 | } 111 | 112 | return $this->providers['solidityNode']; 113 | } 114 | 115 | /** 116 | * Sign server 117 | * 118 | * @throws TronException 119 | * @return HttpProviderInterface 120 | */ 121 | public function signServer(): HttpProviderInterface 122 | { 123 | if (!array_key_exists('signServer', $this->providers)) { 124 | throw new TronException('Sign server is not activated.'); 125 | } 126 | 127 | return $this->providers['signServer']; 128 | } 129 | 130 | /** 131 | * TronScan server 132 | * 133 | * @throws TronException 134 | * @return HttpProviderInterface 135 | */ 136 | public function explorer(): HttpProviderInterface 137 | { 138 | if (!array_key_exists('explorer', $this->providers)) { 139 | throw new TronException('explorer is not activated.'); 140 | } 141 | 142 | return $this->providers['explorer']; 143 | } 144 | 145 | /** 146 | * Event server 147 | * 148 | * @throws TronException 149 | * @return HttpProviderInterface 150 | */ 151 | public function eventServer(): HttpProviderInterface 152 | { 153 | if (!array_key_exists('eventServer', $this->providers)) { 154 | throw new TronException('Event server is not activated.'); 155 | } 156 | 157 | return $this->providers['eventServer']; 158 | } 159 | 160 | /** 161 | * Basic query to nodes 162 | * 163 | * @param $url 164 | * @param array $params 165 | * @param string $method 166 | * @return array 167 | * @throws TronException 168 | */ 169 | public function request($url, array $params = [], string $method = 'post') 170 | { 171 | $split = explode('/', $url); 172 | if(in_array($split[0], ['walletsolidity', 'walletextension'])) { 173 | $response = $this->solidityNode()->request($url, $params, $method); 174 | } elseif(in_array($split[0], ['event'])) { 175 | $response = $this->eventServer()->request($url, $params, 'get'); 176 | } elseif (in_array($split[0], ['trx-sign'])) { 177 | $response = $this->signServer()->request($url, $params, 'post'); 178 | } elseif(in_array($split[0], ['api'])) { 179 | $response = $this->explorer()->request($url, $params, 'get'); 180 | }else { 181 | $response = $this->fullNode()->request($url, $params, $method); 182 | } 183 | 184 | return $response; 185 | } 186 | 187 | /** 188 | * Check connections 189 | * 190 | * @return array 191 | */ 192 | public function isConnected(): array 193 | { 194 | $array = []; 195 | foreach ($this->providers as $key => $value) { 196 | $array[] = [ 197 | $key => boolval($value->isConnected()) 198 | ]; 199 | } 200 | 201 | return $array; 202 | } 203 | } -------------------------------------------------------------------------------- /src/trc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "sender", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "recipient", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "amount", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": false, 96 | "inputs": [ 97 | { 98 | "name": "spender", 99 | "type": "address" 100 | }, 101 | { 102 | "name": "addedValue", 103 | "type": "uint256" 104 | } 105 | ], 106 | "name": "increaseAllowance", 107 | "outputs": [ 108 | { 109 | "name": "", 110 | "type": "bool" 111 | } 112 | ], 113 | "payable": false, 114 | "stateMutability": "nonpayable", 115 | "type": "function" 116 | }, 117 | { 118 | "constant": true, 119 | "inputs": [ 120 | { 121 | "name": "account", 122 | "type": "address" 123 | } 124 | ], 125 | "name": "balanceOf", 126 | "outputs": [ 127 | { 128 | "name": "", 129 | "type": "uint256" 130 | } 131 | ], 132 | "payable": false, 133 | "stateMutability": "view", 134 | "type": "function" 135 | }, 136 | { 137 | "constant": true, 138 | "inputs": [], 139 | "name": "symbol", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "string" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "view", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": false, 152 | "inputs": [ 153 | { 154 | "name": "spender", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "subtractedValue", 159 | "type": "uint256" 160 | } 161 | ], 162 | "name": "decreaseAllowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "bool" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "nonpayable", 171 | "type": "function" 172 | }, 173 | { 174 | "constant": false, 175 | "inputs": [ 176 | { 177 | "name": "recipient", 178 | "type": "address" 179 | }, 180 | { 181 | "name": "amount", 182 | "type": "uint256" 183 | } 184 | ], 185 | "name": "transfer", 186 | "outputs": [ 187 | { 188 | "name": "", 189 | "type": "bool" 190 | } 191 | ], 192 | "payable": false, 193 | "stateMutability": "nonpayable", 194 | "type": "function" 195 | }, 196 | { 197 | "constant": true, 198 | "inputs": [ 199 | { 200 | "name": "owner", 201 | "type": "address" 202 | }, 203 | { 204 | "name": "spender", 205 | "type": "address" 206 | } 207 | ], 208 | "name": "allowance", 209 | "outputs": [ 210 | { 211 | "name": "", 212 | "type": "uint256" 213 | } 214 | ], 215 | "payable": false, 216 | "stateMutability": "view", 217 | "type": "function" 218 | }, 219 | { 220 | "inputs": [], 221 | "payable": false, 222 | "stateMutability": "nonpayable", 223 | "type": "constructor" 224 | }, 225 | { 226 | "anonymous": false, 227 | "inputs": [ 228 | { 229 | "indexed": true, 230 | "name": "from", 231 | "type": "address" 232 | }, 233 | { 234 | "indexed": true, 235 | "name": "to", 236 | "type": "address" 237 | }, 238 | { 239 | "indexed": false, 240 | "name": "value", 241 | "type": "uint256" 242 | } 243 | ], 244 | "name": "Transfer", 245 | "type": "event" 246 | }, 247 | { 248 | "anonymous": false, 249 | "inputs": [ 250 | { 251 | "indexed": true, 252 | "name": "owner", 253 | "type": "address" 254 | }, 255 | { 256 | "indexed": true, 257 | "name": "spender", 258 | "type": "address" 259 | }, 260 | { 261 | "indexed": false, 262 | "name": "value", 263 | "type": "uint256" 264 | } 265 | ], 266 | "name": "Approval", 267 | "type": "event" 268 | } 269 | ] -------------------------------------------------------------------------------- /tests/TronTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($tron->isValidProvider($provider), true); 23 | } 24 | 25 | public function test_setAddress() 26 | { 27 | $tron = new Tron(new HttpProvider(self::FULL_NODE_API), new HttpProvider(self::SOLIDITY_NODE_API)); 28 | $tron->setAddress(self::ADDRESS_HEX); 29 | 30 | $this->assertEquals($tron->getAddress()['hex'],self::ADDRESS_HEX); 31 | $this->assertEquals($tron->getAddress()['base58'], self::ADDRESS_BASE58); 32 | } 33 | 34 | public function test_setDefaultBlock() 35 | { 36 | $tron = new Tron(new HttpProvider(self::FULL_NODE_API),new HttpProvider(self::SOLIDITY_NODE_API)); 37 | $tron->setDefaultBlock(1); 38 | $this->assertEquals($tron->getDefaultBlock(), 1); 39 | 40 | $tron->setDefaultBlock(-2); 41 | $this->assertEquals($tron->getDefaultBlock(),2); 42 | 43 | $tron->setDefaultBlock(0); 44 | $this->assertEquals($tron->getDefaultBlock(),0); 45 | 46 | $tron->setDefaultBlock(); 47 | $this->assertEquals($tron->getDefaultBlock(),false); 48 | 49 | $tron->setDefaultBlock('latest'); 50 | $this->assertEquals($tron->getDefaultBlock(),'latest'); 51 | } 52 | } --------------------------------------------------------------------------------