├── .gitignore ├── LICENSE ├── README-CN.md ├── README.md ├── composer.json ├── src ├── Address.php ├── Api.php ├── Block.php ├── Exceptions │ ├── TransactionException.php │ └── TronErrorException.php ├── Interfaces │ └── WalletInterface.php ├── Support │ ├── Formatter.php │ ├── Key.php │ └── Utils.php ├── TRC20.php ├── TRX.php └── Transaction.php └── tests ├── TRC20Test.php └── TRXTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | *.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fenguoz 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-CN.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 中文 2 | 3 |

TRON-PHP

4 | 5 |

6 | Stable Version 7 | Php Version 8 | tron-php License 9 | Total Downloads 10 |

11 | 12 | ## 概述 13 | 14 | TRON-PHP 目前支持波场的 TRX 和 TRC20 中常用生成地址,发起转账,离线签名等功能。 15 | 16 | ## 特点 17 | 18 | 1. 一套写法兼容 TRON 网络中 TRX 货币和 TRC 系列所有通证 19 | 1. 接口方法可可灵活增减 20 | 21 | ## 支持方法 22 | 23 | - 生成地址 `generateAddress()` 24 | - 验证地址 `validateAddress(Address $address)` 25 | - 根据私钥得到地址 `privateKeyToAddress(string $privateKeyHex)` 26 | - 查询余额 `balance(Address $address)` 27 | - 交易转账(离线签名) `transfer(Address $from, Address $to, float $amount)` 28 | - 查询最新区块 `blockNumber()` 29 | - 根据区块链查询信息 `blockByNumber(int $blockID)` 30 | - 根据交易哈希查询信息 `transactionReceipt(string $txHash)` 31 | 32 | ## 快速开始 33 | 34 | ### 安装 35 | 36 | PHP8 37 | ``` php 38 | composer require fenguoz/tron-php 39 | ``` 40 | 41 | or PHP7 42 | ``` php 43 | composer require fenguoz/tron-php ~1.3 44 | ``` 45 | 46 | ### 接口调用 47 | 48 | ``` php 49 | use GuzzleHttp\Client; 50 | 51 | $uri = 'https://api.trongrid.io';// mainnet 52 | // $uri = 'https://api.shasta.trongrid.io';// shasta testnet 53 | $api = new \Tron\Api(new Client(['base_uri' => $uri])); 54 | 55 | $trxWallet = new \Tron\TRX($api); 56 | $addressData = $trxWallet->generateAddress(); 57 | // $addressData->privateKey 58 | // $addressData->address 59 | 60 | $config = [ 61 | 'contract_address' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',// USDT TRC20 62 | 'decimals' => 6, 63 | ]; 64 | $trc20Wallet = new \Tron\TRC20($api, $config); 65 | $addressData = $trc20Wallet->generateAddress(); 66 | ``` 67 | 68 | ## 计划 69 | 70 | - 支持 TRC10 71 | - 智能合约 72 | 73 | ## 扩展包 74 | 75 | | 扩展包名 | 描述 | 应用场景 | 76 | | :-----| :---- | :---- | 77 | | [fenguoz/tron-api](https://github.com/fenguoz/tron-api) | 波场官方文档推荐 PHP 扩展包 | 波场基础Api | 78 | 79 | ## 🌟🌟 80 | 81 | [![Stargazers over time](https://starchart.cc/Fenguoz/tron-php.svg)](https://starchart.cc/Fenguoz/tron-php) 82 | 83 | ## 合作 84 | 85 | 联系方式 86 | - WX:zgf243944672 87 | - QQ:243944672 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | English | [中文](./README-CN.md) 2 | 3 |

TRON-PHP

4 | 5 |

6 | Stable Version 7 | Php Version 8 | tron-php License 9 | Total Downloads 10 |

