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 = '' . $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 = $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);
}
}