发送TRC20-Token交易

2023-07-24 21:32:08 浏览数 (1)

简介波场网络跟以太坊很像,特别是接口设计,token的发送目标首先是合约地址而不是接收token的钱包地址,其次参数里加上接收者钱包地址和数量。

代码语言:javascript复制
<?php 

function getChainParamValue($chainParams, $key) {
    if (is_array($chainParams)) {
        foreach($chainParams as $chainParam) {
            if ($chainParam['key'] == $key) {
                return $chainParam['value'];
            }
        }
    }

    return false;
}

function base58_encode($string)
{
    $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
    $base = strlen($alphabet);
    if (is_string($string) === false) {
        return false;
    }
    if (strlen($string) === 0) {
        return '';
    }
    $bytes = array_values(unpack('C*', $string));
    $decimal = $bytes[0];
    for ($i = 1, $l = count($bytes); $i < $l; $i  ) {
        $decimal = bcmul($decimal, 256);
        $decimal = bcadd($decimal, $bytes[$i]);
    }
    $output = '';
    while ($decimal >= $base) {
        $div = bcdiv($decimal, $base, 0);
        $mod = bcmod($decimal, $base);
        $output .= $alphabet[$mod];
        $decimal = $div;
    }
    if ($decimal > 0) {
        $output .= $alphabet[$decimal];
    }
    $output = strrev($output);
    foreach ($bytes as $byte) {
        if ($byte === 0) {
            $output = $alphabet[0] . $output;
            continue;
        }
        break;
    }
    return (string) $output;
}
function base58_decode($base58)
{
    $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
    $base = strlen($alphabet);
    if (is_string($base58) === false) {
        return false;
    }
    if (strlen($base58) === 0) {
        return '';
    }
    $indexes = array_flip(str_split($alphabet));
    $chars = str_split($base58);
    foreach ($chars as $char) {
        if (isset($indexes[$char]) === false) {
            return false;
        }
    }
    $decimal = $indexes[$chars[0]];
    for ($i = 1, $l = count($chars); $i < $l; $i  ) {
        $decimal = bcmul($decimal, $base);
        $decimal = bcadd($decimal, $indexes[$chars[$i]]);
    }
    $output = '';
    while ($decimal > 0) {
        $byte = bcmod($decimal, 256);
        $output = pack('C', $byte) . $output;
        $decimal = bcdiv($decimal, 256, 0);
    }
    foreach ($chars as $char) {
        if ($indexes[$char] === 0) {
            $output = "x00" . $output;
            continue;
        }
        break;
    }
    return $output;
}

//encode address from byte[] to base58check string
function base58check_en($address)
{
 $hash0 = hash("sha256", $address);
 $hash1 = hash("sha256", hex2bin($hash0));
 $checksum = substr($hash1, 0, 8);
 $address = $address.hex2bin($checksum);
 $base58add = base58_encode($address);
 return $base58add;
}

//decode address from base58check string to byte[]
function base58check_de($base58add)
{
 $address = base58_decode($base58add);
 $size = strlen($address);
 if ($size != 25) {
    return false;
 }
 $checksum = substr($address, 21);
 $address = substr($address, 0, 21);     
 $hash0 = hash("sha256", $address);
 $hash1 = hash("sha256", hex2bin($hash0));
 $checksum0 = substr($hash1, 0, 8);
 $checksum1 = bin2hex($checksum);
 if (strcmp($checksum0, $checksum1)) {
    return false;
 }
 return $address;
}

function hexString2Base58check($hexString){
    $address = hex2bin($hexString);
    $base58add = base58check_en($address);
    return $base58add;
}

function base58check2HexString($base58add){
    $address = base58check_de($base58add);
    $hexString = bin2hex($address);
    return $hexString;
}

function hexString2Base64($hexString){
    $address = hex2bin($hexString);
    $base64 = base64_encode($address);
    return $base64;
}

function base642HexString($base64){
    $address = base64_decode($base64);
    $hexString = bin2hex($address);
    return $hexString;
}

function base58check2Base64($base58add){
    $address = base58check_de($base58add);
    $base64 = base64_encode($address);
    return $base64;
}

function base642Base58check($base64){
    $address = base64_decode($base64);
    $base58add = base58check_en($address);
    return $base58add;
}

