From 06aae71ff1234edabeeb2ebd463a0220931d1618 Mon Sep 17 00:00:00 2001 From: serderovsh Date: Sat, 29 May 2021 17:25:36 +0300 Subject: [PATCH] added Working with contracts --- composer.json | 2 +- examples/contract.php | 32 ++++ src/Exception/TRC20Exception.php | 5 + src/TRC20Contract.php | 253 +++++++++++++++++++++++++++++ src/TransactionBuilder.php | 2 +- src/Tron.php | 42 +++-- src/trc20.json | 269 +++++++++++++++++++++++++++++++ 7 files changed, 593 insertions(+), 12 deletions(-) create mode 100644 examples/contract.php create mode 100644 src/Exception/TRC20Exception.php create mode 100644 src/TRC20Contract.php create mode 100644 src/trc20.json diff --git a/composer.json b/composer.json index 0bb9e20..519a9b2 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ ], "require": { - "php": "^7.2", + "php": "^7.3", "guzzlehttp/guzzle": "^7.0", "iexbase/web3.php": "^2.0.1", "kornrunner/secp256k1": "^0.1.2", diff --git a/examples/contract.php b/examples/contract.php new file mode 100644 index 0000000..aea32da --- /dev/null +++ b/examples/contract.php @@ -0,0 +1,32 @@ +getMessage(); +} + + +try { + $tron = new Tron($fullNode, $solidityNode, $eventServer, null, true); + $contract = $tron->contract('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'); // Tether USDT https://tronscan.org/#/token20/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t + + + // Data + echo $contract->name(); + echo $contract->symbol(); + echo $contract->balanceOf(); + echo $contract->totalSupply(); + //echo $contract->transfer('to', 'amount', 'from'); + + +} catch (\IEXBase\TronAPI\Exception\TronException $e) { + echo $e->getMessage(); +} \ No newline at end of file diff --git a/src/Exception/TRC20Exception.php b/src/Exception/TRC20Exception.php new file mode 100644 index 0000000..d293198 --- /dev/null +++ b/src/Exception/TRC20Exception.php @@ -0,0 +1,5 @@ + + * @license https://github.com/iexbase/tron-api/blob/master/LICENSE (MIT License) + * @version 1.3.4 + * @link https://github.com/iexbase/tron-api + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace IEXBase\TronAPI; + +use IEXBase\TronAPI\Exception\TRC20Exception; + +class TRC20Contract +{ + const TRX_TO_SUN = 1000000; + const SUN_TO_TRX = 0.000001; + + /*** + * Maximum decimal supported by the Token + * + * @var integer + */ + private $decimals; + + /** + * The smart contract which issued TRC20 Token + * + * @var string + */ + private $contractAddress; + + /** + * ABI Data + * + * @var string + */ + private $abiData; + + /** + * Fee Limit + * + * @var integer + */ + private $feeLimit = 10; + + /** + * Base Tron object + * + * @var Tron + */ + protected $tron; + + /** + * Create Trc20 Contract + * + * @param Tron $tron + * @param string $contractAddress + * @param string|null $abi + */ + public function __construct(Tron $tron, string $contractAddress, string $abi = null) + { + $this->tron = $tron; + + // If abi is absent, then it takes by default + if(is_null($abi)) { + $abi = file_get_contents(__DIR__.'/trc20.json'); + } + + $this->abiData = json_decode($abi, true); + $this->contractAddress = $contractAddress; + } + + /** + * Get contract name + * + * @return string + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function name() + { + return $this->trigger('name', null, [])[0]; + } + + /** + * Get symbol name + * + * @return string + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function symbol() + { + return $this->trigger('symbol', null, [])[0]; + } + + /** + * Balance TRC20 contract + * + * @param string|null $address + * @return string + * @throws TRC20Exception + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function balanceOf(string $address = null) + { + if(is_null($address)) + $address = $this->tron->address['base58']; + + $result = $this->trigger('balanceOf', $address, [ + str_pad($this->tron->address2HexString($address), 64, "0", STR_PAD_LEFT) + ]); + + $balance = $result[0]->toString(); + if (!is_numeric($balance)) + throw new TRC20Exception('Token balance not found'); + + return bcdiv($balance, bcpow("10", $this->decimals()), $this->decimals()); + } + + /** + * Send TRC20 contract + * + * @param string $to + * @param float $amount + * @param string|null $from + * @return string + * @throws TRC20Exception + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function transfer(string $to, float $amount, string $from = null) + { + if($from == null) { + $from = $this->tron->address['base58']; + } + + $feeLimitInSun = bcmul($this->feeLimit, self::TRX_TO_SUN); + + if (!is_numeric($this->feeLimit) OR $this->feeLimit <= 0) { + throw new TRC20Exception('fee_limit is required.'); + } else if($this->feeLimit > 1000) { + throw new TRC20Exception('fee_limit must not be greater than 1000 TRX.'); + } + + $tokenAmount = bcmul($amount, bcpow("10", $this->decimals(), 0), 0); + $transfer = $this->tron->getTransactionBuilder() + ->triggerSmartContract( + $this->abiData, + $this->tron->address2HexString($this->contractAddress), + 'transfer', + [$this->tron->address2HexString($to),$tokenAmount], + $feeLimitInSun, + $this->tron->address2HexString($from) + ) + ; + $signedTransaction = $this->tron->signTransaction($transfer); + $response = $this->tron->sendRawTransaction($signedTransaction); + + return array_merge($response, $signedTransaction); + } + + /** + * The total number of tokens issued on the main network + * + * @return string + * @throws TRC20Exception + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function totalSupply() + { + $result = $this->trigger('totalSupply', null, []); + $totalSupply = $result[0]->toString(); + + if (!is_numeric($totalSupply)) + throw new TRC20Exception("Token totalSupply not found"); + + $totalSupply = bcdiv($totalSupply, bcpow("10", $this->decimals()), $this->decimals()); + return $totalSupply; + } + + + /** + * Maximum decimal supported by the Token + * + * @throws TRC20Exception + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function decimals() + { + if (!is_null($this->decimals)) + return $this->decimals; + + + $result = $this->trigger('decimals', null, []); + $decimals = $result[0]->toString(); + + if (!is_numeric($decimals)) { + throw new TRC20Exception("Token decimals not found"); + } + + $this->decimals = $decimals; + return $this->decimals; + } + + /** + * TRC20 All transactions + * + * @param string $address + * @param int $limit + * @return array + * + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function getTransactions(string $address, $limit = 100) + { + return $this->tron->getManager() + ->request("v1/accounts/{$address}/transactions/trc20?limit={$limit}&contract_address={$this->contractAddress}", [], 'get'); + } + + /** + * Find transaction + * + * @param string $transaction_id + * @return array + * @throws \IEXBase\TronAPI\Exception\TronException + */ + public function getTransaction(string $transaction_id) + { + return $this->tron->getManager() + ->request('/wallet/gettransactioninfobyid', ['value' => $transaction_id], 'post'); + } + + /** + * Config trigger + * + * @param $function + * @param null $address + * @param array $params + * @return mixed + * @throws \IEXBase\TronAPI\Exception\TronException + */ + private function trigger($function, $address = null, $params = []) + { + $owner_address = is_null($address) ? '410000000000000000000000000000000000000000' : $this->tron->address2HexString($address); + + return $this->tron->getTransactionBuilder() + ->triggerConstantContract($this->abiData, $this->tron->address2HexString($this->contractAddress), $function, $params, $owner_address); + } +} diff --git a/src/TransactionBuilder.php b/src/TransactionBuilder.php index 928e832..f940de5 100644 --- a/src/TransactionBuilder.php +++ b/src/TransactionBuilder.php @@ -38,7 +38,7 @@ public function __construct(Tron $tron) */ public function sendTrx($to, $amount, string $from = null) { - if (!is_float($amount) || $amount < 0) { + if ($amount < 0) { throw new TronException('Invalid amount provided'); } diff --git a/src/Tron.php b/src/Tron.php index eecdc49..90c0655 100644 --- a/src/Tron.php +++ b/src/Tron.php @@ -16,8 +16,8 @@ namespace IEXBase\TronAPI; -use BN\BN; use Elliptic\EC; +use IEXBase\TronAPI\Exception\TRC20Exception; use IEXBase\TronAPI\Support\Base58; use IEXBase\TronAPI\Support\Base58Check; use IEXBase\TronAPI\Support\Crypto; @@ -26,7 +26,6 @@ use IEXBase\TronAPI\Support\Utils; use IEXBase\TronAPI\Provider\HttpProviderInterface; use IEXBase\TronAPI\Exception\TronException; -use kornrunner\Secp256k1; /** * A PHP API for interacting with the Tron (TRX) @@ -46,7 +45,10 @@ class Tron implements TronInterface const ADDRESS_PREFIX_BYTE = 0x41; /** - * Default Address + * Default Address: + * Example: + * - base58: T**** + * - hex: 41**** * * @var array */ @@ -73,9 +75,16 @@ class Tron implements TronInterface * Transaction Builder * * @var TransactionBuilder - */ + */ protected $transactionBuilder; + /** + * Transaction Builder + * + * @var TransactionBuilder + */ + protected $trc20Contract; + /** * Provider manager * @@ -169,6 +178,19 @@ public function getManager(): TronManager { return $this->manager; } + + /** + * Contract module + * + * @param string $contractAddress + * @param string|null $abi + * @return TRC20Contract + */ + public function contract(string $contractAddress, string $abi = null) + { + return new TRC20Contract($this, $contractAddress, $abi); + } + /** * Set is object * @@ -1184,16 +1206,16 @@ public function validateAddress(string $address = null, bool $hex = false): arra * @param string|null $address * @return bool */ - public function isAddress(string $address = null) + public function isAddress(string $address = null): bool { - $address = Base58Check::decode($address, 0, 0, false); - $utf8 = hex2bin($address); - if(strlen($address) !== self::ADDRESS_SIZE) return false; - if (strlen($utf8) !== 25 or strpos($utf8, self::ADDRESS_PREFIX_BYTE) !== 0) - return false; + $address = Base58Check::decode($address, 0, 0, false); + $utf8 = hex2bin($address); + + if(strlen($utf8) !== 25) return false; + if(strpos($utf8 , self::ADDRESS_PREFIX_BYTE) !== 0) return false; $checkSum = substr($utf8, 21); $address = substr($utf8, 0, 21); diff --git a/src/trc20.json b/src/trc20.json new file mode 100644 index 0000000..62e8f2f --- /dev/null +++ b/src/trc20.json @@ -0,0 +1,269 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "recipient", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "recipient", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } +] \ No newline at end of file