bsc_api_key = $key; } $this->contract_address = Env::get('rental.contract_address'); if(empty($this->contract_address)){ $this->contract_address = '0x55d398326f99059ff775485246999027b3197955'; } } /** * 根据哈希查看交易详情 * * 此接口为抓取币安浏览器页面html代码而来 * 访问地址:https://bscscan.com/tx/0x48351c4c215645f2404741343c9be17b0ce494d70a7dba1bf11d1f5c2311de57 */ public function getInfoByTransactionHash($tx_hash = '0x48351c4c215645f2404741343c9be17b0ce494d70a7dba1bf11d1f5c2311de57') { if(empty($tx_hash)){ return _error('hash不能为空'); } // 读取远程页面地址 $url = "https://bscscan.com/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 = '' . $this->contract_address . ''; //$get_html_value = 'BSC-USD'; $get_html_value = ''; 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 = "https://api.bscscan.com/api?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 ($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 = "https://api.bscscan.com/api?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 = "https://api.bscscan.com/api?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 = "https://api.bscscan.com/api?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 = "https://api.bscscan.com/api?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); } }