11 | 12 | ## Introduction 13 | 14 | Support TRON's TRX and TRC20, which include functions such as address creation, balance query, transaction transfer, query the latest blockchain, query information based on the blockchain, and query information based on the transaction hash 15 | 16 | ## Advantage 17 | 18 | 1. One set of scripts is compatible with all TRX currencies and TRC20 certifications in the TRON network 19 | 1. Interface methods can be added or subtracted flexibly 20 | 21 | ## Support Method 22 | 23 | - Generate address `generateAddress()` 24 | - Verify address `validateAddress(Address $address)` 25 | - Get the address according to the private key `privateKeyToAddress(string $privateKeyHex)` 26 | - Check balances `balance(Address $address)` 27 | - Transaction transfer (offline signature) `transfer(string $from, string $to, float $amount)` 28 | - Query the latest block `blockNumber()` 29 | - Query information according to the blockchain `blockByNumber(int $blockID)` 30 | - *Query information based on transaction hash `transactionReceipt(string $txHash)` 31 | 32 | ## Quick Start 33 | 34 | ### Install 35 | 36 | PHP8 37 | ``` php 38 | composer require fenguoz/tron-php 39 | ``` 40 | 41 | or PHP7 42 | ``` php 43 | composer require fenguoz/tron-php ~1.3 44 | ``` 45 | 46 | ### Interface 47 | 48 | ``` php 49 | use GuzzleHttp\Client; 50 | 51 | $uri = 'https://api.trongrid.io';// mainnet 52 | // $uri = 'https://api.shasta.trongrid.io';// shasta testnet 53 | $api = new \Tron\Api(new Client(['base_uri' => $uri])); 54 | 55 | $trxWallet = new \Tron\TRX($api); 56 | $addressData = $trxWallet->generateAddress(); 57 | // $addressData->privateKey 58 | // $addressData->address 59 | 60 | $config = [ 61 | 'contract_address' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',// USDT TRC20 62 | 'decimals' => 6, 63 | ]; 64 | $trc20Wallet = new \Tron\TRC20($api, $config); 65 | $addressData = $trc20Wallet->generateAddress(); 66 | ``` 67 | 68 | ## Plan 69 | 70 | - Support TRC10 71 | - Smart Contract 72 | 73 | ## Package 74 | 75 | | Name | description | Scenes | 76 | | :-----| :---- | :---- | 77 | | [Fenguoz/tron-api](https://github.com/Fenguoz/tron-api) | TRON official document recommends PHP extension package | TRON basic API | 78 | 79 | ## 🌟🌟 80 | 81 | [![Stargazers over time](https://starchart.cc/Fenguoz/tron-php.svg)](https://starchart.cc/Fenguoz/tron-php) 82 | 83 | ## Cooperate 84 | 85 | Contact 86 | - WX:zgf243944672 87 | - QQ:243944672 88 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fenguoz/tron-php", 3 | "description": "Support TRON's TRX and TRC20, which include functions such as address creation, balance query, transaction transfer, query the latest blockchain, query information based on the blockchain, and query information based on the transaction hash", 4 | "keywords": [ 5 | "php", 6 | "tron", 7 | "trx", 8 | "trc20" 9 | ], 10 | "type": "library", 11 | "homepage": "https://github.com/Fenguoz/tron-php", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Fenguoz", 16 | "email": "243944672@qq.com" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=8.0", 21 | "fenguoz/tron-api": "~1.1", 22 | "ionux/phactor": "1.0.8", 23 | "kornrunner/keccak": "~1.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "~7.5 || ~9.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Tron\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Test\\": "tests/" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Address.php: -------------------------------------------------------------------------------- 1 | privateKey = $privateKey; 25 | $this->address = $address; 26 | $this->hexAddress = $hexAddress; 27 | } 28 | 29 | /** 30 | * Dont rely on this. Always use Wallet::validateAddress to double check 31 | * against tronGrid. 32 | * 33 | * @return bool 34 | */ 35 | public function isValid(): bool 36 | { 37 | if (strlen($this->address) !== Address::ADDRESS_SIZE) { 38 | return false; 39 | } 40 | 41 | $address = Base58Check::decode($this->address, false, 0, false); 42 | $utf8 = hex2bin($address); 43 | 44 | if (strlen($utf8) !== 25) { 45 | return false; 46 | } 47 | 48 | if (strpos($utf8, chr(self::ADDRESS_PREFIX_BYTE)) !== 0) { 49 | return false; 50 | } 51 | 52 | $checkSum = substr($utf8, 21); 53 | $address = substr($utf8, 0, 21); 54 | 55 | $hash0 = Hash::SHA256($address); 56 | $hash1 = Hash::SHA256($hash0); 57 | $checkSum1 = substr($hash1, 0, 4); 58 | 59 | if ($checkSum === $checkSum1) { 60 | return true; 61 | } 62 | 63 | return false; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Api.php: -------------------------------------------------------------------------------- 1 | _client = $client; 15 | } 16 | 17 | public function getClient(): Client 18 | { 19 | return $this->_client; 20 | } 21 | 22 | /** 23 | * Abstracts some common functionality like formatting the post data 24 | * along with error handling. 25 | * 26 | * @throws TronErrorException 27 | */ 28 | public function post(string $endpoint, array $data = [], bool $returnAssoc = false) 29 | { 30 | if (sizeof($data)) { 31 | $data = ['json' => $data]; 32 | } 33 | 34 | $stream = (string)$this->getClient()->post($endpoint, $data)->getBody(); 35 | $body = json_decode($stream, $returnAssoc); 36 | 37 | $this->checkForErrorResponse($returnAssoc, $body); 38 | 39 | return $body; 40 | } 41 | 42 | /** 43 | * Check if the response has an error and throw it. 44 | * 45 | * @param bool $returnAssoc 46 | * @param $body 47 | * @throws TronErrorException 48 | */ 49 | private function checkForErrorResponse(bool $returnAssoc, $body) 50 | { 51 | if ($returnAssoc) { 52 | if (isset($body['Error'])) { 53 | throw new TronErrorException($body['Error']); 54 | } elseif (isset($body['code']) && isset($body['message'])) { 55 | throw new TronErrorException($body['code'] . ': ' . hex2bin($body['message'])); 56 | } 57 | } 58 | 59 | if (isset($body->Error)) { 60 | throw new TronErrorException($body->Error); 61 | } elseif (isset($body->code) && isset($body->message)) { 62 | throw new TronErrorException($body->code . ': ' . hex2bin($body->message)); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Block.php: -------------------------------------------------------------------------------- 1 | blockID = $blockID; 18 | $this->block_header = $block_header; 19 | $this->transactions = $transactions; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Exceptions/TransactionException.php: -------------------------------------------------------------------------------- 1 | toHex(true); 50 | $padded = mb_substr($bnHex, 0, 1); 51 | 52 | if ($padded !== 'f') { 53 | $padded = '0'; 54 | } 55 | return implode('', array_fill(0, $digit - mb_strlen($bnHex), $padded)) . $bnHex; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Support/Key.php: -------------------------------------------------------------------------------- 1 | keyFromPrivate($privateKey, 'hex'); 68 | $publicKey = $privateKey->getPublic(false, 'hex'); 69 | 70 | return $publicKey; 71 | } 72 | 73 | public static function getBase58CheckAddress(string $addressHex): string 74 | { 75 | $addressBin = hex2bin($addressHex); 76 | $hash0 = Hash::SHA256($addressBin); 77 | $hash1 = Hash::SHA256($hash0); 78 | $checksum = substr($hash1, 0, 4); 79 | $checksum = $addressBin . $checksum; 80 | 81 | return Base58::encode(Crypto::bin2bc($checksum)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Support/Utils.php: -------------------------------------------------------------------------------- 1 | toHex(true); 48 | $hex = preg_replace('/^0+(?!$)/', '', $hex); 49 | } elseif (is_string($value)) { 50 | $value = self::stripZero($value); 51 | $hex = implode('', unpack('H*', $value)); 52 | } elseif ($value instanceof BigInteger) { 53 | $hex = $value->toHex(true); 54 | $hex = preg_replace('/^0+(?!$)/', '', $hex); 55 | } else { 56 | throw new InvalidArgumentException('The value to toHex function is not support.'); 57 | } 58 | if ($isPrefix) { 59 | return '0x' . $hex; 60 | } 61 | return $hex; 62 | } 63 | 64 | /** 65 | * hexToBin 66 | * 67 | * @param string 68 | * @return string 69 | */ 70 | public static function hexToBin($value) 71 | { 72 | if (!is_string($value)) { 73 | throw new InvalidArgumentException('The value to hexToBin function must be string.'); 74 | } 75 | if (self::isZeroPrefixed($value)) { 76 | $count = 1; 77 | $value = str_replace('0x', '', $value, $count); 78 | } 79 | return pack('H*', $value); 80 | } 81 | 82 | /** 83 | * isZeroPrefixed 84 | * 85 | * @param string 86 | * @return bool 87 | */ 88 | public static function isZeroPrefixed($value) 89 | { 90 | if (!is_string($value)) { 91 | throw new InvalidArgumentException('The value to isZeroPrefixed function must be string.'); 92 | } 93 | return (strpos($value, '0x') === 0); 94 | } 95 | 96 | /** 97 | * stripZero 98 | * 99 | * @param string $value 100 | * @return string 101 | */ 102 | public static function stripZero($value) 103 | { 104 | if (self::isZeroPrefixed($value)) { 105 | $count = 1; 106 | return str_replace('0x', '', $value, $count); 107 | } 108 | return $value; 109 | } 110 | 111 | /** 112 | * isNegative 113 | * 114 | * @param string 115 | * @return bool 116 | */ 117 | public static function isNegative($value) 118 | { 119 | if (!is_string($value)) { 120 | throw new InvalidArgumentException('The value to isNegative function must be string.'); 121 | } 122 | return (strpos($value, '-') === 0); 123 | } 124 | 125 | /** 126 | * isAddress 127 | * 128 | * @param string $value 129 | * @return bool 130 | */ 131 | public static function isAddress($value) 132 | { 133 | if (!is_string($value)) { 134 | throw new InvalidArgumentException('The value to isAddress function must be string.'); 135 | } 136 | if (preg_match('/^(0x|0X)?[a-f0-9A-F]{40}$/', $value) !== 1) { 137 | return false; 138 | } elseif (preg_match('/^(0x|0X)?[a-f0-9]{40}$/', $value) === 1 || preg_match('/^(0x|0X)?[A-F0-9]{40}$/', $value) === 1) { 139 | return true; 140 | } 141 | return self::isAddressChecksum($value); 142 | } 143 | 144 | /** 145 | * isAddressChecksum 146 | * 147 | * @param string $value 148 | * @return bool 149 | */ 150 | public static function isAddressChecksum($value) 151 | { 152 | if (!is_string($value)) { 153 | throw new InvalidArgumentException('The value to isAddressChecksum function must be string.'); 154 | } 155 | $value = self::stripZero($value); 156 | $hash = self::stripZero(self::sha3(mb_strtolower($value))); 157 | 158 | for ($i = 0; $i < 40; $i++) { 159 | if ( 160 | (intval($hash[$i], 16) > 7 && mb_strtoupper($value[$i]) !== $value[$i]) || 161 | (intval($hash[$i], 16) <= 7 && mb_strtolower($value[$i]) !== $value[$i]) 162 | ) { 163 | return false; 164 | } 165 | } 166 | return true; 167 | } 168 | 169 | /** 170 | * isHex 171 | * 172 | * @param string $value 173 | * @return bool 174 | */ 175 | public static function isHex($value) 176 | { 177 | return (is_string($value) && preg_match('/^(0x)?[a-f0-9A-F]*$/', $value) === 1); 178 | } 179 | 180 | /** 181 | * sha3 182 | * keccak256 183 | * 184 | * @param string $value 185 | * @return string 186 | */ 187 | public static function sha3($value) 188 | { 189 | if (!is_string($value)) { 190 | throw new InvalidArgumentException('The value to sha3 function must be string.'); 191 | } 192 | if (strpos($value, '0x') === 0) { 193 | $value = self::hexToBin($value); 194 | } 195 | $hash = Keccak::hash($value, 256); 196 | 197 | if ($hash === self::SHA3_NULL_HASH) { 198 | return null; 199 | } 200 | return $hash; 201 | } 202 | 203 | /** 204 | * toBn 205 | * Change number or number string to BigInteger. 206 | * 207 | * @param BigInteger|string|int $number 208 | * @return array|BigInteger 209 | */ 210 | public static function toBn($number) 211 | { 212 | if ($number instanceof BigInteger) { 213 | $bn = $number; 214 | } elseif (is_int($number)) { 215 | $bn = new BigInteger($number); 216 | } elseif (is_numeric($number)) { 217 | $number = (string) $number; 218 | 219 | if (self::isNegative($number)) { 220 | $count = 1; 221 | $number = str_replace('-', '', $number, $count); 222 | $negative1 = new BigInteger(-1); 223 | } 224 | if (strpos($number, '.') > 0) { 225 | $comps = explode('.', $number); 226 | 227 | if (count($comps) > 2) { 228 | throw new InvalidArgumentException('toBn number must be a valid number.'); 229 | } 230 | $whole = $comps[0]; 231 | $fraction = $comps[1]; 232 | 233 | return [ 234 | new BigInteger($whole), 235 | new BigInteger($fraction), 236 | strlen($comps[1]), 237 | isset($negative1) ? $negative1 : false 238 | ]; 239 | } else { 240 | $bn = new BigInteger($number); 241 | } 242 | if (isset($negative1)) { 243 | $bn = $bn->multiply($negative1); 244 | } 245 | } elseif (is_string($number)) { 246 | $number = mb_strtolower($number); 247 | 248 | if (self::isNegative($number)) { 249 | $count = 1; 250 | $number = str_replace('-', '', $number, $count); 251 | $negative1 = new BigInteger(-1); 252 | } 253 | if (self::isZeroPrefixed($number) || preg_match('/[a-f]+/', $number) === 1) { 254 | $number = self::stripZero($number); 255 | $bn = new BigInteger($number, 16); 256 | } elseif (empty($number)) { 257 | $bn = new BigInteger(0); 258 | } else { 259 | throw new InvalidArgumentException('toBn number must be valid hex string.'); 260 | } 261 | if (isset($negative1)) { 262 | $bn = $bn->multiply($negative1); 263 | } 264 | } else { 265 | throw new InvalidArgumentException('toBn number must be BigInteger, string or int.'); 266 | } 267 | return $bn; 268 | } 269 | 270 | /** 271 | * 根据精度展示资产 272 | * @param $number 273 | * @param int $decimals 274 | * @return string 275 | */ 276 | public static function toDisplayAmount($number, int $decimals) 277 | { 278 | $number = number_format($number,0,'.','');//格式化 279 | $bn = self::toBn($number); 280 | $bnt = self::toBn(pow(10, $decimals)); 281 | 282 | return self::divideDisplay($bn->divide($bnt), $decimals); 283 | } 284 | 285 | public static function divideDisplay(array $divResult, int $decimals) 286 | { 287 | list($bnq, $bnr) = $divResult; 288 | $ret = "$bnq->value"; 289 | if ($bnr->value > 0) { 290 | $ret .= '.' . rtrim(sprintf("%0{$decimals}d", $bnr->value), '0'); 291 | } 292 | 293 | return $ret; 294 | } 295 | 296 | public static function toMinUnitByDecimals($number, int $decimals) 297 | { 298 | $bn = self::toBn($number); 299 | $bnt = self::toBn(pow(10, $decimals)); 300 | 301 | if (is_array($bn)) { 302 | // fraction number 303 | list($whole, $fraction, $fractionLength, $negative1) = $bn; 304 | 305 | $whole = $whole->multiply($bnt); 306 | 307 | switch (MATH_BIGINTEGER_MODE) { 308 | case $whole::MODE_GMP: 309 | static $two; 310 | $powerBase = gmp_pow(gmp_init(10), (int) $fractionLength); 311 | break; 312 | case $whole::MODE_BCMATH: 313 | $powerBase = bcpow('10', (string) $fractionLength, 0); 314 | break; 315 | default: 316 | $powerBase = pow(10, (int) $fractionLength); 317 | break; 318 | } 319 | $base = new BigInteger($powerBase); 320 | $fraction = $fraction->multiply($bnt)->divide($base)[0]; 321 | 322 | if ($negative1 !== false) { 323 | return $whole->add($fraction)->multiply($negative1); 324 | } 325 | return $whole->add($fraction); 326 | } 327 | 328 | return $bn->multiply($bnt); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/TRC20.php: -------------------------------------------------------------------------------- 1 | contractAddress = new Address( 23 | $config['contract_address'], 24 | '', 25 | $this->tron->address2HexString($config['contract_address']) 26 | ); 27 | $this->decimals = $config['decimals']; 28 | } 29 | 30 | public function balance(Address $address) 31 | { 32 | $format = Formatter::toAddressFormat($address->hexAddress); 33 | $body = $this->_api->post('/wallet/triggersmartcontract', [ 34 | 'contract_address' => $this->contractAddress->hexAddress, 35 | 'function_selector' => 'balanceOf(address)', 36 | 'parameter' => $format, 37 | 'owner_address' => $address->hexAddress, 38 | ]); 39 | 40 | if (isset($body->result->code)) { 41 | throw new TronErrorException(hex2bin($body->result->message)); 42 | } 43 | 44 | try { 45 | $balance = Utils::toDisplayAmount(hexdec($body->constant_result[0]), $this->decimals); 46 | } catch (InvalidArgumentException $e) { 47 | throw new TronErrorException($e->getMessage()); 48 | } 49 | return $balance; 50 | } 51 | 52 | public function transfer(Address $from, Address $to, float $amount): Transaction 53 | { 54 | $this->tron->setAddress($from->address); 55 | $this->tron->setPrivateKey($from->privateKey); 56 | 57 | $toFormat = Formatter::toAddressFormat($to->hexAddress); 58 | try { 59 | $amount = Utils::toMinUnitByDecimals($amount, $this->decimals); 60 | } catch (InvalidArgumentException $e) { 61 | throw new TronErrorException($e->getMessage()); 62 | } 63 | $numberFormat = Formatter::toIntegerFormat($amount); 64 | 65 | $body = $this->_api->post('/wallet/triggersmartcontract', [ 66 | 'contract_address' => $this->contractAddress->hexAddress, 67 | 'function_selector' => 'transfer(address,uint256)', 68 | 'parameter' => "{$toFormat}{$numberFormat}", 69 | 'fee_limit' => 100000000, 70 | 'call_value' => 0, 71 | 'owner_address' => $from->hexAddress, 72 | ], true); 73 | 74 | if (isset($body['result']['code'])) { 75 | throw new TransactionException(hex2bin($body['result']['message'])); 76 | } 77 | 78 | try { 79 | $tradeobj = $this->tron->signTransaction($body['transaction']); 80 | $response = $this->tron->sendRawTransaction($tradeobj); 81 | } catch (TronException $e) { 82 | throw new TransactionException($e->getMessage(), $e->getCode()); 83 | } 84 | 85 | if (isset($response['result']) && $response['result'] == true) { 86 | return new Transaction( 87 | $body['transaction']['txID'], 88 | $body['transaction']['raw_data'], 89 | 'PACKING' 90 | ); 91 | } else { 92 | throw new TransactionException(hex2bin($response['result']['message'])); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/TRX.php: -------------------------------------------------------------------------------- 1 | _api = $_api; 24 | 25 | $host = $_api->getClient()->getConfig('base_uri')->getScheme() . '://' . $_api->getClient()->getConfig('base_uri')->getHost(); 26 | $fullNode = new HttpProvider($host); 27 | $solidityNode = new HttpProvider($host); 28 | $eventServer = new HttpProvider($host); 29 | try { 30 | $this->tron = new Tron($fullNode, $solidityNode, $eventServer); 31 | } catch (TronException $e) { 32 | throw new TronErrorException($e->getMessage(), $e->getCode()); 33 | } 34 | } 35 | 36 | public function generateAddress(): Address 37 | { 38 | $attempts = 0; 39 | $validAddress = false; 40 | 41 | do { 42 | if ($attempts++ === 5) { 43 | throw new TronErrorException('Could not generate valid key'); 44 | } 45 | 46 | $key = new Key([ 47 | 'private_key_hex' => '', 48 | 'private_key_dec' => '', 49 | 'public_key' => '', 50 | 'public_key_compressed' => '', 51 | 'public_key_x' => '', 52 | 'public_key_y' => '' 53 | ]); 54 | $keyPair = $key->GenerateKeypair(); 55 | $privateKeyHex = $keyPair['private_key_hex']; 56 | $pubKeyHex = $keyPair['public_key']; 57 | 58 | //We cant use hex2bin unless the string length is even. 59 | if (strlen($pubKeyHex) % 2 !== 0) { 60 | continue; 61 | } 62 | 63 | try { 64 | $addressHex = Address::ADDRESS_PREFIX . SupportKey::publicKeyToAddress($pubKeyHex); 65 | $addressBase58 = SupportKey::getBase58CheckAddress($addressHex); 66 | } catch (InvalidArgumentException $e) { 67 | throw new TronErrorException($e->getMessage()); 68 | } 69 | $address = new Address($addressBase58, $privateKeyHex, $addressHex); 70 | $validAddress = $this->validateAddress($address); 71 | } while (!$validAddress); 72 | 73 | return $address; 74 | } 75 | 76 | public function validateAddress(Address $address): bool 77 | { 78 | if (!$address->isValid()) { 79 | return false; 80 | } 81 | 82 | $body = $this->_api->post('/wallet/validateaddress', [ 83 | 'address' => $address->address, 84 | ]); 85 | 86 | return $body->result; 87 | } 88 | 89 | public function privateKeyToAddress(string $privateKeyHex): Address 90 | { 91 | try { 92 | $addressHex = Address::ADDRESS_PREFIX . SupportKey::privateKeyToAddress($privateKeyHex); 93 | $addressBase58 = SupportKey::getBase58CheckAddress($addressHex); 94 | } catch (InvalidArgumentException $e) { 95 | throw new TronErrorException($e->getMessage()); 96 | } 97 | $address = new Address($addressBase58, $privateKeyHex, $addressHex); 98 | $validAddress = $this->validateAddress($address); 99 | if (!$validAddress) { 100 | throw new TronErrorException('Invalid private key'); 101 | } 102 | 103 | return $address; 104 | } 105 | 106 | public function balance(Address $address) 107 | { 108 | $this->tron->setAddress($address->address); 109 | return $this->tron->getBalance(null, true); 110 | } 111 | 112 | public function transfer(Address $from, Address $to, float $amount): Transaction 113 | { 114 | $this->tron->setAddress($from->address); 115 | $this->tron->setPrivateKey($from->privateKey); 116 | 117 | try { 118 | $transaction = $this->tron->getTransactionBuilder()->sendTrx($to->address, $amount, $from->address); 119 | $signedTransaction = $this->tron->signTransaction($transaction); 120 | $response = $this->tron->sendRawTransaction($signedTransaction); 121 | } catch (TronException $e) { 122 | throw new TransactionException($e->getMessage(), $e->getCode()); 123 | } 124 | 125 | if (isset($response['result']) && $response['result'] == true) { 126 | return new Transaction( 127 | $transaction['txID'], 128 | $transaction['raw_data'], 129 | 'PACKING' 130 | ); 131 | } else { 132 | throw new TransactionException(hex2bin($response['message'])); 133 | } 134 | } 135 | 136 | public function blockNumber(): Block 137 | { 138 | try { 139 | $block = $this->tron->getCurrentBlock(); 140 | } catch (TronException $e) { 141 | throw new TransactionException($e->getMessage(), $e->getCode()); 142 | } 143 | $transactions = isset($block['transactions']) ? $block['transactions'] : []; 144 | return new Block($block['blockID'], $block['block_header'], $transactions); 145 | } 146 | 147 | public function blockByNumber(int $blockID): Block 148 | { 149 | try { 150 | $block = $this->tron->getBlockByNumber($blockID); 151 | } catch (TronException $e) { 152 | throw new TransactionException($e->getMessage(), $e->getCode()); 153 | } 154 | 155 | $transactions = isset($block['transactions']) ? $block['transactions'] : []; 156 | return new Block($block['blockID'], $block['block_header'], $transactions); 157 | } 158 | 159 | public function transactionReceipt(string $txHash): Transaction 160 | { 161 | try { 162 | $detail = $this->tron->getTransaction($txHash); 163 | } catch (TronException $e) { 164 | throw new TransactionException($e->getMessage(), $e->getCode()); 165 | } 166 | return new Transaction( 167 | $detail['txID'], 168 | $detail['raw_data'], 169 | $detail['ret'][0]['contractRet'] ?? '' 170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Transaction.php: -------------------------------------------------------------------------------- 1 | txID = $txID; 15 | $this->raw_data = $rawData; 16 | $this->contractRet = $contractRet; 17 | } 18 | 19 | public function isSigned(): bool 20 | { 21 | return (bool)sizeof($this->signature); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/TRC20Test.php: -------------------------------------------------------------------------------- 1 | 9 | * @license https://github.com/Fenguoz/tron-php/blob/master/LICENSE MIT 10 | * @link https://github.com/Fenguoz/tron-php 11 | */ 12 | 13 | namespace Tests; 14 | 15 | use GuzzleHttp\Client; 16 | use PHPUnit\Framework\TestCase; 17 | use Tron\Address; 18 | use Tron\Api; 19 | use Tron\TRC20; 20 | 21 | class TRC20Test extends TestCase 22 | { 23 | const URI = 'https://api.trongrid.io'; // mainnet 24 | // const URI = 'https://api.shasta.trongrid.io'; // shasta testnet 25 | const ADDRESS = 'TGytofNKuSReFmFxsgnNx19em3BAVBTpVB'; 26 | const PRIVATE_KEY = '0xf1b4b7d86a3eff98f1bace9cb2665d0cad3a3f949bc74a7ffb2aaa968c07f521'; 27 | const BLOCK_ID = 13402554; 28 | const TX_HASH = '539e6c2429f19a8626fadc1211985728e310f5bd5d2749c88db2e3f22a8fdf69'; 29 | const CONTRACT = [ 30 | 'contract_address' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT TRC20 31 | 'decimals' => 6, 32 | ]; 33 | 34 | private function getTRC20() 35 | { 36 | $api = new Api(new Client(['base_uri' => self::URI])); 37 | $config = self::CONTRACT; 38 | $trxWallet = new TRC20($api, $config); 39 | return $trxWallet; 40 | } 41 | 42 | public function testGenerateAddress() 43 | { 44 | $addressData = $this->getTRC20()->generateAddress(); 45 | var_dump($addressData); 46 | 47 | $this->assertTrue(true); 48 | } 49 | 50 | public function testPrivateKeyToAddress() 51 | { 52 | $privateKey = self::PRIVATE_KEY; 53 | $addressData = $this->getTRC20()->privateKeyToAddress($privateKey); 54 | var_dump($addressData); 55 | 56 | $this->assertTrue(true); 57 | } 58 | 59 | public function testBalance() 60 | { 61 | $address = new Address( 62 | self::ADDRESS, 63 | '', 64 | $this->getTRC20()->tron->address2HexString(self::ADDRESS) 65 | ); 66 | $balanceData = $this->getTRC20()->balance($address); 67 | var_dump($balanceData); 68 | 69 | $this->assertTrue(true); 70 | } 71 | 72 | public function testTransfer() 73 | { 74 | $privateKey = self::PRIVATE_KEY; 75 | $address = self::ADDRESS; 76 | $amount = 1; 77 | 78 | $from = $this->getTRC20()->privateKeyToAddress($privateKey); 79 | $to = new Address( 80 | $address, 81 | '', 82 | $this->getTRC20()->tron->address2HexString($address) 83 | ); 84 | $transferData = $this->getTRC20()->transfer($from, $to, $amount); 85 | var_dump($transferData); 86 | 87 | $this->assertTrue(true); 88 | } 89 | 90 | public function testBlockNumber() 91 | { 92 | $blockData = $this->getTRC20()->blockNumber(); 93 | var_dump($blockData); 94 | 95 | $this->assertTrue(true); 96 | } 97 | 98 | public function testBlockByNumber() 99 | { 100 | $blockID = self::BLOCK_ID; 101 | $blockData = $this->getTRC20()->blockByNumber($blockID); 102 | var_dump($blockData); 103 | 104 | $this->assertTrue(true); 105 | } 106 | 107 | public function testTransactionReceipt() 108 | { 109 | $txHash = self::TX_HASH; 110 | $txData = $this->getTRC20()->transactionReceipt($txHash); 111 | var_dump($txData); 112 | 113 | $this->assertTrue(true); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/TRXTest.php: -------------------------------------------------------------------------------- 1 | 9 | * @license https://github.com/Fenguoz/tron-php/blob/master/LICENSE MIT 10 | * @link https://github.com/Fenguoz/tron-php 11 | */ 12 | 13 | namespace Tests; 14 | 15 | use GuzzleHttp\Client; 16 | use PHPUnit\Framework\TestCase; 17 | use Tron\Address; 18 | use Tron\Api; 19 | use Tron\TRX; 20 | 21 | class TRXTest extends TestCase 22 | { 23 | const URI = 'https://api.shasta.trongrid.io'; // shasta testnet 24 | const ADDRESS = 'TGytofNKuSReFmFxsgnNx19em3BAVBTpVB'; 25 | const PRIVATE_KEY = '0xf1b4b7d86a3eff98f1bace9cb2665d0cad3a3f949bc74a7ffb2aaa968c07f521'; 26 | const BLOCK_ID = 13402554; 27 | const TX_HASH = '539e6c2429f19a8626fadc1211985728e310f5bd5d2749c88db2e3f22a8fdf69'; 28 | 29 | private function getTRX() 30 | { 31 | $api = new Api(new Client(['base_uri' => self::URI])); 32 | $trxWallet = new TRX($api); 33 | return $trxWallet; 34 | } 35 | 36 | public function testGenerateAddress() 37 | { 38 | $addressData = $this->getTRX()->generateAddress(); 39 | var_dump($addressData); 40 | 41 | $this->assertTrue(true); 42 | } 43 | 44 | public function testPrivateKeyToAddress() 45 | { 46 | $privateKey = self::PRIVATE_KEY; 47 | $addressData = $this->getTRX()->privateKeyToAddress($privateKey); 48 | var_dump($addressData); 49 | 50 | $this->assertTrue(true); 51 | } 52 | 53 | public function testBalance() 54 | { 55 | $address = new Address( 56 | self::ADDRESS, 57 | '', 58 | $this->getTRX()->tron->address2HexString(self::ADDRESS) 59 | ); 60 | $balanceData = $this->getTRX()->balance($address); 61 | var_dump($balanceData); 62 | 63 | $this->assertTrue(true); 64 | } 65 | 66 | public function testTransfer() 67 | { 68 | $privateKey = self::PRIVATE_KEY; 69 | $address = self::ADDRESS; 70 | $amount = 1; 71 | 72 | $from = $this->getTRX()->privateKeyToAddress($privateKey); 73 | $to = new Address( 74 | $address, 75 | '', 76 | $this->getTRX()->tron->address2HexString($address) 77 | ); 78 | $transferData = $this->getTRX()->transfer($from, $to, $amount); 79 | var_dump($transferData); 80 | 81 | $this->assertTrue(true); 82 | } 83 | 84 | public function testBlockNumber() 85 | { 86 | $blockData = $this->getTRX()->blockNumber(); 87 | var_dump($blockData); 88 | 89 | $this->assertTrue(true); 90 | } 91 | 92 | public function testBlockByNumber() 93 | { 94 | $blockID = self::BLOCK_ID; 95 | $blockData = $this->getTRX()->blockByNumber($blockID); 96 | var_dump($blockData); 97 | 98 | $this->assertTrue(true); 99 | } 100 | 101 | public function testTransactionReceipt() 102 | { 103 | $txHash = self::TX_HASH; 104 | $txData = $this->getTRX()->transactionReceipt($txHash); 105 | var_dump($txData); 106 | 107 | $this->assertTrue(true); 108 | } 109 | } 110 | --------------------------------------------------------------------------------