| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- <?php
- namespace app\common\logic;
- use app\common\model\OfflineRechargeRecordModel;
- use app\common\model\OfflineRechargeVerifyModel;
- use app\common\model\ParametersModel;
- use app\common\model\ServersModel;
- use app\common\model\UserModel;
- use fast\Http;
- use fast\RechargeOrderType;
- use fast\RechargeStatus;
- use fast\RechargeType;
- use think\Cache;
- use think\Env;
- use think\Log;
- /**
- * 币安链API
- */
- class BscApi
- {
- protected $transactionLimt = 1;//识别到的最小到账金额,低于此值的到账金额不入账
- protected $bsc_api_key = '8TP62I7UJF2AY7TAH3TAR4R3IM7N9ZCHP2';
- protected $bsc_api_url = 'https://api.etherscan.io/v2/api?chainid=56';//56为币安链主网ID
- protected $contract_address = '0x55d398326f99059ff775485246999027b3197955';//交易币种的合约地址,默认为usdt的:0x55d398326f99059ff775485246999027b3197955,测试合约:0xcf3271b1be72f834e4dd35b9d558d583894473f1
- /**
- * value的放大比例 固定为: 10的18次方
- * @var int
- */
- private string $valueScale = '1000000000000000000';
- public function __construct($contract_address = '') {
- // 初始化逻辑代码
- $key = Env::get('rental.bsc_api_key');
- if(!empty($key)){
- $this->bsc_api_key = $key;
- }
- if(empty($contract_address)){
- $contract_address = $this->contract_address;
- }
- $this->contract_address = strtolower($contract_address);//转小写
- }
- /**
- * 根据哈希查看交易详情
- *
- * 此接口为抓取币安浏览器页面html代码而来
- * 访问地址:https://bscscan.com/tx/0x48351c4c215645f2404741343c9be17b0ce494d70a7dba1bf11d1f5c2311de57
- */
- public function getInfoByTransactionHash($tx_hash = '0x48351c4c215645f2404741343c9be17b0ce494d70a7dba1bf11d1f5c2311de57')
- {
- if(empty($tx_hash)){
- return _error('hash不能为空');
- }
- // 读取远程页面地址
- $url = $this->bsc_api_url . "tx/" . $tx_hash;
- dump($url);
- // 使用 file_get_contents 函数读取远程页面
- //$html = file_get_contents($url);
- $html = Http::get($url);
- if(empty($html)){
- return _error('bscscan.com访问失败');
- }
- // dump($html);
- $dom = new \DOMDocument();
- libxml_use_internal_errors(true);//关掉错误提醒
- $dom->loadHTML($html);
- libxml_clear_errors();
- $get_html_value = $dom->getElementById('spanTxHash');
- if(empty($get_html_value)){
- return _error('当前页面没找到hash');
- }
- $resp['hash'] = $get_html_value->nodeValue;
- //$get_html_value = '<a class="text-break me-2" href="/address/' . $this->contract_address . '">' . $this->contract_address . '</a>';
- //$get_html_value = '<a class="me-1" href="/token/' . $this->contract_address . '" target="_parent">BSC-USD</a>';
- $get_html_value = '<a data-highlight-value data-highlight-target="' . $this->contract_address . '" class="d-flex align-items-center gap-0.5" href="/token/' . $this->contract_address . '">';
- if (!(strstr($html, $get_html_value))) {
- Log::error('合约地址和配置信息不相符:' . $tx_hash);
- return _error('合约地址和配置信息不相符');
- }
- $xpath = new \DOMXPath($dom);
- $get_html_value = $xpath->query('//span[@class="badge bg-success bg-opacity-10 border border-success border-opacity-25 text-green-600 fw-medium text-start text-wrap py-1.5 px-2"]');
- if ($get_html_value->length > 0 && $get_html_value->item(0)->nodeValue == 'Success') {
- $resp['pay_status'] = 1;
- } else {
- return _error('交易失败');
- }
- $get_html_value = $xpath->query('//span[@class="me-1"]');
- if ($get_html_value->length > 0) {
- $amount = $get_html_value->item(1)->nodeValue;//所有 span class='me-1' 的元素中,第二个就是额度,超过1000会有 逗号分割
- $resp['amount'] = str_replace(',', "", $amount);
- } else {
- return _error('当前页面没找到交易额度');
- }
- $get_html_value = $dom->getElementById('chunk_decodeori_0_1');
- if(empty($get_html_value)){
- return _error('当前页面没找到from_address');
- }
- $resp['from_address'] = strtolower($get_html_value->nodeValue);//转小写
- $get_html_value = $dom->getElementById('chunk_decodeori_0_2');
- if(empty($get_html_value)){
- return _error('当前页面没找到to_address');
- }
- $resp['to_address'] = strtolower($get_html_value->nodeValue);
- return _success($resp);
- }
- /**
- * 根据钱包地址查询交易记录
- *
- * 通过接口拿到数据,便利交易数据,返回可用格式
- *
- * 此接口为抓取币安交易所API
- * 使用前需要在交易所申请 bsc_api_key
- * 数据请求地址:https://api.bscscan.com/api?module=account&action=tokentx&contractaddress=0x55d398326f99059ff775485246999027b3197955&page=1&offset=10000&endblock=99999999&apikey=VTCKIP346DCRWB6JNS4KDANUJJEQN9VAKW&address=0xca3dfc6d52131bd0df91a6302e48df5f50288256&startblock=33138777
- * 请求地址参数:https://api.bscscan.com/api?
- * module=account //固定值
- * &action=tokentx //固定值
- * &contractaddress= 合约地址
- * &page=1 //页数
- * &offset=10000 //每页条数
- * &startblock=33138777 //起始区块
- * &endblock=99999999 //结束区块
- * &apikey=币安交易所apikey
- * &address=0xca3dfc6d52131bd0df91a6302e48df5f50288256 //查询钱包地址
- *
- * 数据返回格式
- * {
- * "status":"1",
- * "message":"OK",
- * "result":[
- * {
- * "blockNumber":"33138803",
- * "timeStamp":"1698920688",
- * "hash":"0x0e1d85533edb27e8ee246721145eb15c439d943315608e837cd0dbff9b80d7a2",
- * "nonce":"9",
- * "blockHash":"0x986e923d94cb9390f7de8054ba9db3bdd0abf37a6c52ca7b28f3f79dd87b33a8",
- * "from":"0x86daae6c4dac3cd8a56191af7ea1d13546538dbe",
- * "contractAddress":"0x55d398326f99059ff775485246999027b3197955",
- * "to":"0xca3dfc6d52131bd0df91a6302e48df5f50288256",
- * "value":"400000000000000000000",
- * "tokenName":"Binance-Peg BSC-USD",
- * "tokenSymbol":"BSC-USD",
- * "tokenDecimal":"18",
- * "transactionIndex":"17",
- * "gas":"52224",
- * "gasPrice":"5000000000",
- * "gasUsed":"47424",
- * "cumulativeGasUsed":"1297987",
- * "input":"deprecated",
- * "confirmations":"3883"
- * },
- * {…………},
- * {…………},
- * ]
- * }
- * @$address 查询地址
- * @$from_to 是查询付款交易(from),还剩收款交易(to),默认from
- */
- public function getTransactionRecordsByAddress(string $from, string $to, int $start_block, int $end_block = 99999999)
- {
- if(empty($from) && empty($to)){
- return _error('钱包地址不能都为空');
- }
- $from = strtolower($from);
- $to = strtolower($to);
- $address = $from;
- if(empty($from)){
- $address =$to;
- }
- // 读取远程页面地址
- $url = $this->bsc_api_url . "&module=account&action=tokentx&contractaddress=" . $this->contract_address;
- $url .= "&page=1&offset=10000&endblock=" . $end_block ."&apikey=" . $this->bsc_api_key;
- $url .= "&startblock=" . $start_block . "&address=" . $address;
- Log::log($url);
- $body = Http::get($url);
- if (empty($body)) {
- Log::log('api返回内容为空');
- return _error('api返回内容为空');
- }
- // 转成数组
- $rsArr = json_decode($body, true);
- if (empty($rsArr) || !is_array($rsArr)) {
- Log::log('api返回数据异常,json decode失败');
- return _error('api返回数据异常');
- }
- if ($rsArr['status'] != '1') {
- if($rsArr['message'] == 'No transactions found'){
- Log::log('无交易记录:' . $rsArr['message']);
- return _success();
- }
- Log::log('api返回status不为1,错误信息:' . $rsArr['message']);
- return _error('api返回status不为1,错误信息:' . $rsArr['message']);
- }
- $success_arr = [];
- // 查询匹配交易列表
- foreach ($rsArr['result'] as $v) {
- if (strtolower($v['contractAddress']) != $this->contract_address) {
- continue;//合约地址不相符,不处理。一般币种为USDT的合约地址:0x55d398326f99059ff775485246999027b3197955
- }
- $amount = bcdiv($v['value'], $this->valueScale, 6);//交易金额
- if(!($amount > 0)){
- continue;//交易金额必须大于0
- }
- $info = [
- 'hash' => $v['hash'],
- 'from' => strtolower($v['from']),//转小写
- 'to' => strtolower($v['to']),
- 'contract' => $v['contractAddress'],
- 'amount' => $amount,
- 'block_number' => $v['blockNumber'],
- 'time' => $v['timeStamp'],
- ];
- if(!empty($from) && !empty($to)){
- if($info['from'] == $from && $info['to'] == $to){
- //判断交易是否成功
- $rsData = $this->getHashStatus($info['hash']);
- if($rsData['code'] == 0){//当前哈希交易状态为失败,跳出循环,等待下次判断,若真是失败交易,过几分钟后,这个数据不会再出现,这个接口拿到的都是有效的交易数据
- continue;//当前交易失败
- }
- Log::log('成功1单');
- $success_arr[] = $info;
- }
- }elseif (!empty($from)){
- if($info['from'] == $from){
- //判断交易是否成功
- $rsData = $this->getHashStatus($info['hash']);
- if($rsData['code'] == 0){
- continue;//当前交易失败
- }
- $success_arr[] = $info;
- }
- }elseif (!empty($to)){
- if($info['to'] == $to){
- //判断交易是否成功
- $rsData = $this->getHashStatus($info['hash']);
- if($rsData['code'] == 0){
- continue;//当前交易失败
- }
- $success_arr[] = $info;
- }
- }
- }
- return _success($success_arr);
- }
- /**
- * 获取哈希地址成功状态
- *
- * api接口返回数据格式
- * {
- "status": "1",
- "message": "OK",
- "result": {
- "status": "1"
- }
- }
- * @param $orderInfo
- * @return array|string
- */
- public function getHashStatus($tx_hash):array
- {
- if (empty($tx_hash)) {
- return _error('hash值不能为空');
- }
- $url = $this->bsc_api_url . "&module=transaction&action=gettxreceiptstatus";
- $url .= "&apikey=" . $this->bsc_api_key;
- $url .= "&txhash=" . $tx_hash;
- $body = Http::get($url);
- if (empty($body)) {
- return _error('状态api返回内容为空');
- }
- // 转成数组
- $rsArr = json_decode($body, true);
- if (empty($rsArr) || !is_array($rsArr)) {
- return _error('状态api返回数据异常,json转换失败');
- }
- if ($rsArr['status'] != '1') {
- return _error('状态api返回status不为1,当前值为:' . $rsArr['status']);
- }
- if ($rsArr['result']['status'] != 1) {
- return _error('状态api返回result中的status不为1,当前值为:' . $rsArr['result']['status']);
- }
- return _success();
- }
- /**
- * 根据时间戳获取最近的区块高度
- *
- * api接口返回数据格式
- * {
- "status": "1",
- "message": "OK",
- "result": "33164048"
- }
- * @param $orderInfo
- * @return array|string
- */
- public function getBlockNoByTime($time):array
- {
- if(!Cache::has('block_' . $time)){
- if (empty($time)) {
- return _error('时间戳不能为空');
- }
- //https://api.bscscan.com/api?module=block&action=getblocknobytime×tamp=1601510400&closest=before&apikey=YourApiKeyToken
- $url = $this->bsc_api_url . "&module=block&action=getblocknobytime&";
- $url .= "&closest=before";//closest 值还有个参数 after 控制返回接近时间戳之前还是之后的区块高度
- $url .= "&apikey=" . $this->bsc_api_key;
- $url .= "×tamp=" . $time;
- dump($url);
- $body = Http::get($url);
- if (empty($body)) {
- return _error('获取区块高度api返回内容为空');
- }
- // 转成数组
- $rsArr = json_decode($body, true);
- if (empty($rsArr) || !is_array($rsArr)) {
- return _error('获取区块高度api返回数据异常,json转换失败');
- }
- if ($rsArr['status'] != '1') {
- return _error($rsArr['message'] . ' -- ' . $rsArr['result']);
- }
- Cache::set('block_' . $time, _success($rsArr['result']), 3600);
- }
- //获取开始预约前的区块高度
- return Cache::get('block_' . $time);
- }
- public function getBlcokNoByCache($time){
- if(!Cache::has('block')){
- $get_block = (new BscApi())->getBlockNoByTime($start_time);
- if($get_block['code'] == 0){
- Log::info($get_block['msg'] . date('Y-m-d H:i:s'));
- dump('获取区块高度有误');
- dump($get_block);
- return;
- }
- Cache::set('block', $get_block['data'], 3600);
- }
- //获取开始预约前的区块高度
- $start_block = Cache::get('block');
- }
- /**
- * 获取钱包地址的BNB余额
- *
- * api接口返回数据格式
- * {
- "status": "1",
- "message": "OK",
- "result": "4156708818000000"
- * }
- * @param $orderInfo
- * @return array|string
- */
- public function getBnbBalance($address):array
- {
- if (empty($address)) {
- return _error('钱包地址不能为空');
- }
- $url = $this->bsc_api_url . "&module=account&action=balance";
- $url .= "&apikey=" . $this->bsc_api_key;
- $url .= "&address=" . $address;
- $body = Http::get($url);
- if (empty($body)) {
- return _error('状态api返回内容为空');
- }
- // 转成数组
- $rsArr = json_decode($body, true);
- if (empty($rsArr) || !is_array($rsArr)) {
- return _error('状态api返回数据异常,json转换失败');
- }
- if ($rsArr['status'] != '1') {
- return _error('状态api返回status不为1,当前值为:' . $rsArr['status']);
- }
- $amount = bcdiv($rsArr['result'], $this->valueScale, 6);//交易金额
- return _success($amount);
- }
- /**
- * 获取钱包地址的代币余额
- *
- * api接口返回数据格式
- * {
- "status": "1",
- "message": "OK",
- "result": "4156708818000000"
- * }
- * @param $orderInfo
- * @return array|string
- */
- public function getTokenBalance($address, $contract_address = ''):array
- {
- if (empty($address)) {
- return _error('钱包地址不能为空');
- }
- if(empty($contract_address)){
- $contract_address = $this->contract_address;
- }
- $url = $this->bsc_api_url . "&module=account&action=tokenbalance";
- $url .= "&apikey=" . $this->bsc_api_key;
- $url .= "&address=" . $address;
- $url .= "&contractaddress=" . $contract_address;
- $body = Http::get($url);
- if (empty($body)) {
- return _error('状态api返回内容为空');
- }
- // 转成数组
- $rsArr = json_decode($body, true);
- if (empty($rsArr) || !is_array($rsArr)) {
- return _error('状态api返回数据异常,json转换失败');
- }
- if ($rsArr['status'] != '1') {
- return _error('状态api返回status不为1,当前值为:' . $rsArr['status']);
- }
- $amount = bcdiv($rsArr['result'], $this->valueScale, 6);//交易金额
- return _success($amount);
- }
- }
|