function hex2Dec(string $hex): string
{
    $dec = 0;
    $len = strlen($hex);
    for ($i = 1; $i <= $len; $i  ) {
        $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
    }
    return $dec;
}

function dec2Hex($dec): string
{
    $last = bcmod($dec, 16);
    $remain = bcdiv(bcsub($dec, $last), 16);
    if ($remain == 0) {
        return dechex($last);
    } else {
        return dec2Hex($remain) . dechex($last);
    }
}

function hex2Str(string $hex): string
{
    $str = "";
    for ($i = 0; $i < strlen($hex) - 1; $i  = 2) {
        $str .= chr(hexdec($hex[$i] . $hex[$i   1]));
    }
    return $str;
}

function str2Hex(string $str): string
{
    $hex = "";
    for ($i = 0; $i < strlen($str); $i  ) {    
        $hex .= str_pad( dechex(ord($str[$i])) , 2 ,"0", STR_PAD_LEFT);
    }
    return $hex;
}

代码语言:javascript复制
<?php 
use IEXBaseTronAPITron;
use IEXBaseTronAPISupport;
use Web3ContractsEthabi;
use Web3ContractsTypes{Address, Boolean, Bytes, DynamicBytes, Integer, Str, Uinteger};
use kornrunnerKeccak;
use BitWaspBitcoinKeyFactoryPrivateKeyFactory;

include_once "../libraries/vendor/autoload.php";
include_once("html_iframe_header.php");
include_once("tron_utils.php");

//include all php files that generated by protoc
$dir   = new RecursiveDirectoryIterator('protobuf/core/');
$iter  = new RecursiveIteratorIterator($dir);
$files = new RegexIterator($iter, '/^. .php$/', RecursiveRegexIterator::GET_MATCH); // an Iterator, not an array
foreach ( $files as $file ) {

    if (is_array($file)) {
        foreach($file as $filename) {
            include $filename;
        }
    } else {
        include $file;
    }
}

define("TRX_TO_SUN",'1000000');
define("SUN_TO_TRX", '0.000001');

