├── .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 |
7 |
8 |
9 |
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 | [](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 |
7 |
8 |
9 |
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 | [](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 |
--------------------------------------------------------------------------------