UsdtWalletService.php 79 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2017~2021 LARAVEL研发中心
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://www.laravel.cn
  8. // +----------------------------------------------------------------------
  9. // | Author: laravel开发员 <laravel.qq.com>
  10. // +----------------------------------------------------------------------
  11. namespace App\Services;
  12. use App\Models\CapitalLogModel;
  13. use App\Models\CoinLogModel;
  14. use App\Models\MemberModel;
  15. use App\Models\TradeOrderModel;
  16. use App\Services\Api\MemberService;
  17. use App\Services\Common\CoinLogService;
  18. use App\Services\Oapi\TradeOrderService;
  19. use BitWasp\Bitcoin\Address\AddressCreator;
  20. use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;
  21. use BitWasp\Bitcoin\Bitcoin;
  22. use BitWasp\Bitcoin\Crypto\Random\Random;
  23. use BitWasp\Bitcoin\Key\Factory\HierarchicalKeyFactory;
  24. use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory;
  25. use BitWasp\Bitcoin\Mnemonic\Bip39\Bip39Mnemonic;
  26. use BitWasp\Bitcoin\Mnemonic\Bip39\Bip39SeedGenerator;
  27. use BitWasp\Bitcoin\Mnemonic\MnemonicFactory;
  28. use BitWasp\Bitcoin\Network\NetworkFactory;
  29. use BitWasp\Bitcoin\Script\WitnessProgram;
  30. use etherscan\api\Etherscan;
  31. use GuzzleHttp\Client;
  32. use IEXBase\TronAPI\Tron;
  33. use Tron\Api;
  34. use Tron\TRC20;
  35. use Tron\TRX;
  36. use Web3p\EthereumTx\Transaction;
  37. use Web3p\EthereumUtil\Util;
  38. /**
  39. * USDT链管理-服务类
  40. * Class UsdtWalletService
  41. * @package App\Services
  42. */
  43. class UsdtWalletService extends BaseService
  44. {
  45. protected $apiUrl = '';
  46. protected $config = [];
  47. // 静态对象
  48. protected static $instance = null;
  49. protected $apiUrls = [
  50. 'usdt_trx2_transfer_log' => '/v1/accounts/%s/transactions/trc20?limit=%s&only_to=%s&only_from=%s&only_confirmed=true&contract_address=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
  51. 'usdt_trx2_transfer_logs' => '/v1/accounts/%s/transactions/trc20?limit=%s&only_to=%s&only_from=%s&contract_address=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
  52. 'erc_balance' => '/api?module=account&action=balance&address=%s&tag=latest&apikey=%s',
  53. 'erc20_balance' => '/api?module=account&action=tokenbalance&contractaddress=%s&address=%s&tag=latest&apikey=%s',
  54. 'erc_transaction' => '/api?module=proxy&action=eth_sendRawTransaction&hex=%s&apikey=%s',
  55. 'erc_transaction_log' => '/api?module=account&action=txlist&address=%s&page=%s&offset=%s&sort=desc&apikey=%s',
  56. 'erc20_transaction_log' => '/api?module=account&action=tokentx&address=%s&page=%s&offset=%s&sort=desc&apikey=%s',
  57. ];
  58. /**
  59. * 构造函数
  60. * UsdtWalletService constructor.
  61. */
  62. public function __construct()
  63. {
  64. $this->memberModel = new MemberModel();
  65. $this->coinModel = new CoinLogModel();
  66. $this->capitalModel = new CapitalLogModel();
  67. $this->config = ConfigService::make()->getConfigOptionByGroup(4);
  68. if (empty($this->config)) {
  69. return false;
  70. }
  71. }
  72. /**
  73. * 静态入口
  74. * @return static|null
  75. */
  76. public static function make()
  77. {
  78. if (!self::$instance) {
  79. self::$instance = (new UsdtWalletService());
  80. }
  81. return self::$instance;
  82. }
  83. /**
  84. * 获取TRC2.0钱包地址
  85. * @throws \Tron\Exceptions\TronErrorException
  86. */
  87. public function getTrxAddress()
  88. {
  89. try {
  90. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  91. $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], $headers]));
  92. $trxWallet = new TRX($api);
  93. $addressData = $trxWallet->generateAddress();
  94. $addressData = (array)$addressData;
  95. return ['wif' => $addressData['privateKey'], 'hexAddress' => $addressData['hexAddress'], 'address' => $addressData['address']];
  96. } catch (\Exception $exception) {
  97. $this->error = $exception->getMessage();
  98. return false;
  99. }
  100. }
  101. /**
  102. * trx 转账
  103. * @param $to 进账账户
  104. * @param $amount 转账金额
  105. * @throws \Tron\Exceptions\TransactionException
  106. * @throws \Tron\Exceptions\TronErrorException
  107. */
  108. public function trxTransfer($to, $amount, $from = '')
  109. {
  110. if ($amount <= 0) {
  111. $this->error = '2205';
  112. return false;
  113. }
  114. if (empty($this->config['tron_api_url'])) {
  115. $this->error = '2206';
  116. return false;
  117. }
  118. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  119. $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers]));
  120. $trxWallet = new TRX($api);
  121. // 获取钱包参数
  122. try {
  123. $otcAddress = ConfigService::make()->getConfigByCode('trc_out_address');
  124. $otcAddressPrivate = ConfigService::make()->getConfigByCode('trc_out_private_key');
  125. if (empty($otcAddress) || empty($otcAddressPrivate)) {
  126. $this->error = '2203';
  127. return false;
  128. }
  129. $tron = new Tron();
  130. // 获取平台钱包hex
  131. $tron->setAddress($otcAddress);
  132. $otcAddress = $tron->getAddress();
  133. $tron->setAddress($to);
  134. $toAddress = $tron->getAddress();
  135. $from = new \Tron\Address($otcAddress['base58'], $otcAddressPrivate, $otcAddress['hex']);
  136. $to = new \Tron\Address($toAddress['base58'], '', $toAddress['hex']);
  137. $result = $trxWallet->transfer($from, $to, $amount);
  138. return $result;
  139. } catch (\Exception $exception) {
  140. $message = $exception->getMessage();
  141. $this->error = $message;
  142. return false;
  143. }
  144. }
  145. /**
  146. * usdt-trc2.0 转账
  147. * @param $to 进账账户
  148. * @param $amount 转账金额
  149. * @param $from 转账账户
  150. * @param $fromPrivate 转账账户私钥
  151. * @throws \Tron\Exceptions\TransactionException
  152. * @throws \Tron\Exceptions\TronErrorException
  153. */
  154. public function usdtTrcTransfer($to, $amount, $from = '', $fromPrivate = '')
  155. {
  156. if ($amount <= 0) {
  157. $this->error = '2205';
  158. return false;
  159. }
  160. if (empty($this->config['tron_api_url'])) {
  161. $this->error = '2206';
  162. return false;
  163. }
  164. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  165. $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers]));
  166. $trxWallet = new TRC20($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]);
  167. // 获取钱包参数
  168. try {
  169. // 用出账钱包转账
  170. $otcAddress = $from ? $from : ConfigService::make()->getConfigByCode('trc_out_address');
  171. $otcAddressPrivate = $from ? $fromPrivate : ConfigService::make()->getConfigByCode('trc_out_private_key');
  172. if (empty($otcAddress) || empty($otcAddressPrivate)) {
  173. $this->error = '2203';
  174. return false;
  175. }
  176. $tron = new Tron();
  177. // 获取平台钱包hex
  178. $tron->setAddress($otcAddress);
  179. $otcAddress = $tron->getAddress();
  180. // 获取收款钱包hex
  181. $tron->setAddress($to);
  182. $toAddress = $tron->getAddress();
  183. RedisService::set("caches:wallet:transfer:temp_{$to}", ['to' => $to, 'out' => $otcAddress, 'outPrivate' => $otcAddressPrivate, 'amount' => $amount], 7200);
  184. $from = new \Tron\Address($otcAddress['base58'], $otcAddressPrivate, $otcAddress['hex']);
  185. $to = new \Tron\Address($toAddress['base58'], '', $toAddress['hex']);
  186. $result = $trxWallet->transfer($from, $to, $amount);
  187. $result = (array)$result;
  188. RedisService::set("caches:wallet:transfer:result_{$to}", ['to' => $to, 'out' => $otcAddress, 'result' => $result], 7200);
  189. if (isset($result['txID'])) {
  190. return $result;
  191. }
  192. return false;
  193. } catch (\Exception $exception) {
  194. $message = $exception->getMessage();
  195. $this->error = $message;
  196. RedisService::set("caches:wallet:transfer:error_{$toAddress['base58']}", (array)$exception, 600);
  197. return false;
  198. }
  199. }
  200. /**
  201. * usdt-trc2.0 归集
  202. * @throws \Tron\Exceptions\TransactionException
  203. * @throws \Tron\Exceptions\TronErrorException
  204. */
  205. public function usdtTrcTrigger($force = false)
  206. {
  207. if (empty($this->config['tron_api_url'])) {
  208. $this->error = '2206';
  209. return false;
  210. }
  211. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  212. $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers]));
  213. $trcWallet = new TRC20($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]);
  214. $trxWallet = new TRX($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]);
  215. // 获取钱包参数
  216. try {
  217. // 用收账钱包归集
  218. $otcAddress = ConfigService::make()->getConfigByCode('trc_address');
  219. $otcAddressPrivate = ConfigService::make()->getConfigByCode('trc_private_key');
  220. // 出账手续费钱包
  221. $otcOutAddress = ConfigService::make()->getConfigByCode('trc_out_address');
  222. $otcOutAddressPrivate = ConfigService::make()->getConfigByCode('trc_out_private_key');
  223. $triggerMin = ConfigService::make()->getConfigByCode('trade_trigger_min');
  224. $triggerTime = ConfigService::make()->getConfigByCode('trade_trigger_time');
  225. $triggerFree = ConfigService::make()->getConfigByCode('trade_trigger_trx_free');
  226. $triggerFree = $triggerFree > 0 ? $triggerFree : 8;
  227. $triggerMin = $triggerMin > 0 ? $triggerMin : 0.1;
  228. $triggerTime = $triggerTime > 0 ? $triggerTime * 86400 : 86400;
  229. if (empty($otcAddress) || empty($otcAddressPrivate)) {
  230. $this->error = '2203';
  231. return false;
  232. }
  233. // 出账钱包
  234. if (empty($otcOutAddress) || empty($otcOutAddressPrivate)) {
  235. $this->error = '2203';
  236. return false;
  237. }
  238. $page = RedisService::get("caches:wallet:transferTrxPage");
  239. $page = $page ? $page : 1;
  240. // 归集时间段为凌晨0点-5点
  241. if ((date('H:i') >= '05:00') && !$force) {
  242. $this->error = '不在归集时间段';
  243. return false;
  244. }
  245. if (RedisService::get("caches:wallet:triggerTrxLock:{$page}")) {
  246. $this->error = '不要频繁操作,30秒后重试';
  247. return false;
  248. }
  249. // 上锁
  250. RedisService::set("caches:wallet:triggerTrxLock:{$page}", 1, rand(10, 30));
  251. $addrList = MemberService::make()->getTriggerAddressList($triggerMin, $page, 200);
  252. if (empty($addrList)) {
  253. RedisService::set("caches:wallet:transferPage", 1, 600);
  254. $this->error = '1019';
  255. return false;
  256. }
  257. // 平台钱包地址
  258. $count = 0;
  259. $failedCount = 0;
  260. $retryCount = 0;
  261. $notBalance = false;
  262. $tron = new Tron();
  263. $tron->setAddress($otcAddress);
  264. $otcAddress = $tron->getAddress();
  265. $otcAddressData = new \Tron\Address($otcAddress['base58'], $otcAddressPrivate, $otcAddress['hex']);
  266. // 平台出账钱包地址
  267. $tron->setAddress($otcOutAddress);
  268. $otcOutAddress = $tron->getAddress();
  269. $otcOutAddressData = new \Tron\Address($otcOutAddress['base58'], $otcOutAddressPrivate, $otcOutAddress['hex']);
  270. $cacheKey = "caches:wallet:triggerTrx:";
  271. foreach ($addrList as $v) {
  272. try {
  273. // 获取子钱包TRC-USDT余额
  274. $userId = isset($v['id']) ? $v['id'] : 0;
  275. $address = isset($v['trc_address']) ? $v['trc_address'] : '';
  276. $hexAddress = isset($v['trc_hexaddress']) ? $v['trc_hexaddress'] : '';
  277. $addressPrivate = isset($v['trc_wif']) ? $v['trc_wif'] : '';
  278. $triggerAddress = new \Tron\Address($address, $addressPrivate, $hexAddress);
  279. // 可归集的USDT余额
  280. $triggerUsdt = $trcWallet->balance($triggerAddress);
  281. // USDT 余额低于归集金额,则不归集
  282. if ($triggerUsdt < $triggerMin) {
  283. $failedCount++;
  284. $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'error' => '用户余额不足归集', 'date' => date('Y-m-d H:i:s')];
  285. RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200);
  286. continue;
  287. }
  288. // 获取子钱包TRX余额
  289. $triggerTrx = $trxWallet->balance($triggerAddress);
  290. // 获取平台出账钱包TRX余额
  291. $otcOutTrxTotal = $trxWallet->balance($otcOutAddressData);
  292. // 如果子钱包和平台钱包TRX余额不足手续费,则不归集
  293. if ($triggerTrx < $triggerFree && $otcOutTrxTotal < $triggerFree) {
  294. $failedCount++;
  295. $notBalance = true;
  296. $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerTrx' => $triggerTrx, 'otcTrx' => $otcOutTrxTotal, 'error' => '平台钱包手续费不足', 'date' => date('Y-m-d H:i:s')];
  297. RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200);
  298. continue;
  299. }
  300. // 如果子钱包TRX不足手续费8个,平台TRX充足则自动充值后下次调用时归集
  301. if ($triggerTrx < $triggerFree) {
  302. $retryCount++;
  303. $result = $this->trxTransfer($address, $triggerFree);
  304. $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerTrx' => $triggerTrx, 'transfer' => $result, 'error' => '归集钱包手续费不足,先充值', 'date' => date('Y-m-d H:i:s')];
  305. RedisService::set($cacheKey . "U_{$userId}:catch", $error, 7200);
  306. continue;
  307. }
  308. // 满足归集条件处理
  309. $result = $trcWallet->transfer($triggerAddress, $otcAddressData, $triggerUsdt);
  310. RedisService::set($cacheKey . "U_{$userId}:result", ['data' => $v, 'result' => $result, 'msg' => '归集已提交', 'date' => date('Y-m-d H:i:s')], 7200);
  311. $count++;
  312. } catch (\Exception $exception) {
  313. $failedCount++;
  314. RedisService::set($cacheKey . "U_{$userId}:exception", ['data' => $v, 'msg' => $exception->getMessage(), 'date' => date('Y-m-d H:i:s')], 7200);
  315. }
  316. }
  317. // 超出分页数,下次处理下一页
  318. RedisService::set($cacheKey."trc_result", ['success' => $count, 'fail' => $failedCount, 'retry' => $retryCount], 7200);
  319. if (count($addrList) >= 200) {
  320. RedisService::set("caches:wallet:transferPage", $page + 1, 600);
  321. }
  322. if ($count > 0) {
  323. $this->error = lang(2225, ['success' => $count, 'fail' => $failedCount, 'retry' => $retryCount]);
  324. return ['success' => $count, 'fail' => $failedCount, 'retryCount' => $retryCount];
  325. } else if ($failedCount == count($addrList)) {
  326. $this->error = 2221;
  327. return false;
  328. } else if ($retryCount) {
  329. $this->error = lang(2222, ['num' => $retryCount]);
  330. return false;
  331. } else if ($notBalance) {
  332. $this->error = 2221;
  333. return false;
  334. } else {
  335. $this->error = lang(2224, ['num' => $failedCount]);
  336. return false;
  337. }
  338. } catch (\Exception $exception) {
  339. $this->error = $exception->getMessage();
  340. return false;
  341. }
  342. }
  343. /**
  344. * 监听USDT-TRC2.0用户子钱包充值存币处理
  345. * @param $userId 用户ID
  346. * @param $address 用户钱包地址
  347. * @param int $coinType 币种:1-usdt
  348. * @param int $limit 转账记录数,最新的
  349. * @return array|false
  350. */
  351. public function getTrc20RechargeLog($userId, $address, $coinType = 1, $limit = 100)
  352. {
  353. if ($userId <= 0 || empty($address)) {
  354. $this->error = '1013';
  355. return false;
  356. }
  357. $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'true', 'false');
  358. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  359. RedisService::set("caches:wallets:trx_recharge_temp_{$userId}", ['url' => $this->config['tron_api_url'] . $url], 600);
  360. $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10);
  361. $result = $result ? json_decode($result, true) : [];
  362. $datas = isset($result['data']) ? $result['data'] : [];
  363. $status = isset($result['success']) ? $result['success'] : '';
  364. RedisService::set("caches:wallets:trx_recharge_result_{$userId}", ['url' => $this->config['tron_api_url'] . $url,'result'=>$result,'result'=>$result], 7200);
  365. if ($status != true || empty($datas)) {
  366. $this->error = '2207';
  367. return false;
  368. }
  369. $logs = [];
  370. $coinInMin = ConfigService::make()->getConfigByCode('trc_in_limit');
  371. $coinInMin = $coinInMin > 0 ? $coinInMin : 0;
  372. if ($datas) {
  373. foreach ($datas as $v) {
  374. $amount = isset($v['value']) ? intval($v['value']) : 0;
  375. $amount = moneyFormat($amount / 1000000, 6);
  376. $time = isset($v['block_timestamp']) ? intval($v['block_timestamp'] / 1000) : 0;
  377. $txid = isset($v['transaction_id']) ? $v['transaction_id'] : '';
  378. if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) {
  379. $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num');
  380. $orderNo = get_order_num('TR');
  381. $log = [
  382. 'user_id' => $userId,
  383. 'change_type' => 1,
  384. 'coin_type' => $coinType,
  385. 'contact_type' => 1,
  386. 'order_no' => $orderNo,
  387. 'from_address' => isset($v['from']) ? $v['from'] : '',
  388. 'to_address' => isset($v['to']) ? $v['to'] : '',
  389. 'txid' => $txid,
  390. 'num' => $amount,
  391. 'balance' => $balance,
  392. 'create_time' => $time ? $time : time(),
  393. 'update_time' => time(),
  394. 'status' => 2,
  395. 'mark' => 1,
  396. ];
  397. if ($amount >= $coinInMin && $this->memberModel->where(['id' => $userId])->increment('usdt_num', $amount)) {
  398. $this->memberModel->where(['id' => $userId])->increment('trc_usdt', $amount);
  399. $log['status'] = 1;
  400. $data = [
  401. 'order_no' => $orderNo,
  402. 'user_id' => $userId,
  403. 'type' => 4,
  404. 'pay_type' => 1,
  405. 'trade_type' => 3,
  406. 'change_type' => 1,
  407. 'num' => $amount,
  408. 'total' => 0,
  409. 'balance' => floatval($balance + $amount),
  410. 'create_time' => time(),
  411. 'update_time' => time(),
  412. 'remark' => '存币',
  413. 'status' => 1,
  414. 'mark' => 1,
  415. ];
  416. $this->capitalModel->edit($data);
  417. }
  418. $logs[] = $log;
  419. $this->coinModel->insert($log);
  420. }
  421. }
  422. }
  423. RedisService::set("caches:wallets:recharge_trx_result_{$userId}", ['url' => $this->config['tron_api_url'] . $url,'result'=> $result,'logs'=> $logs], 600);
  424. return $logs;
  425. }
  426. /**
  427. * 获取USDT-TRC2.0交易记录(进出账)
  428. * @param $address 用户钱包地址
  429. * @param int $coinType 币种:1-usdt
  430. * @param int $limit 转账记录数,最新的
  431. * @return array|false
  432. */
  433. public function getTrc20TransferLog($address, $type = 1, $limit = 50)
  434. {
  435. if (empty($address)) {
  436. $this->error = '1013';
  437. return false;
  438. }
  439. // 存币
  440. if ($type == 1) {
  441. $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'true', 'false');
  442. } // 提币
  443. else {
  444. $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'false', 'true');
  445. }
  446. try {
  447. $cacheKey = "caches:wallets:transfer:trc_{$address}";
  448. if (RedisService::get($cacheKey . '_lock')) {
  449. return false;
  450. }
  451. RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10));
  452. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  453. RedisService::set("caches:wallets:transfer_temp_{$type}", ['url' => $this->config['tron_api_url'] . $url], 600);
  454. $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10);
  455. $result = $result ? json_decode($result, true) : [];
  456. $datas = isset($result['data']) ? $result['data'] : [];
  457. $status = isset($result['success']) ? $result['success'] : '';
  458. if ($status != true || empty($datas)) {
  459. $this->error = '2207';
  460. return false;
  461. }
  462. return $datas;
  463. } catch (\Exception $exception) {
  464. $message = $exception->getMessage();
  465. $this->error = $message;
  466. return false;
  467. }
  468. }
  469. /**
  470. * 监听USDT-TRC2.0提币记录并进账(平台钱包)
  471. * @param $address 用户钱包地址
  472. * @param int $limit 转账记录数,最新的
  473. * @return array|false
  474. */
  475. public function getTrc20TransferLogByOtc($address, $tradeType=1, $limit = 50)
  476. {
  477. if (empty($address)) {
  478. $this->error = '1013';
  479. return false;
  480. }
  481. try {
  482. $cacheKey = "caches:wallets:transfer:trc_{$address}";
  483. if (RedisService::get($cacheKey . '_lock')) {
  484. return false;
  485. }
  486. RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10));
  487. $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'false', 'true');
  488. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  489. RedisService::set("caches:wallets:transfer_temp_otc", ['url' => $this->config['tron_api_url'] . $url], 600);
  490. $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10);
  491. $result = $result ? json_decode($result, true) : [];
  492. $datas = isset($result['data']) ? $result['data'] : [];
  493. $status = isset($result['success']) ? $result['success'] : '';
  494. RedisService::set("caches:wallets:transfer_result_otc", ['url' => $this->config['tron_api_url'] . $url,'result'=>$result], 600);
  495. if ($status != true || empty($datas)) {
  496. $this->error = '2207';
  497. return false;
  498. }
  499. $results = [
  500. 'logs' => [],
  501. 'trades' => []
  502. ];
  503. $cachekey = "caches:tradeOtc:{$address}_{$tradeType}";
  504. foreach ($datas as $v) {
  505. $amount = isset($v['value']) ? intval($v['value']) : 0;
  506. $amount = moneyFormat($amount / 1000000, 6);
  507. $time = isset($v['block_timestamp']) ? intval($v['block_timestamp'] / 1000) : 0;
  508. $txid = isset($v['transaction_id']) ? $v['transaction_id'] : '';
  509. if ($time > time() - 15 * 24 * 3600) {
  510. // 有记录,且是用户提币
  511. if($tradeType==1){
  512. $coinInfo = CoinLogService::make()->getCacheInfoByTxid($txid);
  513. $userId = isset($coinInfo['user_id']) ? $coinInfo['user_id'] : 0;
  514. if ($coinInfo && $userId && $coinInfo['status'] == 4 && $coinInfo['contact_type'] == 1) {
  515. // 直接更新提币状态
  516. CoinLogModel::where(['txid' => $txid, 'user_id' => $userId])->update(['from_address' => $v['from'], 'status' => 1, 'update_time' => time()]);
  517. // 明细处理
  518. $num = floatval($coinInfo['num'] + $coinInfo['free']);
  519. $data = [
  520. 'order_no' => $coinInfo['order_no'],
  521. 'user_id' => $userId,
  522. 'type' => 5,
  523. 'pay_type' => 1,
  524. 'trade_type' => 3,
  525. 'change_type' => 2,
  526. 'num' => $num,
  527. 'total' => 0,
  528. 'balance' => floatval($coinInfo['balance'] - $num),
  529. 'create_time' => time(),
  530. 'update_time' => time(),
  531. 'remark' => '提币:' . $amount,
  532. 'status' => 1,
  533. 'mark' => 1,
  534. ];
  535. $this->capitalModel->edit($data);
  536. $results['trades'][] = ['log' => $v, 'order' => $coinInfo];
  537. } // 其他交易明细
  538. else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) {
  539. $results['logs'][] = [
  540. 'type' => 3,
  541. 'user_id' => 0,
  542. 'from_address' => $v['from'],
  543. 'to_address' => $v['to'],
  544. 'change_type' => $v['from'] == $address ? 2 : 1,
  545. 'coin_type' => 1,
  546. 'contact_type' => 1,
  547. 'order_no' => get_order_num('TC'),
  548. 'txid' => $txid,
  549. 'num' => $amount,
  550. 'free' => 0,
  551. 'balance' => 0,
  552. 'create_time' => time(),
  553. 'update_time' => time(),
  554. 'status' => 1,
  555. 'mark' => 1,
  556. ];
  557. }
  558. }
  559. // 外来客户派单监听转入币,并且匹配交易元给客户
  560. else{
  561. RedisService::set($cacheKey.'_temp',['data'=> $v], 7200);
  562. $orderInfo = TradeOrderService::make()->getInfoByTradeHash($txid);
  563. $userId = isset($orderInfo['user_id']) ? $orderInfo['user_id'] : 0;
  564. $apiId = isset($orderInfo['api_id']) ? $orderInfo['api_id'] : 0;
  565. $num = isset($orderInfo['num']) ? $orderInfo['num'] : 0;
  566. if ($orderInfo && ($userId||$apiId) && $num>0 && ($num == $amount) && $orderInfo['status'] == 0 && $orderInfo['contact_type'] == 1) {
  567. // 获取匹配商家
  568. $businessInfo = \App\Services\Api\MemberService::make()->getTradeMember($num, 1, $userId);
  569. if (empty($businessInfo)) {
  570. RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到交易员'], 7200);
  571. continue;
  572. }
  573. if($businessInfo['usdt_num']<$num){
  574. RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到合适的交易员'], 7200);
  575. continue;
  576. }
  577. if(!TradeOrderModel::where(['txid' => $txid, 'id' => $orderInfo['id']])->update(['business_id' => $businessInfo['id'], 'status' => 1,'create_time'=>time(), 'update_time' => time()])){
  578. RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'business'=> $businessInfo,'info'=> $orderInfo,'error'=>'更新订单匹配交易数据失败'], 7200);
  579. continue;
  580. }
  581. // 记录平台钱包交易明细
  582. $results['logs'][] = [
  583. 'type' => 3,
  584. 'user_id' => 0,
  585. 'from_address' => $v['from'],
  586. 'to_address' => $v['to'],
  587. 'change_type' => $v['from'] == $address ? 2 : 1,
  588. 'coin_type' => 1,
  589. 'contact_type' => 1,
  590. 'order_no' => get_order_num('TP'),
  591. 'txid' => $txid,
  592. 'num' => $amount,
  593. 'free' => 0,
  594. 'balance' => 0,
  595. 'create_time' => time(),
  596. 'update_time' => time(),
  597. 'status' => 1,
  598. 'mark' => 1,
  599. ];
  600. $results['trades'][] = ['log' => $v, 'order' => $orderInfo];
  601. } // 其他交易明细
  602. else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) {
  603. $results['logs'][] = [
  604. 'type' => 3,
  605. 'user_id' => 0,
  606. 'from_address' => $v['from'],
  607. 'to_address' => $v['to'],
  608. 'change_type' => $v['from'] == $address ? 2 : 1,
  609. 'coin_type' => 1,
  610. 'contact_type' => 1,
  611. 'order_no' => get_order_num('TC'),
  612. 'txid' => $txid,
  613. 'num' => $amount,
  614. 'free' => 0,
  615. 'balance' => 0,
  616. 'create_time' => time(),
  617. 'update_time' => time(),
  618. 'status' => 1,
  619. 'mark' => 1,
  620. ];
  621. }
  622. }
  623. }
  624. }
  625. if ($results['logs']) {
  626. $this->coinModel->insertAll($results['logs']);
  627. }
  628. return $results;
  629. } catch (\Exception $exception) {
  630. $message = $exception->getMessage();
  631. $this->error = $message;
  632. return false;
  633. }
  634. }
  635. /**
  636. * 监听USDT-TRC2.0用户子钱包提币处理
  637. * @param $userId 用户ID
  638. * @param $address 用户钱包地址
  639. * @param int $coinType 币种:1-usdt
  640. * @param int $limit 转账记录数,最新的
  641. * @return array|false
  642. */
  643. public function getTrc20TransferLogByUser($userId, $address, $coinType = 1, $limit = 50)
  644. {
  645. if ($userId <= 0 || empty($address)) {
  646. $this->error = '1013';
  647. return false;
  648. }
  649. $url = sprintf($this->apiUrls['usdt_trx2_transfer_log'], $address, $limit, 'false', 'true');
  650. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  651. RedisService::set("caches:wallets:transfer_temp_{$userId}", ['url' => $this->config['tron_api_url'] . $url], 600);
  652. $result = curl_get($this->config['tron_api_url'] . $url, [], $headers, 10);
  653. $result = $result ? json_decode($result, true) : [];
  654. $datas = isset($result['data']) ? $result['data'] : [];
  655. $status = isset($result['success']) ? $result['success'] : '';
  656. if ($status != true || empty($datas)) {
  657. $this->error = '2207';
  658. return false;
  659. }
  660. $logs = [];
  661. $coinOutMin = ConfigService::make()->getConfigByCode('trc_out_limit');
  662. $coinOutMin = $coinOutMin > 0 ? $coinOutMin : 0;
  663. foreach ($datas as $v) {
  664. $amount = isset($v['value']) ? intval($v['value']) : 0;
  665. $amount = moneyFormat($amount / 1000000, 6);
  666. $time = isset($v['block_timestamp']) ? intval($v['block_timestamp'] / 1000) : 0;
  667. $txid = isset($v['transaction_id']) ? $v['transaction_id'] : '';
  668. if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) {
  669. $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num');
  670. $orderNo = get_order_num('TW');
  671. $log = [
  672. 'user_id' => $userId,
  673. 'change_type' => 2,
  674. 'coin_type' => $coinType,
  675. 'contact_type' => 1,
  676. 'order_no' => $orderNo,
  677. 'from_address' => isset($v['from']) ? $v['from'] : '',
  678. 'to_address' => isset($v['to']) ? $v['to'] : '',
  679. 'txid' => $txid,
  680. 'num' => $amount,
  681. 'balance' => $balance,
  682. 'create_time' => $time ? $time : time(),
  683. 'update_time' => time(),
  684. 'status' => 2,
  685. 'mark' => 1,
  686. ];
  687. if ($amount >= $coinOutMin && $this->memberModel->where(['id' => $userId])->decrement('usdt_num', $amount)) {
  688. $this->memberModel->where(['id' => $userId])->decrement('trc_usdt', $amount);
  689. $log['status'] = 1;
  690. // 明细处理
  691. $data = [
  692. 'order_no' => $orderNo,
  693. 'user_id' => $userId,
  694. 'type' => 5,
  695. 'pay_type' => 1,
  696. 'trade_type' => 3,
  697. 'change_type' => 2,
  698. 'num' => $amount,
  699. 'total' => 0,
  700. 'balance' => floatval($balance - $amount),
  701. 'create_time' => time(),
  702. 'update_time' => time(),
  703. 'remark' => '提币',
  704. 'status' => 1,
  705. 'mark' => 1,
  706. ];
  707. $this->capitalModel->edit($data);
  708. }
  709. $logs[] = $log;
  710. $this->coinModel->insert($log);
  711. }
  712. }
  713. return $logs;
  714. }
  715. /**
  716. * TRX余额
  717. * @param $address
  718. * @return false|float|string
  719. */
  720. public function getTrxBalance($address, $cache = false)
  721. {
  722. if (empty($address)) {
  723. $this->error = '1018';
  724. return false;
  725. }
  726. if (empty($this->config['tron_api_url'])) {
  727. $this->error = '2206';
  728. return false;
  729. }
  730. $cacheKey = "caches:wallet:balance:trx_{$address}";
  731. if ($data = RedisService::get($cacheKey) && $cache) {
  732. return $data;
  733. }
  734. if (RedisService::get($cacheKey . '_lock') && $cache) {
  735. return false;
  736. }
  737. try {
  738. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  739. $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers]));
  740. $trxWallet = new TRX($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]);
  741. $tron = new Tron();
  742. $tron->setAddress($address);
  743. $address = $tron->getAddress();
  744. $address = new \Tron\Address($address['base58'], '', $address['hex']);
  745. $result = $trxWallet->balance($address);
  746. $result = $result ? floatval($result) : '0.00';
  747. RedisService::set($cacheKey, $result, rand(5, 10));
  748. RedisService::set($cacheKey . '_lock', true, rand(10, 20));
  749. return $result;
  750. } catch (\Exception $exception) {
  751. $this->error = $exception->getMessage();
  752. return false;
  753. }
  754. }
  755. /**
  756. * USDT-TRC20余额
  757. * @param $address
  758. * @return false|float|string
  759. */
  760. public function getTrc20Usdt($address, $cache = false)
  761. {
  762. if (empty($address)) {
  763. $this->error = '1018';
  764. return false;
  765. }
  766. if (empty($this->config['tron_api_url'])) {
  767. $this->error = '2206';
  768. return false;
  769. }
  770. $cacheKey = "caches:wallet:balance:usdt_{$address}";
  771. if ($data = RedisService::get($cacheKey) && $cache) {
  772. return $data;
  773. }
  774. if (RedisService::get($cacheKey . '_lock') && $cache) {
  775. return false;
  776. }
  777. try {
  778. $headers = ["TRON-PRO-API-KEY" => $this->config['tron_api_key']];
  779. $api = new Api(new Client(['base_uri' => $this->config['tron_api_url'], 'headers' => $headers]));
  780. $trxWallet = new TRC20($api, ['contract_address' => $this->config['tron_contract_address'], 'decimals' => 6]);
  781. $tron = new Tron();
  782. $tron->setAddress($address);
  783. $address = $tron->getAddress();
  784. $address = new \Tron\Address($address['base58'], '', $address['hex']);
  785. $result = $trxWallet->balance($address);
  786. $result = $result ? floatval($result) : '0.00';
  787. RedisService::set($cacheKey, $result, rand(5, 10));
  788. RedisService::set($cacheKey . '_lock', true, rand(10, 20));
  789. return $result;
  790. } catch (\Exception $exception) {
  791. $this->error = $exception->getMessage();
  792. return false;
  793. }
  794. }
  795. /********************** ERC钱包 **************************/
  796. /**
  797. * 获取ERC2.0钱包地址
  798. * @param string $type
  799. * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
  800. */
  801. public function getErcAddress()
  802. {
  803. $random = new Random();
  804. // 生成随机数(initial entropy)
  805. $entropy = $random->bytes(Bip39Mnemonic::MIN_ENTROPY_BYTE_LEN);
  806. $bip39 = MnemonicFactory::bip39();
  807. // 通过随机数生成助记词
  808. $mnemonic = $bip39->entropyToMnemonic($entropy);
  809. echo "mnemonic: " . $mnemonic.PHP_EOL.PHP_EOL;// 助记词
  810. $seedGenerator = new Bip39SeedGenerator();
  811. // 通过助记词生成种子,传入可选加密串'hello'
  812. $seed = $seedGenerator->getSeed($mnemonic);
  813. echo "seed: " . $seed->getHex() . PHP_EOL;
  814. $hdFactory = new HierarchicalKeyFactory();
  815. $master = $hdFactory->fromEntropy($seed);
  816. $util = new Util();
  817. // 设置路径account
  818. $hardened = $master->derivePath("44'/60'/0'/0/0");
  819. $publicKey = $hardened->getPublicKey()->getHex();
  820. $privateKey = $hardened->getPrivateKey()->getHex();
  821. $address = $util->publicKeyToAddress($util->privateKeyToPublicKey($hardened->getPrivateKey()->getHex()));
  822. return ['public_key' => $publicKey, 'hexAddress' => $address, 'address' => $address, 'wif'=> $privateKey];
  823. }
  824. /**
  825. * 获取ERC2.0钱包地址
  826. * @param string $type
  827. * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
  828. */
  829. public function getErcHexPrivate($wif)
  830. {
  831. $privateKeyFactory = new PrivateKeyFactory();
  832. $privateKey = $privateKeyFactory->fromWif($wif);
  833. $publicKey = $privateKey->getPublicKey();
  834. $publicKey = $privateKey->getSecret();
  835. // p2pkh 格式的地址
  836. $addressService = new PayToPubKeyHashAddress($publicKey->getPubKeyHash());
  837. // 将生成的钱包保存到数据库中
  838. $hex = $privateKey->getHex();
  839. $address = $addressService->getAddress();
  840. return ['wif' => $wif, 'hexAddress' => $this->getHexAddress($address), 'address' => $address,'hex_private'=>$hex];
  841. }
  842. /**
  843. * 获取HASH钱包地址
  844. * @param $address 钱包地址
  845. * @return \BitWasp\Buffertools\BufferInterface
  846. * @throws \BitWasp\Bitcoin\Exceptions\UnrecognizedAddressException
  847. */
  848. public function getHexAddress($address)
  849. {
  850. $data = WitnessProgram::v0((new AddressCreator())->fromString($address)->getHash());
  851. $buffer = $data->getProgram()->getHex();
  852. return $buffer ? '0x' . $buffer : '';
  853. }
  854. /**
  855. * TRC余额
  856. * @param $address
  857. * @return false|float|string
  858. */
  859. public function getErcBalance($address, $cache = false)
  860. {
  861. if (empty($address)) {
  862. $this->error = '1018';
  863. return false;
  864. }
  865. if (empty($this->config['eth_api_url'])) {
  866. $this->error = '2206';
  867. return false;
  868. }
  869. if (empty($this->config['eth_api_key'])) {
  870. $this->error = '2207';
  871. return false;
  872. }
  873. $cacheKey = "caches:wallet:balance:erc_{$address}";
  874. if ($data = RedisService::get($cacheKey) && $cache) {
  875. return floatval($data);
  876. }
  877. if (RedisService::get($cacheKey . '_lock') && $cache) {
  878. return false;
  879. }
  880. try {
  881. $url = sprintf($this->apiUrls['erc_balance'],$address, $this->config['eth_api_key']);
  882. $url = $this->config['eth_api_url'] . $url;
  883. RedisService::set("caches:trades:erc:balance_{$address}", ['url' => $url, 'config' => $this->config], 600);
  884. $result = curl_api($url);
  885. $result = $result? json_decode($result, true) : [];
  886. $balance = isset($result['result']) ? floatval($result['result']) : 0.00;
  887. $balance = $balance>0? floatval($balance / pow(10, 18)) : '0.00';
  888. RedisService::set($cacheKey, $balance, rand(5, 10));
  889. RedisService::set($cacheKey . '_lock', true, rand(10, 20));
  890. return floatval($balance);
  891. } catch (\Exception $exception) {
  892. $this->error = $exception->getMessage();
  893. return false;
  894. }
  895. }
  896. /**
  897. * USDT-ERC20余额
  898. * @param $address
  899. * @return false|float|string
  900. */
  901. public function getErc20Usdt($address, $cache = false)
  902. {
  903. if (empty($address)) {
  904. $this->error = '1018';
  905. return false;
  906. }
  907. if (empty($this->config['eth_api_url'])) {
  908. $this->error = '2206';
  909. return false;
  910. }
  911. if (empty($this->config['eth_api_key'])) {
  912. $this->error = '2207';
  913. return false;
  914. }
  915. if (empty($this->config['erc_contract_address'])) {
  916. $this->error = '2228';
  917. return false;
  918. }
  919. $cacheKey = "caches:wallet:balance:erc_usdt_{$address}";
  920. if ($data = RedisService::get($cacheKey) && $cache) {
  921. return floatval($data);
  922. }
  923. if (RedisService::get($cacheKey . '_lock') && $cache) {
  924. return false;
  925. }
  926. try {
  927. $url = sprintf($this->apiUrls['erc20_balance'], $this->config['erc_contract_address'],$address, $this->config['eth_api_key']);
  928. $url = $this->config['eth_api_url'] . $url;
  929. RedisService::set("caches:trades:erc:balance20_{$address}", ['url' => $url, 'config' => $this->config], 600);
  930. $result = curl_api($url);
  931. $result = $result? json_decode($result, true) : [];
  932. $balance = isset($result['result']) ? floatval($result['result']) : 0.00;
  933. $balance = $balance>0? floatval($balance / pow(10, 6)) : '0.00';
  934. RedisService::set($cacheKey, floatval($balance), rand(5, 10));
  935. RedisService::set($cacheKey . '_lock', true, rand(10, 20));
  936. return floatval($balance);
  937. } catch (\Exception $exception) {
  938. $this->error = $exception->getMessage();
  939. return false;
  940. }
  941. }
  942. /**
  943. * ERC 转账
  944. * @param $to
  945. * @param $amount
  946. * @param string $from
  947. * @param string $fromPrivate
  948. * @return array|false
  949. */
  950. public function ercTransfer($to, $amount, $from = '', $fromPrivate = '')
  951. {
  952. if ($amount <= 0) {
  953. $this->error = '2205';
  954. return false;
  955. }
  956. if (empty($this->config['eth_api_url'])) {
  957. $this->error = '2206';
  958. return false;
  959. }
  960. if (empty($this->config['eth_api_key'])) {
  961. $this->error = '2207';
  962. return false;
  963. }
  964. // 获取钱包参数
  965. try {
  966. $otcAddress = $from ? $from : ConfigService::make()->getConfigByCode('erc_out_address');
  967. $otcAddressPrivate = $from ? $fromPrivate : ConfigService::make()->getConfigByCode('erc_out_private_key');
  968. if (empty($otcAddress) || empty($otcAddressPrivate)) {
  969. $this->error = '2228';
  970. return false;
  971. }
  972. $amount = floatval($amount * pow(10, 18));
  973. $params = [
  974. "from" => $otcAddress, // 来源
  975. "to" => $to, // 收款
  976. "gas" => '0x' . dechex(90000), // gas量,默认90000
  977. "gasPrice" => "0x9184e72a000",
  978. "value" => '0x' . dechex($amount),
  979. "data" => ''
  980. ];
  981. $transaction = new Transaction($params);
  982. $transaction->sign($otcAddressPrivate);
  983. $hex = '0x' . $transaction;
  984. $url = sprintf($this->apiUrls['erc_transaction'], $hex, $this->config['eth_api_key']);
  985. $url = $this->config['eth_api_url'] . $url;
  986. RedisService::set("caches:trades:erc:transfer_{$to}", ['url' => $url, 'params' => $params, 'hex' => $hex], 600);
  987. $result = curl_api($url);
  988. $result = $result ? json_decode($result, true) : [];
  989. $tradeHash = isset($result['result']) ? $result['result'] : '';
  990. if ($tradeHash && hexdec($tradeHash) != 0) {
  991. return ['txid' => $tradeHash, 'amount' => $amount];
  992. } else {
  993. $this->error = '2229';
  994. return false;
  995. }
  996. } catch (\Exception $exception) {
  997. $message = $exception->getMessage();
  998. $this->error = $message;
  999. return false;
  1000. }
  1001. }
  1002. /**
  1003. * usdt-erc2.0 转账
  1004. * @param $to 进账账户
  1005. * @param $amount 转账金额
  1006. * @param $from 转账账户
  1007. * @param $fromPrivate 转账账户私钥
  1008. * @throws \Tron\Exceptions\TransactionException
  1009. * @throws \Tron\Exceptions\TronErrorException
  1010. */
  1011. public function usdtErcTransfer($to, $amount, $from = '', $fromPrivate = '')
  1012. {
  1013. if ($amount <= 0) {
  1014. $this->error = '2205';
  1015. return false;
  1016. }
  1017. if (empty($this->config['eth_api_url'])) {
  1018. $this->error = '2206';
  1019. return false;
  1020. }
  1021. if (empty($this->config['eth_api_key'])) {
  1022. $this->error = '2207';
  1023. return false;
  1024. }
  1025. // 获取钱包参数
  1026. try {
  1027. // 用出账钱包转账
  1028. $otcAddress = $from ? $from : ConfigService::make()->getConfigByCode('erc_out_address');
  1029. $otcAddressPrivate = $from ? $fromPrivate : ConfigService::make()->getConfigByCode('erc_out_private_key');
  1030. if (empty($otcAddress) || empty($otcAddressPrivate)) {
  1031. $this->error = '2228';
  1032. return false;
  1033. }
  1034. $amount = floatval($amount * pow(10, 18));
  1035. $sign = $this->getSha3Sign("transfer(address,uint256)");
  1036. $params = [
  1037. "from" => $otcAddress, // 来源
  1038. "to" => $to, // 收款
  1039. "gas" => '0x' . dechex(90000), // gas量,默认90000
  1040. "gasPrice" => "0x9184e72a000",
  1041. "value" => '0x' . dechex($amount),
  1042. "data" => $this->getTransactionSignData($sign, $to, $amount)
  1043. ];
  1044. $transaction = new Transaction($params);
  1045. $transaction->sign($otcAddressPrivate);
  1046. $hex = '0x' . $transaction;
  1047. $url = sprintf($this->apiUrls['erc_transaction'], $hex, $this->config['eth_api_key']);
  1048. $url = $this->config['eth_api_url'] . $url;
  1049. RedisService::set("caches:trades:erc20:transfer_{$to}", ['url' => $url, 'params' => $params, 'hex' => $hex], 600);
  1050. $result = curl_api($url);
  1051. $result = $result ? json_decode($result, true) : [];
  1052. $tradeHash = isset($result['result']) ? $result['result'] : '';
  1053. $error = isset($result['error']) ? $result['error'] : [];
  1054. if ($tradeHash && hexdec($tradeHash) != 0) {
  1055. return ['txID' => $tradeHash, 'amount' => $amount];
  1056. } else {
  1057. $this->error = isset($error['message']) ? $error['message'] : '2229';
  1058. return false;
  1059. }
  1060. } catch (\Exception $exception) {
  1061. $message = $exception->getMessage();
  1062. $this->error = $message;
  1063. return false;
  1064. }
  1065. }
  1066. /**
  1067. * 合约方法签名编号
  1068. * @param string $data
  1069. * @return string
  1070. */
  1071. public function getSha3Sign($data)
  1072. {
  1073. $util = new Util();
  1074. $result = $util->sha3($data);
  1075. return '0x' . substr($result, 0, 8);
  1076. }
  1077. /**
  1078. * 获取签名数据
  1079. * @param $sign
  1080. * @param $address
  1081. * @return string
  1082. */
  1083. public function getSignData($sign, $address)
  1084. {
  1085. return $sign . str_repeat('0', 24) . substr($address, 2);
  1086. }
  1087. /**
  1088. * 获取交易签名参数
  1089. * @param $sign 调用的合约方法生成的sha3签名编号
  1090. * @param $toAddress 收款地址
  1091. * @param $amount 转账金额/十进制
  1092. * @return string
  1093. */
  1094. public function getTransactionSignData($sign, $toAddress, $amount)
  1095. {
  1096. $toAddress = substr($toAddress, 2); // 去掉0x
  1097. $toAddressSign = str_repeat('0', 64 - strlen($toAddress)) . $toAddress; // 前面补齐0为64位
  1098. $amount = dechex($amount); // 十进制转十六进制
  1099. $amountSign = str_repeat('0', 64 - strlen($amount)) . $amount; // 前面补齐0为64位
  1100. return $sign . $toAddressSign . $amountSign;
  1101. }
  1102. /**
  1103. * 监听USDT-ERC2.0用户子钱包存币处理
  1104. * @param $userId 用户ID
  1105. * @param $address 用户钱包地址
  1106. * @param int $coinType 币种:1-usdt
  1107. * @param int $limit 转账记录数,最新的
  1108. * @return array|false
  1109. */
  1110. public function getErc20RechargeLog($userId, $address, $coinType = 1, $limit = 50)
  1111. {
  1112. if ($userId <= 0 || empty($address)) {
  1113. $this->error = '1013';
  1114. return false;
  1115. }
  1116. if (empty($this->config['eth_api_url'])) {
  1117. $this->error = '2206';
  1118. return false;
  1119. }
  1120. if (empty($this->config['eth_api_key'])) {
  1121. $this->error = '2207';
  1122. return false;
  1123. }
  1124. try {
  1125. $cacheKey = "caches:wallets:recharge:erc_{$userId}_{$address}";
  1126. if (RedisService::get($cacheKey . '_lock')) {
  1127. return false;
  1128. }
  1129. RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10));
  1130. $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, 1, $limit, $this->config['eth_api_key']);
  1131. $url = $this->config['eth_api_url'] . $url;
  1132. RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600);
  1133. $result = curl_api($url);
  1134. $result = $result ? json_decode($result, true) : [];
  1135. $status = isset($result['status']) ? $result['status'] : 0;
  1136. $datas = isset($result['result']) ? $result['result'] : [];
  1137. if ($status != 1 || empty($datas)) {
  1138. $this->error = '2207';
  1139. return false;
  1140. }
  1141. $logs = [];
  1142. $coinInMin = ConfigService::make()->getConfigByCode('erc_in_limit');
  1143. $coinInMin = $coinInMin > 0 ? $coinInMin : 0;
  1144. if ($datas) {
  1145. foreach ($datas as $v) {
  1146. $amount = isset($v['value']) ? intval($v['value']) : 0;
  1147. $amount = moneyFormat($amount / 1000000, 6);
  1148. $time = isset($v['timeStamp']) ? intval($v['timeStamp']) : 0;
  1149. $txid = isset($v['hash']) ? $v['hash'] : '';
  1150. if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) {
  1151. $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num');
  1152. $orderNo = get_order_num('ER');
  1153. $log = [
  1154. 'type' => 1,
  1155. 'user_id' => $userId,
  1156. 'change_type' => 1,
  1157. 'coin_type' => $coinType,
  1158. 'contact_type' => 2,
  1159. 'order_no' => $orderNo,
  1160. 'from_address' => isset($v['from']) ? $v['from'] : '',
  1161. 'to_address' => isset($v['to']) ? $v['to'] : '',
  1162. 'txid' => $txid,
  1163. 'num' => $amount,
  1164. 'balance' => $balance,
  1165. 'create_time' => $time ? $time : time(),
  1166. 'update_time' => time(),
  1167. 'status' => 2,
  1168. 'mark' => 1,
  1169. ];
  1170. if ($amount >= $coinInMin && $this->memberModel->where(['id' => $userId])->increment('usdt_num', $amount)) {
  1171. $this->memberModel->where(['id' => $userId])->increment('erc_usdt', $amount);
  1172. $log['status'] = 1;
  1173. $data = [
  1174. 'order_no' => $orderNo,
  1175. 'user_id' => $userId,
  1176. 'type' => 4,
  1177. 'pay_type' => 1,
  1178. 'trade_type' => 3,
  1179. 'change_type' => 1,
  1180. 'num' => $amount,
  1181. 'total' => 0,
  1182. 'balance' => floatval($balance + $amount),
  1183. 'create_time' => time(),
  1184. 'update_time' => time(),
  1185. 'remark' => '存币:' . $amount,
  1186. 'status' => 1,
  1187. 'mark' => 1,
  1188. ];
  1189. $this->capitalModel->edit($data);
  1190. }
  1191. $logs[] = $log;
  1192. $this->coinModel->insert($log);
  1193. }
  1194. }
  1195. }
  1196. return $datas;
  1197. } catch (\Exception $exception) {
  1198. $message = $exception->getMessage();
  1199. $this->error = $message;
  1200. return false;
  1201. }
  1202. }
  1203. /**
  1204. * 获取USDT-ERC2.0交易记录(进出账)
  1205. * @param $address 用户钱包地址
  1206. * @param int $coinType 币种:1-usdt
  1207. * @param int $limit 转账记录数,最新的
  1208. * @return array|false
  1209. */
  1210. public function getErc20TransferLog($address, $type = 1, $page = 1, $limit = 50)
  1211. {
  1212. if (empty($this->config['eth_api_url'])) {
  1213. $this->error = '2206';
  1214. return false;
  1215. }
  1216. if (empty($this->config['eth_api_key'])) {
  1217. $this->error = '2207';
  1218. return false;
  1219. }
  1220. try {
  1221. $cacheKey = "caches:wallets:transfer:erc_{$address}";
  1222. if (RedisService::get($cacheKey . '_lock')) {
  1223. return false;
  1224. }
  1225. RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10));
  1226. $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, $page, $limit, $this->config['eth_api_key']);
  1227. $url = $this->config['eth_api_url'] . $url;
  1228. RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600);
  1229. $result = curl_api($url);
  1230. $result = $result ? json_decode($result, true) : [];
  1231. $status = isset($result['status']) ? $result['status'] : 0;
  1232. $datas = isset($result['result']) ? $result['result'] : [];
  1233. if ($status != 1 || empty($datas)) {
  1234. $this->error = '2207';
  1235. return false;
  1236. }
  1237. return $datas;
  1238. } catch (\Exception $exception) {
  1239. $message = $exception->getMessage();
  1240. $this->error = $message;
  1241. return false;
  1242. }
  1243. }
  1244. /**
  1245. * 监听USDT-ERC2.0提币记录并进账(平台钱包)
  1246. * @param $address 用户钱包地址
  1247. * @param int $limit 转账记录数,最新的
  1248. * @return array|false
  1249. */
  1250. public function getErc20TransferLogByOtc($address, $tradeType=1, $limit = 100)
  1251. {
  1252. if (empty($address)) {
  1253. $this->error = '1013';
  1254. return false;
  1255. }
  1256. if (empty($this->config['eth_api_url'])) {
  1257. $this->error = '2206';
  1258. return false;
  1259. }
  1260. if (empty($this->config['eth_api_key'])) {
  1261. $this->error = '2207';
  1262. return false;
  1263. }
  1264. try {
  1265. $cacheKey = "caches:wallets:transfer:erc_{$address}";
  1266. if (RedisService::get($cacheKey . '_lock')) {
  1267. return false;
  1268. }
  1269. RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10));
  1270. $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, 1, $limit, $this->config['eth_api_key']);
  1271. $url = $this->config['eth_api_url'] . $url;
  1272. RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600);
  1273. $result = curl_api($url);
  1274. $result = $result ? json_decode($result, true) : [];
  1275. $status = isset($result['status']) ? $result['status'] : 0;
  1276. $datas = isset($result['result']) ? $result['result'] : [];
  1277. if ($status != 1 || empty($datas)) {
  1278. $this->error = '2207';
  1279. return false;
  1280. }
  1281. $results = [
  1282. 'logs' => [],
  1283. 'trades' => [],
  1284. ];
  1285. if ($datas) {
  1286. $cachekey = "caches:tradeOtc:{$address}_{$tradeType}";
  1287. foreach ($datas as $v) {
  1288. $from = isset($v['from']) ? trim($v['from']) : '';
  1289. $to = isset($v['to']) ? trim($v['to']) : '';
  1290. $amount = isset($v['value']) ? intval($v['value']) : 0;
  1291. $decimal = isset($v['tokenDecimal']) ? intval($v['tokenDecimal']) : 0;
  1292. $decimal = $decimal ? $decimal : 6;
  1293. $amount = moneyFormat($amount / pow(10, $decimal), 6);
  1294. $time = isset($v['timeStamp']) ? intval($v['timeStamp']) : 0;
  1295. $txid = isset($v['hash']) ? $v['hash'] : '';
  1296. if($tradeType==1){
  1297. if ($time > time() - 15 * 24 * 3600 && $from == $address) {
  1298. // 有记录,且是用户提币
  1299. $coinInfo = CoinLogService::make()->getCacheInfoByTxid($txid);
  1300. $userId = isset($coinInfo['user_id']) ? $coinInfo['user_id'] : 0;
  1301. if ($coinInfo && $userId && $coinInfo['status'] == 4 && $coinInfo['contact_type'] == 2) {
  1302. // 直接更新提币状态
  1303. CoinLogModel::where(['txid' => $txid, 'user_id' => $userId])->update(['status' => 1, 'update_time' => time()]);
  1304. // 明细处理
  1305. $num = floatval($coinInfo['num'] + $coinInfo['free']);
  1306. $data = [
  1307. 'order_no' => $coinInfo['order_no'],
  1308. 'user_id' => $userId,
  1309. 'type' => 5,
  1310. 'pay_type' => 1,
  1311. 'trade_type' => 3,
  1312. 'change_type' => 2,
  1313. 'num' => $num,
  1314. 'total' => 0,
  1315. 'balance' => floatval($coinInfo['balance'] - $num),
  1316. 'create_time' => time(),
  1317. 'update_time' => time(),
  1318. 'remark' => '提币:' . $amount,
  1319. 'status' => 1,
  1320. 'mark' => 1,
  1321. ];
  1322. $this->capitalModel->edit($data);
  1323. $results['trades'][] = ['log' => $v, 'order' => $coinInfo];
  1324. }
  1325. } // 其他交易明细
  1326. else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) {
  1327. $results['logs'][] = [
  1328. 'type' => 3,
  1329. 'user_id' => 0,
  1330. 'from_address' => $from,
  1331. 'to_address' => $to,
  1332. 'change_type' => $from == $address ? 2 : 1,
  1333. 'coin_type' => 1,
  1334. 'contact_type' => 2,
  1335. 'order_no' => get_order_num('TC'),
  1336. 'txid' => $txid,
  1337. 'num' => $amount,
  1338. 'free' => 0,
  1339. 'balance' => 0,
  1340. 'create_time' => time(),
  1341. 'update_time' => time(),
  1342. 'status' => 1,
  1343. 'mark' => 1,
  1344. ];
  1345. }
  1346. }
  1347. // 外来客户派单监听转入币,并且匹配交易元给客户
  1348. else{
  1349. RedisService::set($cacheKey.'_temp',['data'=> $v], 7200);
  1350. $orderInfo = TradeOrderService::make()->getInfoByTradeHash($txid);
  1351. $userId = isset($orderInfo['user_id']) ? $orderInfo['user_id'] : 0;
  1352. $apiId = isset($orderInfo['api_id']) ? $orderInfo['api_id'] : 0;
  1353. $num = isset($orderInfo['num']) ? $orderInfo['num'] : 0;
  1354. if ($orderInfo && ($userId||$apiId) && $num>0 && ($num == $amount) && $orderInfo['status'] == 0 && $orderInfo['contact_type'] == 2) {
  1355. // 获取匹配商家
  1356. $businessInfo = \App\Services\Api\MemberService::make()->getTradeMember($num, 1, $userId);
  1357. if (empty($businessInfo)) {
  1358. RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到交易员'], 7200);
  1359. continue;
  1360. }
  1361. if($businessInfo['usdt_num']<$num){
  1362. RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'info'=> $orderInfo,'error'=>'未匹配到合适的交易员'], 7200);
  1363. continue;
  1364. }
  1365. if(!TradeOrderModel::where(['txid' => $txid, 'id' => $orderInfo['id']])->update(['business_id' => $businessInfo['id'], 'status' => 1,'create_time'=>time(), 'update_time' => time()])){
  1366. RedisService::set($cachekey.":{$userId}_{$apiId}_error", ['data'=> $v,'business'=> $businessInfo,'info'=> $orderInfo,'error'=>'更新订单匹配交易数据失败'], 7200);
  1367. continue;
  1368. }
  1369. // 记录平台钱包交易明细
  1370. $results['logs'][] = [
  1371. 'type' => 3,
  1372. 'user_id' => 0,
  1373. 'from_address' => $v['from'],
  1374. 'to_address' => $v['to'],
  1375. 'change_type' => $v['from'] == $address ? 2 : 1,
  1376. 'coin_type' => 1,
  1377. 'contact_type' => 1,
  1378. 'order_no' => get_order_num('TP'),
  1379. 'txid' => $txid,
  1380. 'num' => $amount,
  1381. 'free' => 0,
  1382. 'balance' => 0,
  1383. 'create_time' => time(),
  1384. 'update_time' => time(),
  1385. 'status' => 1,
  1386. 'mark' => 1,
  1387. ];
  1388. $results['trades'][] = ['log' => $v, 'order' => $orderInfo];
  1389. } // 其他交易明细
  1390. else if ($time > time() - 15 * 24 * 3600 && !$coinInfo) {
  1391. $results['logs'][] = [
  1392. 'type' => 3,
  1393. 'user_id' => 0,
  1394. 'from_address' => $v['from'],
  1395. 'to_address' => $v['to'],
  1396. 'change_type' => $v['from'] == $address ? 2 : 1,
  1397. 'coin_type' => 1,
  1398. 'contact_type' => 1,
  1399. 'order_no' => get_order_num('TC'),
  1400. 'txid' => $txid,
  1401. 'num' => $amount,
  1402. 'free' => 0,
  1403. 'balance' => 0,
  1404. 'create_time' => time(),
  1405. 'update_time' => time(),
  1406. 'status' => 1,
  1407. 'mark' => 1,
  1408. ];
  1409. }
  1410. }
  1411. }
  1412. }
  1413. if ($results['logs']) {
  1414. $this->coinModel->insertAll($results['logs']);
  1415. }
  1416. return $results;
  1417. } catch (\Exception $exception) {
  1418. $message = $exception->getMessage();
  1419. $this->error = $message;
  1420. return false;
  1421. }
  1422. }
  1423. /**
  1424. * USDT-ERC20 用户子钱包提币处理
  1425. * @param $userId
  1426. * @param $address
  1427. * @param int $coinType
  1428. * @param int $limit
  1429. * @return false
  1430. */
  1431. public function getErc20TransferLogByUser($userId, $address, $coinType = 1, $limit = 50)
  1432. {
  1433. if (empty($address)) {
  1434. $this->error = '1013';
  1435. return false;
  1436. }
  1437. if (empty($this->config['eth_api_url'])) {
  1438. $this->error = '2206';
  1439. return false;
  1440. }
  1441. if (empty($this->config['eth_api_key'])) {
  1442. $this->error = '2207';
  1443. return false;
  1444. }
  1445. try {
  1446. $cacheKey = "caches:wallets:transfer:erc_{$address}";
  1447. if (RedisService::get($cacheKey . '_lock')) {
  1448. return false;
  1449. }
  1450. RedisService::set($cacheKey . '_lock', ['config' => $this->config, 'address' => $address], rand(5, 10));
  1451. $url = sprintf($this->apiUrls['erc20_transaction_log'], $address, 1, $limit, $this->config['eth_api_key']);
  1452. $url = $this->config['eth_api_url'] . $url;
  1453. RedisService::set("caches:trades:erc20:logs_{$address}", ['url' => $url, 'params' => ['address' => $address, 'config' => $this->config, 'limit' => $limit]], 600);
  1454. $result = curl_api($url);
  1455. $result = $result ? json_decode($result, true) : [];
  1456. $status = isset($result['status']) ? $result['status'] : 0;
  1457. $datas = isset($result['result']) ? $result['result'] : [];
  1458. if ($status != 1 || empty($datas)) {
  1459. $this->error = '2207';
  1460. return false;
  1461. }
  1462. $logs = [];
  1463. $coinOutMin = ConfigService::make()->getConfigByCode('trc_out_limit');
  1464. $coinOutMin = $coinOutMin > 0 ? $coinOutMin : 0;
  1465. if ($datas) {
  1466. foreach ($datas as $v) {
  1467. $decimal = isset($v['tokenDecimal']) ? intval($v['tokenDecimal']) : 0;
  1468. $decimal = $decimal ? $decimal : 6;
  1469. $amount = isset($v['value']) ? intval($v['value']) : 0;
  1470. $amount = moneyFormat($amount / pow(10, $decimal), 6);
  1471. $time = isset($v['timestamp']) ? intval($v['timestamp']) : 0;
  1472. $txid = isset($v['hash']) ? $v['hash'] : '';
  1473. if (!CoinLogService::make()->checkExists('txid', $txid) && $time > time() - 15 * 24 * 3600) {
  1474. $balance = $this->memberModel->where(['id' => $userId])->value('usdt_num');
  1475. $orderNo = get_order_num('TW');
  1476. $log = [
  1477. 'user_id' => $userId,
  1478. 'change_type' => 2,
  1479. 'coin_type' => $coinType,
  1480. 'contact_type' => 2,
  1481. 'order_no' => $orderNo,
  1482. 'from_address' => isset($v['from']) ? $v['from'] : '',
  1483. 'to_address' => isset($v['to']) ? $v['to'] : '',
  1484. 'txid' => $txid,
  1485. 'num' => $amount,
  1486. 'balance' => $balance,
  1487. 'create_time' => $time ? $time : time(),
  1488. 'update_time' => time(),
  1489. 'status' => 2,
  1490. 'mark' => 1,
  1491. ];
  1492. if ($amount >= $coinOutMin && $this->memberModel->where(['id' => $userId])->decrement('usdt_num', $amount)) {
  1493. $this->memberModel->where(['id' => $userId])->decrement('erc_usdt', $amount);
  1494. $log['status'] = 1;
  1495. // 明细处理
  1496. $data = [
  1497. 'order_no' => $orderNo,
  1498. 'user_id' => $userId,
  1499. 'type' => 5,
  1500. 'pay_type' => 1,
  1501. 'trade_type' => 3,
  1502. 'change_type' => 2,
  1503. 'num' => $amount,
  1504. 'total' => 0,
  1505. 'balance' => floatval($balance - $amount),
  1506. 'create_time' => time(),
  1507. 'update_time' => time(),
  1508. 'remark' => '提币:' . $amount,
  1509. 'status' => 1,
  1510. 'mark' => 1,
  1511. ];
  1512. $this->capitalModel->edit($data);
  1513. }
  1514. $logs[] = $log;
  1515. $this->coinModel->insert($log);
  1516. }
  1517. }
  1518. }
  1519. return $logs;
  1520. } catch (\Exception $exception) {
  1521. $message = $exception->getMessage();
  1522. $this->error = $message;
  1523. return false;
  1524. }
  1525. }
  1526. /**
  1527. * usdt-erc2.0 归集
  1528. * @throws \Tron\Exceptions\TransactionException
  1529. * @throws \Tron\Exceptions\TronErrorException
  1530. */
  1531. public function usdtErcTrigger($force = false)
  1532. {
  1533. if (empty($this->config['eth_api_url'])) {
  1534. $this->error = '2206';
  1535. return false;
  1536. }
  1537. if (empty($this->config['eth_api_key'])) {
  1538. $this->error = '2207';
  1539. return false;
  1540. }
  1541. // 获取钱包参数
  1542. try {
  1543. // 用收账钱包归集
  1544. $otcAddress = ConfigService::make()->getConfigByCode('erc_address');
  1545. $otcAddressPrivate = ConfigService::make()->getConfigByCode('erc_private_key');
  1546. // 出账手续费钱包
  1547. $otcOutAddress = ConfigService::make()->getConfigByCode('erc_out_address');
  1548. $otcOutAddressPrivate = ConfigService::make()->getConfigByCode('erc_out_private_key');
  1549. $triggerMin = ConfigService::make()->getConfigByCode('trade_trigger_min');
  1550. $triggerTime = ConfigService::make()->getConfigByCode('trade_trigger_time');
  1551. $triggerFree = ConfigService::make()->getConfigByCode('trade_trigger_eth_free');
  1552. $triggerFree = $triggerFree > 0 ? $triggerFree : (5/pow(10, 8));
  1553. $triggerMin = $triggerMin > 0 ? $triggerMin : 0.1;
  1554. $triggerTime = $triggerTime > 0 ? $triggerTime * 86400 : 86400;
  1555. if (empty($otcAddress) || empty($otcAddressPrivate)) {
  1556. $this->error = '2203';
  1557. return false;
  1558. }
  1559. // 出账钱包
  1560. if (empty($otcOutAddress) || empty($otcOutAddressPrivate)) {
  1561. $this->error = '2203';
  1562. return false;
  1563. }
  1564. $page = RedisService::get("caches:wallet:transferRecPage");
  1565. $page = $page ? $page : 1;
  1566. // 归集时间段为凌晨0点-5点
  1567. if ((date('H:i') >= '05:00') && !$force) {
  1568. $this->error = '不在归集时间段';
  1569. return false;
  1570. }
  1571. if (RedisService::get("caches:wallet:triggerErcLock:{$page}")) {
  1572. $this->error = '不要频繁操作,30秒后重试';
  1573. return false;
  1574. }
  1575. // 上锁
  1576. RedisService::set("caches:wallet:triggerErcLock:{$page}", 1, rand(10, 30));
  1577. $addrList = MemberService::make()->getTriggerAddressList($triggerMin, $page, 200);
  1578. if (empty($addrList)) {
  1579. RedisService::set("caches:wallet:transferErcPage", 1, 600);
  1580. $this->error = '1019';
  1581. return false;
  1582. }
  1583. // 平台钱包地址
  1584. $count = 0;
  1585. $failedCount = 0;
  1586. $api = new Etherscan($this->config['eth_api_key']);
  1587. $cacheKey = "caches:wallet:triggerErc:";
  1588. foreach ($addrList as $v) {
  1589. try {
  1590. // 获取子钱包TRC-USDT余额
  1591. $userId = isset($v['id']) ? $v['id'] : 0;
  1592. $address = isset($v['erc_address']) ? $v['erc_address'] : '';
  1593. $triggerAddress = isset($v['erc_hexaddress']) ? $v['erc_hexaddress'] : '';
  1594. $addressPrivate = isset($v['erc_wif']) ? $v['erc_wif'] : '';
  1595. // 可归集的USDT余额
  1596. $result = $api->balance($triggerAddress);
  1597. // $result = $api->tokenBalance($triggerAddress);
  1598. $triggerUsdt = isset($result['result']) ? floatval($result['result']) : 0.00;
  1599. $triggerUsdt = $triggerUsdt ? floatval($triggerUsdt / pow(10, 6)) : '0.00';
  1600. // USDT 余额低于归集金额,则不归集
  1601. if ($triggerUsdt < $triggerMin) {
  1602. $failedCount++;
  1603. $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'error' => '用户余额不足归集', 'date' => date('Y-m-d H:i:s')];
  1604. RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200);
  1605. continue;
  1606. }
  1607. // 获取子钱包Eth余额
  1608. $result = $api->balance($triggerAddress);
  1609. $triggerEth = isset($result['result']) ? floatval($result['result']) : 0.00;
  1610. $triggerEth = $triggerEth ? floatval($triggerEth / pow(10, 18)) : '0.00';
  1611. // 获取平台出账钱包ETH余额
  1612. $result = $api->balance($otcOutAddress);
  1613. $otcOutEthTotal = isset($result['result']) ? floatval($result['result']) : 0.00;
  1614. $otcOutEthTotal = $otcOutEthTotal ? floatval($otcOutEthTotal / pow(10, 18)) : '0.00';
  1615. // 如果子钱包和平台钱包TRX余额不足手续费,则不归集
  1616. if ($triggerEth < $triggerFree && $otcOutEthTotal < $triggerFree) {
  1617. $failedCount++;
  1618. $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerTrx' => $triggerEth, 'otcTrx' => $otcOutEthTotal, 'error' => '平台钱包ETH手续费不足', 'date' => date('Y-m-d H:i:s')];
  1619. RedisService::set($cacheKey . "U_{$userId}:error", $error, 7200);
  1620. continue;
  1621. }
  1622. // 如果子钱包ETH不足手续费5个gwei,平台ETH充足则自动充值后下次调用时归集
  1623. if ($triggerEth < $triggerFree) {
  1624. $failedCount++;
  1625. $result = $this->ercTransfer($address, $triggerFree);
  1626. $error = ['data' => $v, 'usdt' => $triggerUsdt, 'triggerMin' => $triggerMin, 'triggerEth' => $triggerEth, 'transfer' => $result, 'error' => '归集钱包ETH手续费不足,先充值', 'date' => date('Y-m-d H:i:s')];
  1627. RedisService::set($cacheKey . "U_{$userId}:catch", $error, 7200);
  1628. continue;
  1629. }
  1630. // 满足归集条件处理
  1631. $result = $this->usdtErcTransfer($otcOutAddress, $triggerUsdt, $triggerAddress, $addressPrivate);
  1632. RedisService::set($cacheKey . "U_{$userId}:result", ['data' => $v, 'result' => $result, 'msg' => '归集已提交', 'date' => date('Y-m-d H:i:s')], 7200);
  1633. $count++;
  1634. } catch (\Exception $exception) {
  1635. $failedCount++;
  1636. RedisService::set($cacheKey . "U_{$userId}:exception", ['data' => $v, 'msg' => $exception->getMessage(), 'date' => date('Y-m-d H:i:s')], 7200);
  1637. }
  1638. }
  1639. // 超出分页数,下次处理下一页
  1640. if (count($addrList) >= 200) {
  1641. RedisService::set("caches:wallet:transferErcPage", $page + 1, 600);
  1642. }
  1643. if ($count > 0) {
  1644. return ['success' => $count, 'fail' => $failedCount];
  1645. } else {
  1646. $this->error = 1021;
  1647. return false;
  1648. }
  1649. } catch (\Exception $exception) {
  1650. $this->error = $exception->getMessage();
  1651. return false;
  1652. }
  1653. }
  1654. }