$supportChains = ['main'=>"Tron Mainnet", 'shasta'=>"Shasta Testnet"];

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    try {

        $feeLimit = $_POST['fee_limit'];
        $feeLimitInSun = bcmul($feeLimit, TRX_TO_SUN);

        if (!is_numeric($feeLimit) OR $feeLimit <= 0) {
            throw new Exception('fee_limit is required.');
        } else if($feeLimit > 1000) {
            throw new Exception('fee_limit must not be greater than 1000 TRX.');
        }

        if ($_POST['chain'] == 'main') {
            $fullNode = new IEXBaseTronAPIProviderHttpProvider('https://api.trongrid.io');
            $solidityNode = new IEXBaseTronAPIProviderHttpProvider('https://api.trongrid.io');
            $eventServer = new IEXBaseTronAPIProviderHttpProvider('https://api.trongrid.io');
        } else {
            $fullNode = new IEXBaseTronAPIProviderHttpProvider('https://api.shasta.trongrid.io');
            $solidityNode = new IEXBaseTronAPIProviderHttpProvider('https://api.shasta.trongrid.io');
            $eventServer = new IEXBaseTronAPIProviderHttpProvider('https://api.shasta.trongrid.io');
        }

        $tron = new IEXBaseTronAPITron($fullNode, $solidityNode, $eventServer);

        if ($_POST['generate_way'] == 'Generate Offline') {
            //[GENERATE ENCODED DATA FOR TOKEN CONTRACT]
            $ethAbi = new Ethabi(['address' => new Address,'bool' => new Boolean,'bytes' => new Bytes,'dynamicBytes' => new DynamicBytes,'int' => new Integer,'string' => new Str,'uint' => new Uinteger,]);

            $function = "transfer(address,uint256)";
            $functionAbi = json_decode('{"constant":false,"inputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}',true);
            $functionSignature = ltrim($ethAbi->encodeFunctionSignature($function), '0x');

            $recipient = $_POST['recipient'];
            $tokenAmount = bcmul($_POST['token_amount'], bcpow("10", $_POST['token_decimals'], 0), 0);
            $contractParams = [base58check2HexString($recipient),$tokenAmount];
            $parameters = substr($ethAbi->encodeParameters($functionAbi, $contractParams),2);

            //[GENERATE CONTRACT'S SERIALIZED HEX]
            //get owner address from private key
            $privKeyFactory = new PrivateKeyFactory();
            $privateKey = $privKeyFactory->fromHexUncompressed($_POST['privkey']);
            $publicKey  = $privateKey->getPublicKey();
            $publicKeyHex = substr($publicKey->getHex(), 2);

            $ownerAddressHex = Keccak::hash(hex2bin($publicKeyHex), 256);
            $ownerAddressHex = "41" . substr($ownerAddressHex, -40);

            $ownerAddressBin = hex2str($ownerAddressHex);
            $contractAddressBin = hex2str(base58check2HexString($_POST['contract_addr']));
            $callValue = "0";

            $contract = new ProtocolTransactionContract();
            $triggerSmartContract = new ProtocolTriggerSmartContract();

            $triggerSmartContract->setData(hex2str($functionSignature.$parameters ));
            $triggerSmartContract->setOwnerAddress($ownerAddressBin);
            $triggerSmartContract->setContractAddress($contractAddressBin);
            $triggerSmartContract->setCallValue($callValue);

            $any = new GoogleProtobufAny();
            $any->pack($triggerSmartContract);

            $contract->setParameter( $any );
            $contract->setType( ProtocolTransactionContractContractType::TriggerSmartContract );

            //[GENERATE RAW TX]
            //get current block
            $newestBlock = $tron->getCurrentBlock();
            $currentHeight = (int)$newestBlock['block_header']['raw_data']['number'];
            if ($currentHeight<=0) {
                throw new Exception("Fail retrieve current block.");
            }

            //get last confirmed block
            $confirmation = 20;
            $targetHeight = ($currentHeight - $confirmation)   1;

            $confirmedBlock = $tron->getBlockByNumber($targetHeight);
            $blockHeight = (int)$confirmedBlock['block_header']['raw_data']['number'];
            $blockTs = (int)$confirmedBlock['block_header']['raw_data']['timestamp'];
            $blockHash = $confirmedBlock['blockID'];

            $currentTimeMillis = round(microtime(true) * 1000);

            //build tx
            $raw = new ProtocolTransactionRaw();
            $raw->setContract([$contract]);
            $raw->setFeeLimit($feeLimitInSun);

            $blockHeightIn64bits = str_pad(dechex($blockHeight), 8 * 2 /* 8 bytes = 16 hex chars*/, "0", STR_PAD_LEFT);

            $raw->setRefBlockBytes( hex2Str( $refBlockBytes = substr($blockHeightIn64bits, 12, 4) ));
            $raw->setRefBlockHash( hex2Str( $refBlockHash =  substr($blockHash, 16, 16) ));
            $raw->setTimestamp($currentTimeMillis);
            $raw->setExpiration( $blockTs   (10 * 60 * 60 * 1000) );#expiration set 10 hours from last confirmed block
            $txId = hash("sha256", $raw->serializeToString());

            $tx = new ProtocolTransaction();
            $tx->setRawData($raw);

            $signature = SupportSecp::sign($txId, $_POST['privkey']);
            $tx->setSignature([hex2str( $signature )]);

            ?>
            <div class="alert alert-success">

                <h6 class="mt-3">Raw Tx (Hex)</h6>
                <textarea class="form-control" rows="5" id="comment" readonly><?php echo str2hex($tx->serializeToString());?></textarea>

                <h6 class="mt-3">Contract Serialized Hex</h6>
                <textarea class="form-control" rows="5" id="comment" readonly><?php echo str2hex($contract->serializeToString());?></textarea>

                <h6 class="mt-3">Encoded Data (Hex)</h6>
                <textarea class="form-control" rows="5" id="comment" readonly><?php echo "0x".$functionSignature.$parameters ;?></textarea>

                <h6 class="mt-3">Function</h6>
                <input class="form-control" value="<?php echo $function ;?>" readonly/>

                <h6 class="mt-3">TX Hash</h6>
                <input class="form-control" readonly value="<?php echo $txId?>"/>
            </div>
            <?Php
        } else {

            $tokenAmount = bcmul($_POST['token_amount'], bcpow("10", $_POST['token_decimals'], 0), 0);
            $function = "transfer";

            //get owner address from private key
            $privKeyFactory = new PrivateKeyFactory();
            $privateKey = $privKeyFactory->fromHexUncompressed($_POST['privkey']);
            $publicKey  = $privateKey->getPublicKey();
            $publicKeyHex = substr($publicKey->getHex(), 2);

            $ownerAddressHex = Keccak::hash(hex2bin($publicKeyHex), 256);
            $ownerAddressHex = "41" . substr($ownerAddressHex, -40);

            $abi = '[{"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"}]';
            $abiAry = json_decode($abi, true);

            $tx = $tron->getTransactionBuilder()->triggerSmartContract(
                $abiAry, 
                base58check2HexString($_POST['contract_addr']), 
                $function, 
                [base58check2HexString($_POST['recipient']),$tokenAmount], 
                $feeLimitInSun,
                $ownerAddressHex, 
                0, 
                0
            );

            $tron->setPrivateKey($_POST['privkey']);

            $mutatedTx = $tron->signTransaction($tx);

            $newTx = new ProtocolTransaction();
            $parsedRaw =  new ProtocolTransactionRaw();
            $parsedRaw->mergeFromString(hex2str($mutatedTx['raw_data_hex']));

            $newTx->setRawData($parsedRaw);
            $signature = SupportSecp::sign($mutatedTx['txID'], $_POST['privkey']);
            $newTx->setSignature([hex2str( $signature )]);

            ?>
            <div class="alert alert-success">

                <h6 class="mt-3">Function Return Result</h6>
                <textarea class="form-control" rows="10" id="comment" readonly><?Php print_r($mutatedTx)?></textarea>

                <h6 class="mt-3">Raw Tx (Hex)</h6>
                <textarea class="form-control" rows="5" id="comment" readonly><?php echo str2hex($newTx->serializeToString());?></textarea>

                <h6 class="mt-3">TX Hash</h6>
                <input class="form-control" readonly value="<?php echo $mutatedTx['txID']?>"/>
            </div>
            <?php
        }

    } catch (Exception $e) {
        $errmsg .= "Problem found. " . $e->getMessage();

    }
} 

