// +---------------------------------------------------------------------- namespace App\Services; use App\Models\CapitalLogModel; use App\Models\CoinLogModel; use App\Models\MemberModel; use App\Models\TradeOrderModel; use App\Services\Api\MemberService; use App\Services\Common\CoinLogService; use App\Services\Oapi\TradeOrderService; use BitWasp\Bitcoin\Address\AddressCreator; use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress; use BitWasp\Bitcoin\Bitcoin; use BitWasp\Bitcoin\Crypto\Random\Random; use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory; use BitWasp\Bitcoin\Script\WitnessProgram; use etherscan\api\Etherscan; use GuzzleHttp\Client; use IEXBase\TronAPI\Tron; use Tron\Api; use Tron\TRC20; use Tron\TRX; use Web3p\EthereumTx\Transaction; use Web3p\EthereumUtil\Util; /** * USDT链管理-服务类 * Class UsdtWalletService * @package App\Services */ class UsdtWalletService extends BaseService { protected $apiUrl = ''; protected $config = []; // 静态对象 protected static $instance = null; protected $apiUrls = [ 'usdt_trx2_transfer_log' => '/v1/accounts/%s/transactions/trc20?limit=%s&only_to=%s&only_from=%s&only_confirmed=true&contract_address=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', 'usdt_trx2_transfer_logs' => '/v1/accounts/%s/transactions/trc20?limit=%s&only_to=%s&only_from=%s&contract_address=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', 'erc_balance' => '/api?module=account&action=balance&address=%s&tag=latest&apikey=%s', 'erc20_balance' => '/api?module=account&action=tokenbalance&contractaddress=%s&address=%s&tag=latest&apikey=%s', 'erc_transaction' => '/api?module=proxy&action=eth_sendRawTransaction&hex=%s&apikey=%s', 'erc_transaction_log' => '/api?module=account&action=txlist&address=%s&page=%s&offset=%s&sort=desc&apikey=%s', 'erc20_transaction_log' => '/api?module=account&action=tokentx&address=%s&page=%s&offset=%s&sort=desc&apikey=%s', ]; /** * 构造函数 * UsdtWalletService constructor. */ public function __construct() { $this->memberModel = new MemberModel(); $this->coinModel = new CoinLogModel(); $this->capitalModel = new CapitalLogModel(); $this->config = ConfigService::make()->getConfigOptionByGroup(4); if (empty($this->config)) { return false; } } /** * 静态入口 * @return static|null */ public static function make() { if (!self::$instance) { self::$instance = (new UsdtWalletService()); } return self::$instance; } /** * 获取TRC钱包地址余额 * @throws \Tron\Exceptions\TronErrorException */ public function getAddrUSDTNum($addr=''){ try { if(!$addr) { $this->error('addr is empty'); return false; } $result = file_get_contents($this->config['tron_api_url'].'/v1/accounts/'.$addr); $res = json_decode($result); if(isset($res->success)&&$res->success){ return $res->data[0]->trc20; } else { $this->error('request failed'); return false; } } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * 解析TRC2.0订单收款地址 * @throws \Tron\Exceptions\TronErrorException */ public function getUSDTAddr($str) { try { $str = substr($str,-128,64); $str = '41'.ltrim($str,'0'); $tron = new \IEXBase\TronAPI\Tron(); $addr = $tron->fromHex($str); return $addr; } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * 解析TRC2.0订单USDT数量 * @throws \Tron\Exceptions\TronErrorException */ public function getUSDTNum($str) { try { $str = substr($str,-64); $amount = base_convert($str, 16, 10)/1000000; return $amount; } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * 获取TRC2.0订单详情 * @throws \Tron\Exceptions\TronErrorException */ public function getTrxOrder($txid) { try { $result = $this->TrcRequest('/wallet/gettransactionbyid',[ 'value'=>$txid ]); return $result; } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /* * 随机 tron_api_key 实现接口分流 */ protected function getTrcKey(){ $seed = mt_rand(1,6); switch ($seed) { case 1: return $this->config['tron_api_key']; break; default: $key = 'tron_api_key'.($seed-1); return $this->config[$key]; break; } } /** * 封装TRC2.0请求 */ protected function TrcRequest($url='',$data=[]){ $curl = curl_init(); $key = $this->getTrcKey(); curl_setopt_array($curl, [ CURLOPT_URL => $this->config['tron_api_url'].$url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => [ "TRON-PRO-API-KEY: ".$key, "accept: application/json", "content-type: application/json" ], ]); $response = curl_exec($curl); $err = curl_error($curl); curl_close($curl); if ($err) { echo "cURL Error #:" . $err; } else { $res = json_decode($response); if(isset($res->Error)) { //被限制,想办法记录 // file_put_contents(storage_path('logs/key_limit.txt'), $key.' - '.$res->Error); $msg = [ 'title' => 'OTC168接口调用失败通知', 'description' => '
OTC168接口受限通知

API KEY:'.$key.'
返回信息:'.$res->Error.'
', 'url' => 'http://h5-open.otc168.net/test.html', ]; WxMsgService::make()->sendMsg($msg); return false; } return $res; } } /** * 获取TRC2.0钱包地址 * @throws \Tron\Exceptions\TronErrorException */ public function getTrxAddress() { try { $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], $headers])); $trxWallet = new TRX($api); $addressData = $trxWallet->generateAddress(); $addressData = (array)$addressData; return ['wif' => $addressData['privateKey'], 'hexAddress' => $addressData['hexAddress'], 'address' => $addressData['address']]; } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * trx 转账 * @param $to 进账账户 * @param $amount 转账金额 * @throws \Tron\Exceptions\TransactionException * @throws \Tron\Exceptions\TronErrorException */ public function trxTransfer($to, $amount, $from = '') { if ($amount <= 0) { $this->error = '2205'; return false; } if (empty($this->config['tron_api_url'])) { $this->error = '2206'; return false; } $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers])); $trxWallet = new TRX($api); // 获取钱包参数 try { $otcAddress = ConfigService::make()->getConfigByCode('trc_out_address'); $otcAddressPrivate = ConfigService::make()->getConfigByCode('trc_out_private_key'); if (empty($otcAddress) || empty($otcAddressPrivate)) { $this->error = '2203'; return false; } $tron = new Tron(); // 获取平台钱包hex $tron->setAddress($otcAddress); $otcAddress = $tron->getAddress(); $tron->setAddress($to); $toAddress = $tron->getAddress(); $from = new \Tron\Address($otcAddress['base58'], $otcAddressPrivate, $otcAddress['hex']); $to = new \Tron\Address($toAddress['base58'], '', $toAddress['hex']); $result = $trxWallet->transfer($from, $to, $amount); return $result; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * usdt-trc2.0 转账 * @param $to 进账账户 * @param $amount 转账金额 * @param $from 转账账户 * @param $fromPrivate 转账账户私钥 * @throws \Tron\Exceptions\TransactionException * @throws \Tron\Exceptions\TronErrorException */ public function usdtTrcTransfer($to, $amount, $from = '', $fromPrivate = '') { if ($amount <= 0) { $this->error = '2205'; return false; } if (empty($this->config['tron_api_url'])) { $this->error = '2206'; return false; } $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers])); $trxWallet = new TRC20($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]); // 获取钱包参数 try { // 用出账钱包转账 $otcAddress = $from ? $from : ConfigService::make()->getConfigByCode('trc_out_address'); $otcAddressPrivate = $from ? $fromPrivate : ConfigService::make()->getConfigByCode('trc_out_private_key'); if (empty($otcAddress) || empty($otcAddressPrivate)) { $this->error = '2203'; return false; } //判断够不够钱 $balance = $this->getTrc20Usdt($otcAddress); if(!$balance>=$amount) { $this->error = '2219'; return false; } $tron = new Tron(); // 获取平台钱包hex $tron->setAddress($otcAddress); $otcAddress = $tron->getAddress(); // 获取收款钱包hex $tron->setAddress($to); $toAddress = $tron->getAddress(); RedisService::set("caches:wallet:transfer:temp_{$to}", ['to' => $to, 'out' => $otcAddress, 'outPrivate' => $otcAddressPrivate, 'amount' => $amount], 7200); $from1 = new \Tron\Address($otcAddress['base58'], $otcAddressPrivate, $otcAddress['hex']); $to1 = new \Tron\Address($toAddress['base58'], '', $toAddress['hex']); $result = $trxWallet->transfer($from1, $to1, $amount); $result = (array)$result; // file_put_contents(storage_path('logs/test.txt'), json_encode($result)); // var_dump($result); RedisService::set("caches:wallet:transfer:result_{$to}", ['to' => $to, 'out' => $otcAddress, 'result' => $result], 7200); if (isset($result['txID'])) { return $result; } return false; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; // var_dump($message); RedisService::set("caches:wallet:transfer:error_{$toAddress['base58']}", (array)$exception, 600); return false; } } /** * usdt-trc2.0 归集 * @throws \Tron\Exceptions\TransactionException * @throws \Tron\Exceptions\TronErrorException */ public function usdtTrcTrigger($force = false) { if (empty($this->config['tron_api_url'])) { $this->error = '2206'; return false; } // 获取钱包参数 try { // 用收账钱包归集 $otcAddress = ConfigService::make()->getConfigByCode('trc_address'); $otcAddressPrivate = ConfigService::make()->getConfigByCode('trc_private_key'); // 出账手续费钱包 $otcOutAddress = ConfigService::make()->getConfigByCode('trc_out_address'); $otcOutAddressPrivate = ConfigService::make()->getConfigByCode('trc_out_private_key'); $triggerMin = ConfigService::make()->getConfigByCode('trade_trigger_min'); $triggerTime = ConfigService::make()->getConfigByCode('trade_trigger_time'); $triggerFree = ConfigService::make()->getConfigByCode('trade_trigger_trx_free'); $triggerFree = $triggerFree > 0 ? $triggerFree : 8; $triggerMin = $triggerMin > 0 ? $triggerMin : 0.1; $triggerTime = $triggerTime > 0 ? $triggerTime * 86400 : 86400; if (empty($otcAddress) || empty($otcAddressPrivate)) { $this->error = '2203'; return false; } // 出账钱包 if (empty($otcOutAddress) || empty($otcOutAddressPrivate)) { $this->error = '2203'; return false; } $page = RedisService::get("caches:wallet:transferTrxPage"); $page = $page ? $page : 1; // 归集时间段为凌晨0点-5点 if ((date('H:i') >= '05:00') && !$force) { $this->error = '不在归集时间段'; return false; } if (RedisService::get("caches:wallet:triggerTrxLock:{$page}")) { $this->error = '不要频繁操作,30秒后重试'; return false; } // 上锁 RedisService::set("caches:wallet:triggerTrxLock:{$page}", 1, rand(10, 30)); $addrList = MemberService::make()->getTriggerAddressList($triggerMin, $page, 200); if (empty($addrList)) { RedisService::set("caches:wallet:transferPage", 1, 600); $this->error = '1019'; return false; } // 平台钱包地址 $count = 0; $failedCount = 0; $retryCount = 0; $notBalance = false; $tron = new Tron(); $tron->setAddress($otcAddress); $otcAddress = $tron->getAddress(); $otcAddressData = new \Tron\Address($otcAddress['base58'], $otcAddressPrivate, $otcAddress['hex']); // 平台出账钱包地址 $tron->setAddress($otcOutAddress); $otcOutAddress = $tron->getAddress(); $otcOutAddressData = new \Tron\Address($otcOutAddress['base58'], $otcOutAddressPrivate, $otcOutAddress['hex']); $cacheKey = "caches:wallet:triggerTrx:"; foreach ($addrList as $v) { try { $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers])); $trcWallet = new TRC20($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]); $trxWallet = new TRX($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]); // 获取子钱包TRC-USDT余额 $userId = isset($v['id']) ? $v['id'] : 0; $address = isset($v['trc_address']) ? $v['trc_address'] : ''; $hexAddress = isset($v['trc_hexaddress']) ? $v['trc_hexaddress'] : ''; $addressPrivate = isset($v['trc_wif']) ? $v['trc_wif'] : ''; $triggerAddress = new \Tron\Address($address, $addressPrivate, $hexAddress); // 可归集的USDT余额 $triggerUsdt = $trcWallet->balance($triggerAddress); // USDT 余额低于归集金额,则不归集 if ($triggerUsdt < $triggerMin) { $failedCount++; $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'error' => '用户余额不足归集', 'date' => date('Y-m-d H:i:s')]; RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200); continue; } // 获取子钱包TRX余额 $triggerTrx = $trxWallet->balance($triggerAddress); // 获取平台出账钱包TRX余额 $otcOutTrxTotal = $trxWallet->balance($otcOutAddressData); // 如果子钱包和平台钱包TRX余额不足手续费,则不归集 if ($triggerTrx < $triggerFree && $otcOutTrxTotal < $triggerFree) { $failedCount++; $notBalance = true; $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerTrx' => $triggerTrx, 'otcTrx' => $otcOutTrxTotal, 'error' => '平台钱包手续费不足', 'date' => date('Y-m-d H:i:s')]; RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200); continue; } // 如果子钱包TRX不足手续费8个,平台TRX充足则自动充值后下次调用时归集 if ($triggerTrx < $triggerFree) { $retryCount++; $result = $this->trxTransfer($address, $triggerFree); $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerTrx' => $triggerTrx, 'transfer' => $result, 'error' => '归集钱包手续费不足,先充值', 'date' => date('Y-m-d H:i:s')]; RedisService::set($cacheKey . "U_{$userId}:catch", $error, 7200); continue; } // 满足归集条件处理 $result = $trcWallet->transfer($triggerAddress, $otcAddressData, $triggerUsdt); RedisService::set($cacheKey . "U_{$userId}:result", ['data' => $v, 'result' => $result, 'msg' => '归集已提交', 'date' => date('Y-m-d H:i:s')], 7200); $count++; // usleep(600); } catch (\Exception $exception) { $failedCount++; RedisService::set($cacheKey . "U_{$userId}:exception", ['data' => $v, 'msg' => $exception->getMessage(), 'date' => date('Y-m-d H:i:s')], 7200); } } // 超出分页数,下次处理下一页 RedisService::set($cacheKey."trc_result", ['success' => $count, 'fail' => $failedCount, 'retry' => $retryCount], 7200); if (count($addrList) >= 200) { RedisService::set("caches:wallet:transferPage", $page + 1, 600); } if ($count > 0) { $this->error = lang(2225, ['success' => $count, 'fail' => $failedCount, 'retry' => $retryCount]); return ['success' => $count, 'fail' => $failedCount, 'retryCount' => $retryCount]; } else if ($failedCount == count($addrList)) { $this->error = 2221; return false; } else if ($retryCount) { $this->error = lang(2222, ['num' => $retryCount]); return false; } else if ($notBalance) { $this->error = 2221; return false; } else { $this->error = lang(2224, ['num' => $failedCount]); return false; } } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * 监听USDT-TRC2.0用户子钱包充值存币处理 * @param $userId 用户ID * @param $address 用户钱包地址 * @param int $coinType 币种:1-usdt * @param int $limit 转账记录数,最新的 * @return array|false */ public function getTrc20RechargeLog($userId, $address, $coinType = 1, $limit = 100) { if ($userId <= 0 || empty($address)) { $this->error = '1013'; return false; } $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'true', 'false'); $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; RedisService::set("caches:wallets:trx_recharge_temp_{$userId}", ['url' => $this->config['tron_api_url'] . $url], 600); $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10); $result = $result ? json_decode($result, true) : []; $datas = isset($result['data']) ? $result['data'] : []; if($userId==10078) file_put_contents(storage_path('logs/datas.txt'), json_encode($datas)); $status = isset($result['success']) ? $result['success'] : ''; RedisService::set("caches:wallets:trx_recharge_result_{$userId}", ['url' => $this->config['tron_api_url'] . $url,'result'=>$result,'result'=>$result], 7200); if ($status != true || empty($datas)) { $this->error = '2207'; return false; } $logs = []; $coinInMin = ConfigService::make()->getConfigByCode('trc_in_limit'); $coinInMin = $coinInMin > 0 ? $coinInMin : 0; if ($datas) { foreach ($datas as $v) { $amount = isset($v['value']) ? intval($v['value']) : 0; $amount = moneyFormat($amount / 1000000, 6); $time = isset($v['block_timestamp']) ? intval($v['block_timestamp'] / 1000) : 0; $txid = isset($v['transaction_id']) ? $v['transaction_id'] : ''; if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) { $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num'); $orderNo = get_order_num('TR'); $log = [ 'user_id' => $userId, 'change_type' => 1, 'coin_type' => $coinType, 'contact_type' => 1, 'order_no' => $orderNo, 'from_address' => isset($v['from']) ? $v['from'] : '', 'to_address' => isset($v['to']) ? $v['to'] : '', 'txid' => $txid, 'num' => $amount, 'balance' => $balance, 'create_time' => $time ? $time : time(), 'update_time' => time(), 'status' => 2, 'mark' => 1, ]; if ($amount >= $coinInMin && $this->memberModel->where(['id' => $userId])->increment('usdt_num', $amount)) { $this->memberModel->where(['id' => $userId])->increment('trc_usdt', $amount); $log['status'] = 1; $data = [ 'order_no' => $orderNo, 'user_id' => $userId, 'type' => 4, 'pay_type' => 1, 'trade_type' => 3, 'change_type' => 1, 'num' => $amount, 'total' => 0, 'balance' => floatval($balance + $amount), 'create_time' => time(), 'update_time' => time(), 'remark' => '存币', 'status' => 1, 'mark' => 1, ]; $this->capitalModel->edit($data); } $logs[] = $log; $this->coinModel->insert($log); } } } RedisService::set("caches:wallets:recharge_trx_result_{$userId}", ['url' => $this->config['tron_api_url'] . $url,'result'=> $result,'logs'=> $logs], 600); return $logs; } /** * 获取USDT-TRC2.0交易记录(进出账) * @param $address 用户钱包地址 * @param int $coinType 币种:1-usdt * @param int $limit 转账记录数,最新的 * @return array|false */ public function getTrc20TransferLog($address, $type = 1, $limit = 50) { if (empty($address)) { $this->error = '1013'; return false; } // 存币 if ($type == 1) { $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'true', 'false'); } // 提币 else { $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'false', 'true'); } try { $cacheKey = "caches:wallets:transfer:trc_{$address}"; if (RedisService::get($cacheKey . '_lock')) { return false; } RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10)); $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; RedisService::set("caches:wallets:transfer_temp_{$type}", ['url' => $this->config['tron_api_url'] . $url], 600); $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10); $result = $result ? json_decode($result, true) : []; $datas = isset($result['data']) ? $result['data'] : []; $status = isset($result['success']) ? $result['success'] : ''; if ($status != true || empty($datas)) { $this->error = '2207'; return false; } return $datas; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * 监听USDT-TRC2.0提币记录并进账(平台钱包) * @param $address 用户钱包地址 * @param int $limit 转账记录数,最新的 * @return array|false */ public function getTrc20TransferLogByOtc($address, $tradeType=1, $limit = 50) { if (empty($address)) { $this->error = '1013'; return false; } try { $cacheKey = "caches:wallets:transfer:trc_{$address}"; if (RedisService::get($cacheKey . '_lock')) { return false; } RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10)); $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'false', 'true'); $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; RedisService::set("caches:wallets:transfer_temp_otc", ['url' => $this->config['tron_api_url'] . $url], 600); $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10); $result = $result ? json_decode($result, true) : []; $datas = isset($result['data']) ? $result['data'] : []; $status = isset($result['success']) ? $result['success'] : ''; RedisService::set("caches:wallets:transfer_result_otc", ['url' => $this->config['tron_api_url'] . $url,'result'=>$result], 600); if ($status != true || empty($datas)) { $this->error = '2207'; return false; } $results = [ 'logs' => [], 'trades' => [] ]; $cachekey = "caches:tradeOtc:{$address}_{$tradeType}"; foreach ($datas as $v) { $amount = isset($v['value']) ? intval($v['value']) : 0; $amount = moneyFormat($amount / 1000000, 6); $time = isset($v['block_timestamp']) ? intval($v['block_timestamp'] / 1000) : 0; $txid = isset($v['transaction_id']) ? $v['transaction_id'] : ''; if ($time > time() - 15 * 24 * 3600) { // 有记录,且是用户提币 if($tradeType==1){ $coinInfo = CoinLogService::make()->getCacheInfoByTxid($txid); $userId = isset($coinInfo['user_id']) ? $coinInfo['user_id'] : 0; if ($coinInfo && $userId && $coinInfo['status'] == 4 && $coinInfo['contact_type'] == 1) { // 直接更新提币状态 CoinLogModel::where(['txid' => $txid, 'user_id' => $userId])->update(['from_address' => $v['from'], 'status' => 1, 'update_time' => time()]); // 明细处理 $num = floatval($coinInfo['num'] + $coinInfo['free']); $data = [ 'order_no' => $coinInfo['order_no'], 'user_id' => $userId, 'type' => 5, 'pay_type' => 1, 'trade_type' => 3, 'change_type' => 2, 'num' => $num, 'total' => 0, 'balance' => floatval($coinInfo['balance'] - $num), 'create_time' => time(), 'update_time' => time(), 'remark' => '提币:' . $amount, 'status' => 1, 'mark' => 1, ]; $this->capitalModel->edit($data); $results['trades'][] = ['log' => $v, 'order' => $coinInfo]; } // 其他交易明细 else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) { $results['logs'][] = [ 'type' => 3, 'user_id' => 0, 'from_address' => $v['from'], 'to_address' => $v['to'], 'change_type' => $v['from'] == $address ? 2 : 1, 'coin_type' => 1, 'contact_type' => 1, 'order_no' => get_order_num('TC'), 'txid' => $txid, 'num' => $amount, 'free' => 0, 'balance' => 0, 'create_time' => time(), 'update_time' => time(), 'status' => 1, 'mark' => 1, ]; } } // 外来客户派单监听转入币,并且匹配交易元给客户 else{ RedisService::set($cacheKey.'_temp',['data'=> $v], 7200); $orderInfo = TradeOrderService::make()->getInfoByTradeHash($txid); $userId = isset($orderInfo['user_id']) ? $orderInfo['user_id'] : 0; $apiId = isset($orderInfo['api_id']) ? $orderInfo['api_id'] : 0; $num = isset($orderInfo['num']) ? $orderInfo['num'] : 0; if ($orderInfo && ($userId||$apiId) && $num>0 && ($num == $amount) && $orderInfo['status'] == 0 && $orderInfo['contact_type'] == 1) { // 获取匹配商家 $businessInfo = \App\Services\Api\MemberService::make()->getTradeMember($num, 1, $userId); if (empty($businessInfo)) { RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到交易员'], 7200); continue; } if($businessInfo['usdt_num']<$num){ RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到合适的交易员'], 7200); continue; } if(!TradeOrderModel::where(['txid' => $txid, 'id' => $orderInfo['id']])->update(['business_id' => $businessInfo['id'], 'status' => 1,'create_time'=>time(), 'update_time' => time()])){ RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'business'=> $businessInfo,'info'=> $orderInfo,'error'=>'更新订单匹配交易数据失败'], 7200); continue; } // 记录平台钱包交易明细 $results['logs'][] = [ 'type' => 3, 'user_id' => 0, 'from_address' => $v['from'], 'to_address' => $v['to'], 'change_type' => $v['from'] == $address ? 2 : 1, 'coin_type' => 1, 'contact_type' => 1, 'order_no' => get_order_num('TP'), 'txid' => $txid, 'num' => $amount, 'free' => 0, 'balance' => 0, 'create_time' => time(), 'update_time' => time(), 'status' => 1, 'mark' => 1, ]; $results['trades'][] = ['log' => $v, 'order' => $orderInfo]; } // 其他交易明细 else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) { $results['logs'][] = [ 'type' => 3, 'user_id' => 0, 'from_address' => $v['from'], 'to_address' => $v['to'], 'change_type' => $v['from'] == $address ? 2 : 1, 'coin_type' => 1, 'contact_type' => 1, 'order_no' => get_order_num('TC'), 'txid' => $txid, 'num' => $amount, 'free' => 0, 'balance' => 0, 'create_time' => time(), 'update_time' => time(), 'status' => 1, 'mark' => 1, ]; } } } } if ($results['logs']) { $this->coinModel->insertAll($results['logs']); } return $results; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * 监听USDT-TRC2.0用户子钱包提币处理 * @param $userId 用户ID * @param $address 用户钱包地址 * @param int $coinType 币种:1-usdt * @param int $limit 转账记录数,最新的 * @return array|false */ public function getTrc20TransferLogByUser($userId, $address, $coinType = 1, $limit = 50) { if ($userId <= 0 || empty($address)) { $this->error = '1013'; return false; } $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'false', 'true'); $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; RedisService::set("caches:wallets:transfer_temp_{$userId}", ['url' => $this->config['tron_api_url'] . $url], 600); $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10); $result = $result ? json_decode($result, true) : []; $datas = isset($result['data']) ? $result['data'] : []; $status = isset($result['success']) ? $result['success'] : ''; if ($status != true || empty($datas)) { $this->error = '2207'; return false; } $logs = []; $coinOutMin = ConfigService::make()->getConfigByCode('trc_out_limit'); $coinOutMin = $coinOutMin > 0 ? $coinOutMin : 0; foreach ($datas as $v) { $amount = isset($v['value']) ? intval($v['value']) : 0; $amount = moneyFormat($amount / 1000000, 6); $time = isset($v['block_timestamp']) ? intval($v['block_timestamp'] / 1000) : 0; $txid = isset($v['transaction_id']) ? $v['transaction_id'] : ''; if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) { $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num'); $orderNo = get_order_num('TW'); $log = [ 'user_id' => $userId, 'change_type' => 2, 'coin_type' => $coinType, 'contact_type' => 1, 'order_no' => $orderNo, 'from_address' => isset($v['from']) ? $v['from'] : '', 'to_address' => isset($v['to']) ? $v['to'] : '', 'txid' => $txid, 'num' => $amount, 'balance' => $balance, 'create_time' => $time ? $time : time(), 'update_time' => time(), 'status' => 2, 'mark' => 1, ]; if ($amount >= $coinOutMin && $this->memberModel->where(['id' => $userId])->decrement('usdt_num', $amount)) { $this->memberModel->where(['id' => $userId])->decrement('trc_usdt', $amount); $log['status'] = 1; // 明细处理 $data = [ 'order_no' => $orderNo, 'user_id' => $userId, 'type' => 5, 'pay_type' => 1, 'trade_type' => 3, 'change_type' => 2, 'num' => $amount, 'total' => 0, 'balance' => floatval($balance - $amount), 'create_time' => time(), 'update_time' => time(), 'remark' => '提币', 'status' => 1, 'mark' => 1, ]; $this->capitalModel->edit($data); } $logs[] = $log; $this->coinModel->insert($log); } } return $logs; } /** * TRX余额 * @param $address * @return false|float|string */ public function getTrxBalance($address, $cache = false) { if (empty($address)) { $this->error = '1018'; return false; } if (empty($this->config['tron_api_url'])) { $this->error = '2206'; return false; } $cacheKey = "caches:wallet:balance:trx_{$address}"; if ($data = RedisService::get($cacheKey) && $cache) { return $data; } if (RedisService::get($cacheKey . '_lock') && $cache) { return false; } try { $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers])); $trxWallet = new TRX($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]); $tron = new Tron(); $tron->setAddress($address); $address = $tron->getAddress(); $address = new \Tron\Address($address['base58'], '', $address['hex']); $result = $trxWallet->balance($address); $result = $result ? floatval($result) : '0.00'; RedisService::set($cacheKey, $result, rand(5, 10)); RedisService::set($cacheKey . '_lock', true, rand(10, 20)); return $result; } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * USDT-TRC20余额 * @param $address * @return false|float|string */ public function getTrc20Usdt($address, $cache = false) { if (empty($address)) { $this->error = '1018'; return false; } if (empty($this->config['tron_api_url'])) { $this->error = '2206'; return false; } $cacheKey = "caches:wallet:balance:usdt_{$address}"; if ($data = RedisService::get($cacheKey) && $cache) { return $data; } if (RedisService::get($cacheKey . '_lock') && $cache) { return false; } try { $headers = ["TRON-PRO-API-KEY" => $this->getTrcKey()]; $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers])); $trxWallet = new TRC20($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]); $tron = new Tron(); $tron->setAddress($address); $address = $tron->getAddress(); $address = new \Tron\Address($address['base58'], '', $address['hex']); $result = $trxWallet->balance($address); $result = $result ? floatval($result) : '0.00'; RedisService::set($cacheKey, $result, rand(5, 10)); RedisService::set($cacheKey . '_lock', true, rand(10, 20)); return $result; } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /********************** ERC钱包 **************************/ /** * 获取ERC2.0钱包地址 * @param string $type * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure */ public function getErcAddress() { $random = new Random(); $network = Bitcoin::getNetwork(); $privateKeyFactory = new PrivateKeyFactory(); $privateKey = $privateKeyFactory->generateCompressed($random); $publicKey = $privateKey->getPublicKey(); // p2pkh 格式的地址 $addressService = new PayToPubKeyHashAddress($publicKey->getPubKeyHash()); // 将生成的钱包保存到数据库中 $wif = $privateKey->toWif($network); $hex = $privateKey->getHex(); $address = $addressService->getAddress(); return ['wif' => $wif, 'hexAddress' => $this->getHexAddress($address), 'address' => $address,'hex_private'=>'0x'.$hex]; } /** * 获取HASH钱包地址 * @param $address 钱包地址 * @return \BitWasp\Buffertools\BufferInterface * @throws \BitWasp\Bitcoin\Exceptions\UnrecognizedAddressException */ public function getHexAddress($address) { $data = WitnessProgram::v0((new AddressCreator())->fromString($address)->getHash()); $buffer = $data->getProgram()->getHex(); return $buffer ? '0x' . $buffer : ''; } /** * ERC余额 * @param $address * @return false|float|string */ public function getErcBalance($address, $cache = false) { if (empty($address)) { $this->error = '1018'; return false; } if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } $cacheKey = "caches:wallet:balance:erc_{$address}"; if ($data = RedisService::get($cacheKey) && $cache) { return floatval($data); } if (RedisService::get($cacheKey . '_lock') && $cache) { return false; } try { $url = sprintf($this->apiUrls['erc_balance'],$address, $this->config['eth_api_key']); $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc:balance_{$address}", ['url' => $url, 'config' => $this->config], 600); $result = curl_api($url); $result = $result? json_decode($result, true) : []; $balance = isset($result['result']) ? floatval($result['result']) : 0.00; $balance = $balance>0? floatval($balance / pow(10, 18)) : '0.00'; RedisService::set($cacheKey, $balance, rand(5, 10)); RedisService::set($cacheKey . '_lock', true, rand(10, 20)); return floatval($balance); } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * USDT-ERC20余额 * @param $address * @return false|float|string */ public function getErc20Usdt($address, $cache = false) { if (empty($address)) { $this->error = '1018'; return false; } if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } if (empty($this->config['erc_contract_address'])) { $this->error = '2228'; return false; } $cacheKey = "caches:wallet:balance:erc_usdt_{$address}"; if ($data = RedisService::get($cacheKey) && $cache) { return floatval($data); } if (RedisService::get($cacheKey . '_lock') && $cache) { return false; } try { $url = sprintf($this->apiUrls['erc20_balance'], $this->config['erc_contract_address'],$address, $this->config['eth_api_key']); $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc:balance20_{$address}", ['url' => $url, 'config' => $this->config], 600); $result = curl_api($url); $result = $result? json_decode($result, true) : []; $balance = isset($result['result']) ? floatval($result['result']) : 0.00; $balance = $balance>0? floatval($balance / pow(10, 6)) : '0.00'; RedisService::set($cacheKey, floatval($balance), rand(5, 10)); RedisService::set($cacheKey . '_lock', true, rand(10, 20)); return floatval($balance); } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } /** * ERC 转账 * @param $to * @param $amount * @param string $from * @param string $fromPrivate * @return array|false */ public function ercTransfer($to, $amount, $from = '', $fromPrivate = '') { if ($amount <= 0) { $this->error = '2205'; return false; } if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } // 获取钱包参数 try { $otcAddress = $from ? $from : ConfigService::make()->getConfigByCode('erc_out_address'); $otcAddressPrivate = $from ? $fromPrivate : ConfigService::make()->getConfigByCode('erc_out_private_key'); if (empty($otcAddress) || empty($otcAddressPrivate)) { $this->error = '2228'; return false; } $amount = floatval($amount * pow(10, 18)); $params = [ "from" => $otcAddress, // 来源 "to" => $to, // 收款 "gas" => '0x' . dechex(90000), // gas量,默认90000 "gasPrice" => "0x9184e72a000", "value" => '0x' . dechex($amount), "data" => '' ]; $transaction = new Transaction($params); $transaction->sign($otcAddressPrivate); $hex = '0x' . $transaction; $url = sprintf($this->apiUrls['erc_transaction'], $hex, $this->config['eth_api_key']); // $url = $this->apiUrls['erc_transaction']; $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc:transfer_{$to}", ['url' => $url, 'params' => $params, 'hex' => $hex], 600); $result = curl_api($url); $result = $result ? json_decode($result, true) : []; $tradeHash = isset($result['result']) ? $result['result'] : ''; if ($tradeHash && hexdec($tradeHash) != 0) { return ['txid' => $tradeHash, 'amount' => $amount]; } else { $this->error = '2229'; return false; } } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * usdt-erc2.0 转账 * @param $to 进账账户 * @param $amount 转账金额 * @param $from 转账账户 * @param $fromPrivate 转账账户私钥 * @throws \Tron\Exceptions\TransactionException * @throws \Tron\Exceptions\TronErrorException */ public function usdtErcTransfer($to, $amount, $from = '', $fromPrivate = '') { if ($amount <= 0) { $this->error = '2205'; return false; } if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } // 获取钱包参数 try { // 用出账钱包转账 $otcAddress = $from ? $from : ConfigService::make()->getConfigByCode('erc_out_address'); $otcAddressPrivate = $from ? $fromPrivate : ConfigService::make()->getConfigByCode('erc_out_private_key'); if (empty($otcAddress) || empty($otcAddressPrivate)) { $this->error = '2228'; return false; } $amount = floatval($amount * pow(10, 18)); $sign = $this->getSha3Sign("transfer(address,uint256)"); $params = [ "from" => $otcAddress, // 来源 "to" => $to, // 收款 "gas" => '0x' . dechex(90000), // gas量,默认90000 "gasPrice" => "0x9184e72a000", "value" => '0x' . dechex($amount), "data" => $this->getTransactionSignData($sign, $to, $amount) ]; $transaction = new Transaction($params); $transaction->sign($otcAddressPrivate); // die(var_dump($transaction)); $hex = '0x' . $transaction; // $hex = $transaction->serialize(); $url = sprintf($this->apiUrls['erc_transaction'],$hex, $this->config['eth_api_key']); $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc20:transfer_{$to}", ['url' => $url, 'params' => $params, 'hex' => $hex], 600); $result = curl_api($url); $result = $result ? json_decode($result, true) : []; die(var_dump($result)); $tradeHash = isset($result['result']) ? $result['result'] : ''; $error = isset($result['error']) ? $result['error'] : []; if ($tradeHash && hexdec($tradeHash) != 0) { return ['txID' => $tradeHash, 'amount' => $amount]; } else { $this->error = isset($error['message']) ? $error['message'] : '2229'; return false; } } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * 合约方法签名编号 * @param string $data * @return string */ public function getSha3Sign($data) { $util = new Util(); $result = $util->sha3($data); return '0x' . substr($result, 0, 8); } /** * 获取签名数据 * @param $sign * @param $address * @return string */ public function getSignData($sign, $address) { return $sign . str_repeat('0', 24) . substr($address, 2); } /** * 获取交易签名参数 * @param $sign 调用的合约方法生成的sha3签名编号 * @param $toAddress 收款地址 * @param $amount 转账金额/十进制 * @return string */ public function getTransactionSignData($sign, $toAddress, $amount) { $toAddress = substr($toAddress, 2); // 去掉0x $toAddressSign = str_repeat('0', 64 - strlen($toAddress)) . $toAddress; // 前面补齐0为64位 $amount = dechex($amount); // 十进制转十六进制 $amountSign = str_repeat('0', 64 - strlen($amount)) . $amount; // 前面补齐0为64位 return $sign . $toAddressSign . $amountSign; } /** * 监听USDT-ERC2.0用户子钱包存币处理 * @param $userId 用户ID * @param $address 用户钱包地址 * @param int $coinType 币种:1-usdt * @param int $limit 转账记录数,最新的 * @return array|false */ public function getErc20RechargeLog($userId, $address, $coinType = 1, $limit = 50) { if ($userId <= 0 || empty($address)) { $this->error = '1013'; return false; } if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } try { $cacheKey = "caches:wallets:recharge:erc_{$userId}_{$address}"; if (RedisService::get($cacheKey . '_lock')) { return false; } RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10)); $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, 1, $limit, $this->config['eth_api_key']); $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600); $result = curl_api($url); $result = $result ? json_decode($result, true) : []; $status = isset($result['status']) ? $result['status'] : 0; $datas = isset($result['result']) ? $result['result'] : []; if ($status != 1 || empty($datas)) { $this->error = '2207'; return false; } $logs = []; $coinInMin = ConfigService::make()->getConfigByCode('erc_in_limit'); $coinInMin = $coinInMin > 0 ? $coinInMin : 0; if ($datas) { foreach ($datas as $v) { $amount = isset($v['value']) ? intval($v['value']) : 0; $amount = moneyFormat($amount / 1000000, 6); $time = isset($v['timeStamp']) ? intval($v['timeStamp']) : 0; $txid = isset($v['hash']) ? $v['hash'] : ''; if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) { $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num'); $orderNo = get_order_num('ER'); $log = [ 'type' => 1, 'user_id' => $userId, 'change_type' => 1, 'coin_type' => $coinType, 'contact_type' => 2, 'order_no' => $orderNo, 'from_address' => isset($v['from']) ? $v['from'] : '', 'to_address' => isset($v['to']) ? $v['to'] : '', 'txid' => $txid, 'num' => $amount, 'balance' => $balance, 'create_time' => $time ? $time : time(), 'update_time' => time(), 'status' => 2, 'mark' => 1, ]; if ($amount >= $coinInMin && $this->memberModel->where(['id' => $userId])->increment('usdt_num', $amount)) { $this->memberModel->where(['id' => $userId])->increment('erc_usdt', $amount); $log['status'] = 1; $data = [ 'order_no' => $orderNo, 'user_id' => $userId, 'type' => 4, 'pay_type' => 1, 'trade_type' => 3, 'change_type' => 1, 'num' => $amount, 'total' => 0, 'balance' => floatval($balance + $amount), 'create_time' => time(), 'update_time' => time(), 'remark' => '存币:' . $amount, 'status' => 1, 'mark' => 1, ]; $this->capitalModel->edit($data); } $logs[] = $log; $this->coinModel->insert($log); } } } return $datas; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * 获取USDT-ERC2.0交易记录(进出账) * @param $address 用户钱包地址 * @param int $coinType 币种:1-usdt * @param int $limit 转账记录数,最新的 * @return array|false */ public function getErc20TransferLog($address, $type = 1, $page = 1, $limit = 50) { if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } try { $cacheKey = "caches:wallets:transfer:erc_{$address}"; if (RedisService::get($cacheKey . '_lock')) { return false; } RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10)); $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, $page, $limit, $this->config['eth_api_key']); $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600); $result = curl_api($url); $result = $result ? json_decode($result, true) : []; $status = isset($result['status']) ? $result['status'] : 0; $datas = isset($result['result']) ? $result['result'] : []; if ($status != 1 || empty($datas)) { $this->error = '2207'; return false; } return $datas; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * 监听USDT-ERC2.0提币记录并进账(平台钱包) * @param $address 用户钱包地址 * @param int $limit 转账记录数,最新的 * @return array|false */ public function getErc20TransferLogByOtc($address, $tradeType=1, $limit = 100) { if (empty($address)) { $this->error = '1013'; return false; } if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } try { $cacheKey = "caches:wallets:transfer:erc_{$address}"; if (RedisService::get($cacheKey . '_lock')) { return false; } RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10)); $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, 1, $limit, $this->config['eth_api_key']); $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600); $result = curl_api($url); $result = $result ? json_decode($result, true) : []; $status = isset($result['status']) ? $result['status'] : 0; $datas = isset($result['result']) ? $result['result'] : []; if ($status != 1 || empty($datas)) { $this->error = '2207'; return false; } $results = [ 'logs' => [], 'trades' => [], ]; if ($datas) { $cachekey = "caches:tradeOtc:{$address}_{$tradeType}"; foreach ($datas as $v) { $from = isset($v['from']) ? trim($v['from']) : ''; $to = isset($v['to']) ? trim($v['to']) : ''; $amount = isset($v['value']) ? intval($v['value']) : 0; $decimal = isset($v['tokenDecimal']) ? intval($v['tokenDecimal']) : 0; $decimal = $decimal ? $decimal : 6; $amount = moneyFormat($amount / pow(10, $decimal), 6); $time = isset($v['timeStamp']) ? intval($v['timeStamp']) : 0; $txid = isset($v['hash']) ? $v['hash'] : ''; if($tradeType==1){ if ($time > time() - 15 * 24 * 3600 && $from == $address) { // 有记录,且是用户提币 $coinInfo = CoinLogService::make()->getCacheInfoByTxid($txid); $userId = isset($coinInfo['user_id']) ? $coinInfo['user_id'] : 0; if ($coinInfo && $userId && $coinInfo['status'] == 4 && $coinInfo['contact_type'] == 2) { // 直接更新提币状态 CoinLogModel::where(['txid' => $txid, 'user_id' => $userId])->update(['status' => 1, 'update_time' => time()]); // 明细处理 $num = floatval($coinInfo['num'] + $coinInfo['free']); $data = [ 'order_no' => $coinInfo['order_no'], 'user_id' => $userId, 'type' => 5, 'pay_type' => 1, 'trade_type' => 3, 'change_type' => 2, 'num' => $num, 'total' => 0, 'balance' => floatval($coinInfo['balance'] - $num), 'create_time' => time(), 'update_time' => time(), 'remark' => '提币:' . $amount, 'status' => 1, 'mark' => 1, ]; $this->capitalModel->edit($data); $results['trades'][] = ['log' => $v, 'order' => $coinInfo]; } } // 其他交易明细 else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) { $results['logs'][] = [ 'type' => 3, 'user_id' => 0, 'from_address' => $from, 'to_address' => $to, 'change_type' => $from == $address ? 2 : 1, 'coin_type' => 1, 'contact_type' => 2, 'order_no' => get_order_num('TC'), 'txid' => $txid, 'num' => $amount, 'free' => 0, 'balance' => 0, 'create_time' => time(), 'update_time' => time(), 'status' => 1, 'mark' => 1, ]; } } // 外来客户派单监听转入币,并且匹配交易元给客户 else{ RedisService::set($cacheKey.'_temp',['data'=> $v], 7200); $orderInfo = TradeOrderService::make()->getInfoByTradeHash($txid); $userId = isset($orderInfo['user_id']) ? $orderInfo['user_id'] : 0; $apiId = isset($orderInfo['api_id']) ? $orderInfo['api_id'] : 0; $num = isset($orderInfo['num']) ? $orderInfo['num'] : 0; if ($orderInfo && ($userId||$apiId) && $num>0 && ($num == $amount) && $orderInfo['status'] == 0 && $orderInfo['contact_type'] == 2) { // 获取匹配商家 $businessInfo = \App\Services\Api\MemberService::make()->getTradeMember($num, 1, $userId); if (empty($businessInfo)) { RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到交易员'], 7200); continue; } if($businessInfo['usdt_num']<$num){ RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到合适的交易员'], 7200); continue; } if(!TradeOrderModel::where(['txid' => $txid, 'id' => $orderInfo['id']])->update(['business_id' => $businessInfo['id'], 'status' => 1,'create_time'=>time(), 'update_time' => time()])){ RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'business'=> $businessInfo,'info'=> $orderInfo,'error'=>'更新订单匹配交易数据失败'], 7200); continue; } // 记录平台钱包交易明细 $results['logs'][] = [ 'type' => 3, 'user_id' => 0, 'from_address' => $v['from'], 'to_address' => $v['to'], 'change_type' => $v['from'] == $address ? 2 : 1, 'coin_type' => 1, 'contact_type' => 1, 'order_no' => get_order_num('TP'), 'txid' => $txid, 'num' => $amount, 'free' => 0, 'balance' => 0, 'create_time' => time(), 'update_time' => time(), 'status' => 1, 'mark' => 1, ]; $results['trades'][] = ['log' => $v, 'order' => $orderInfo]; } // 其他交易明细 else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) { $results['logs'][] = [ 'type' => 3, 'user_id' => 0, 'from_address' => $v['from'], 'to_address' => $v['to'], 'change_type' => $v['from'] == $address ? 2 : 1, 'coin_type' => 1, 'contact_type' => 1, 'order_no' => get_order_num('TC'), 'txid' => $txid, 'num' => $amount, 'free' => 0, 'balance' => 0, 'create_time' => time(), 'update_time' => time(), 'status' => 1, 'mark' => 1, ]; } } } } if ($results['logs']) { $this->coinModel->insertAll($results['logs']); } return $results; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * USDT-ERC20 用户子钱包提币处理 * @param $userId * @param $address * @param int $coinType * @param int $limit * @return false */ public function getErc20TransferLogByUser($userId, $address, $coinType = 1, $limit = 50) { if (empty($address)) { $this->error = '1013'; return false; } if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } try { $cacheKey = "caches:wallets:transfer:erc_{$address}"; if (RedisService::get($cacheKey . '_lock')) { return false; } RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10)); $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, 1, $limit, $this->config['eth_api_key']); $url = $this->config['eth_api_url'] . $url; RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600); $result = curl_api($url); $result = $result ? json_decode($result, true) : []; $status = isset($result['status']) ? $result['status'] : 0; $datas = isset($result['result']) ? $result['result'] : []; if ($status != 1 || empty($datas)) { $this->error = '2207'; return false; } $logs = []; $coinOutMin = ConfigService::make()->getConfigByCode('trc_out_limit'); $coinOutMin = $coinOutMin > 0 ? $coinOutMin : 0; if ($datas) { foreach ($datas as $v) { $decimal = isset($v['tokenDecimal']) ? intval($v['tokenDecimal']) : 0; $decimal = $decimal ? $decimal : 6; $amount = isset($v['value']) ? intval($v['value']) : 0; $amount = moneyFormat($amount / pow(10, $decimal), 6); $time = isset($v['timestamp']) ? intval($v['timestamp']) : 0; $txid = isset($v['hash']) ? $v['hash'] : ''; if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) { $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num'); $orderNo = get_order_num('TW'); $log = [ 'user_id' => $userId, 'change_type' => 2, 'coin_type' => $coinType, 'contact_type' => 2, 'order_no' => $orderNo, 'from_address' => isset($v['from']) ? $v['from'] : '', 'to_address' => isset($v['to']) ? $v['to'] : '', 'txid' => $txid, 'num' => $amount, 'balance' => $balance, 'create_time' => $time ? $time : time(), 'update_time' => time(), 'status' => 2, 'mark' => 1, ]; if ($amount >= $coinOutMin && $this->memberModel->where(['id' => $userId])->decrement('usdt_num', $amount)) { $this->memberModel->where(['id' => $userId])->decrement('erc_usdt', $amount); $log['status'] = 1; // 明细处理 $data = [ 'order_no' => $orderNo, 'user_id' => $userId, 'type' => 5, 'pay_type' => 1, 'trade_type' => 3, 'change_type' => 2, 'num' => $amount, 'total' => 0, 'balance' => floatval($balance - $amount), 'create_time' => time(), 'update_time' => time(), 'remark' => '提币:' . $amount, 'status' => 1, 'mark' => 1, ]; $this->capitalModel->edit($data); } $logs[] = $log; $this->coinModel->insert($log); } } } return $logs; } catch (\Exception $exception) { $message = $exception->getMessage(); $this->error = $message; return false; } } /** * usdt-erc2.0 归集 * @throws \Tron\Exceptions\TransactionException * @throws \Tron\Exceptions\TronErrorException */ public function usdtErcTrigger($force = false) { if (empty($this->config['eth_api_url'])) { $this->error = '2206'; return false; } if (empty($this->config['eth_api_key'])) { $this->error = '2207'; return false; } // 获取钱包参数 try { // 用收账钱包归集 $otcAddress = ConfigService::make()->getConfigByCode('erc_address'); $otcAddressPrivate = ConfigService::make()->getConfigByCode('erc_private_key'); // 出账手续费钱包 $otcOutAddress = ConfigService::make()->getConfigByCode('erc_out_address'); $otcOutAddressPrivate = ConfigService::make()->getConfigByCode('erc_out_private_key'); $triggerMin = ConfigService::make()->getConfigByCode('trade_trigger_min'); $triggerTime = ConfigService::make()->getConfigByCode('trade_trigger_time'); $triggerFree = ConfigService::make()->getConfigByCode('trade_trigger_eth_free'); $triggerFree = $triggerFree > 0 ? $triggerFree : (5/pow(10, 8)); $triggerMin = $triggerMin > 0 ? $triggerMin : 0.1; $triggerTime = $triggerTime > 0 ? $triggerTime * 86400 : 86400; if (empty($otcAddress) || empty($otcAddressPrivate)) { $this->error = '2203'; return false; } // 出账钱包 if (empty($otcOutAddress) || empty($otcOutAddressPrivate)) { $this->error = '2203'; return false; } $page = RedisService::get("caches:wallet:transferRecPage"); $page = $page ? $page : 1; // 归集时间段为凌晨0点-5点 if ((date('H:i') >= '05:00') && !$force) { //$this->error = '不在归集时间段'; //return false; } if (RedisService::get("caches:wallet:triggerErcLock:{$page}")) { $this->error = '不要频繁操作,30秒后重试'; return false; } // 上锁 RedisService::set("caches:wallet:triggerErcLock:{$page}", 1, rand(10, 30)); $addrList = MemberService::make()->getTriggerAddressList($triggerMin, $page, 200); if (empty($addrList)) { RedisService::set("caches:wallet:transferErcPage", 1, 600); $this->error = '1019'; return false; } // 平台钱包地址 $count = 0; $failedCount = 0; $api = new Etherscan($this->config['eth_api_key']); $cacheKey = "caches:wallet:triggerErc:"; foreach ($addrList as $v) { try { // 获取子钱包TRC-USDT余额 $userId = isset($v['id']) ? $v['id'] : 0; $address = isset($v['trc_address']) ? $v['trc_address'] : ''; $triggerAddress = isset($v['trc_hexaddress']) ? $v['trc_hexaddress'] : ''; $addressPrivate = isset($v['trc_wif']) ? $v['trc_wif'] : ''; // 可归集的USDT余额 $result = $api->balance($triggerAddress); $triggerUsdt = isset($result['result']) ? floatval($result['result']) : 0.00; $triggerUsdt = $triggerUsdt ? floatval($triggerUsdt / pow(10, 6)) : '0.00'; // USDT 余额低于归集金额,则不归集 if ($triggerUsdt < $triggerMin) { $failedCount++; $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'error' => '用户余额不足归集', 'date' => date('Y-m-d H:i:s')]; RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200); continue; } // 获取子钱包Eth余额 $result = $api->balance($triggerAddress); $triggerEth = isset($result['result']) ? floatval($result['result']) : 0.00; $triggerEth = $triggerEth ? floatval($triggerEth / pow(10, 18)) : '0.00'; // 获取平台出账钱包ETH余额 $result = $api->balance($otcOutAddress); $otcOutEthTotal = isset($result['result']) ? floatval($result['result']) : 0.00; $otcOutEthTotal = $otcOutEthTotal ? floatval($otcOutEthTotal / pow(10, 18)) : '0.00'; // 如果子钱包和平台钱包TRX余额不足手续费,则不归集 if ($triggerEth < $triggerFree && $otcOutEthTotal < $triggerFree) { $failedCount++; $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerTrx' => $triggerEth, 'otcTrx' => $otcOutEthTotal, 'error' => '平台钱包ETH手续费不足', 'date' => date('Y-m-d H:i:s')]; RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200); continue; } // 如果子钱包ETH不足手续费5个gwei,平台ETH充足则自动充值后下次调用时归集 if ($triggerEth < $triggerFree) { $failedCount++; $result = $this->ercTransfer($address, $triggerFree); $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerEth' => $triggerEth, 'transfer' => $result, 'error' => '归集钱包ETH手续费不足,先充值', 'date' => date('Y-m-d H:i:s')]; RedisService::set($cacheKey . "U_{$userId}:catch", $error, 7200); continue; } // 满足归集条件处理 $result = $this->usdtErcTransfer($otcOutAddress, $triggerUsdt, $triggerAddress, $addressPrivate); RedisService::set($cacheKey . "U_{$userId}:result", ['data' => $v, 'result' => $result, 'msg' => '归集已提交', 'date' => date('Y-m-d H:i:s')], 7200); $count++; } catch (\Exception $exception) { $failedCount++; RedisService::set($cacheKey . "U_{$userId}:exception", ['data' => $v, 'msg' => $exception->getMessage(), 'date' => date('Y-m-d H:i:s')], 7200); } } // 超出分页数,下次处理下一页 if (count($addrList) >= 200) { RedisService::set("caches:wallet:transferErcPage", $page + 1, 600); } if ($count > 0) { return ['success' => $count, 'fail' => $failedCount]; } else { $this->error = 1021; return false; } } catch (\Exception $exception) { $this->error = $exception->getMessage(); return false; } } }