if ($errmsg) {
?>
    <div class="alert alert-danger">
        <strong>Error!</strong> <?php echo $errmsg?>
    </div>
<?php
}
?>
<form action='' method='post'>

    <div class="form-group">
        <label for="chain">Chain:</label>
        <select id="chain" name="chain" class="form-control" >
            <?php
            foreach($supportChains as $k=>$v) {
                echo "<option value='{$k}'".($k == $_POST['chain'] ? " selected": "").">{$v}</option>";
            }
            ?>
        </select>
    </div>

    <div class="form-group">
        <label for="fee_limit">Fee Limit:</label>

        <div class="input-group mb-3">
            <input class="form-control" type='text' name='fee_limit' id='fee_limit' value='<?php echo $_POST['fee_limit']?>'>
            <div class="input-group-append">
              <span class="input-group-text">TRX</span>
            </div>
        </div>
    </div>

    <div class="form-group">
        <label for="contract_addr">To:</label>
        <input placeholder="Token's Contract Address" class="form-control" type='text' name='contract_addr' id='contract_addr' value='<?php echo $_POST['contract_addr']?>'>
    </div>

    <div class="form-group">
        <label for="token_amount">Send Token Amount:</label>
        <input class="form-control" type='text' name='token_amount' id='token_amount' value='<?php echo $_POST['token_amount']?>'>
    </div>

    <div class="form-group">
        <label for="token_decimals">Token Decimal Places:</label>
        <input class="form-control" type='text' name='token_decimals' id='token_decimals' value='<?php echo $_POST['token_decimals']?>'>
    </div>

    <div class="form-group">
        <label for="recipient">Recipient:</label>
        <input class="form-control" type='text' name='recipient' id='recipient' value='<?php echo $_POST['recipient']?>'>
    </div>

     <div class="form-group">
        <label for="privkey">From:</label>
        <input placeholder="Sender's Private Key (Hex)" class="form-control" type='text' name='privkey' id='privkey' value='<?php echo $_POST['privkey']?>'>
    </div>

    <input type='submit' class="btn btn-success" name="generate_way" value="Generate Offline"/>

    <input type='submit' class="btn btn-success" name="generate_way" value="Generate By Trongrid"/>
</form>
<?php
include_once("html_iframe_footer.php");

0 人点赞