diff --git a/.env.development b/.env.development index 9bbbaec..e3b07e8 100644 --- a/.env.development +++ b/.env.development @@ -45,4 +45,10 @@ HASH_SALT='iG4Rpsa)6U31$H#^T85$^^3' # 游戏服, 支付上报地址 GAME_PAY_CB_URL=https://game2006api-test.kingsome.cn/webapp/index.php -REDIS=redis://127.0.0.1:6379/14 \ No newline at end of file +REDIS=redis://127.0.0.1:6379/14 + +# CHAIN_RPC_URL=https://arb1.arbitrum.io/rpc +CHAIN_RPC_URL=https://arb-mainnet.g.alchemy.com/v2/tFPlLg-MT8uGkM5wKHyoBqEHWJJ0o3Nt +CHAIN_CEG_ADDRESS=0x741482aE1480E552735E44Ff3A733448AcBbeD8d +CHAIN_WALLET_ADDRESS=0x9c257b7830566e889cd2eef906541f130d4a75f6 +CHAIN_GAS_BOOST=1.3 \ No newline at end of file diff --git a/src/api.server.ts b/src/api.server.ts index 376d54d..25024f3 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -9,6 +9,7 @@ import { ConnectOptions } from 'mongoose' import CodeTaskSchedule from 'schedule/codetask.schedule' import { PriceSvr } from 'service/price.svr' import { GooglePaySvr } from 'service/googlepay.svr' +import { GasSvr } from 'service/gas.svr' const zReqParserPlugin = require('plugins/zReqParser') @@ -90,6 +91,7 @@ export class ApiServer { initSchedules() { new CodeTaskSchedule().scheduleAll() new PriceSvr().scheduleAll() + new GasSvr().scheduleAll() } async connectDB() { diff --git a/src/controllers/alchemy.controller.ts b/src/controllers/alchemy.controller.ts index 8a5ac92..9549329 100644 --- a/src/controllers/alchemy.controller.ts +++ b/src/controllers/alchemy.controller.ts @@ -1,12 +1,13 @@ import logger from 'logger/logger' -import BaseController from 'common/base.controller' +import BaseController, { ROLE_ANON } from 'common/base.controller' import { ZError } from 'common/ZError' -import { router } from 'decorators/router' +import { role, router } from 'decorators/router' import { createOrder, queryFiat, queryPrice, refreshToken } from 'service/alchemy.svr' import { generateKVStr } from 'utils/net.util' import { PayRecord, PayStatus } from 'modules/PayRecord' import { PriceSvr } from 'service/price.svr' import { reportPayResult } from 'service/game.svr' +import { GasSvr } from 'service/gas.svr' const CALL_BACK_URL = `${process.env.ALCHEMY_PAY_CB_URL}/pay/out/alchemy/result` class AlchemyController extends BaseController { @@ -47,6 +48,9 @@ class AlchemyController extends BaseController { // 根据 `查询数字货币接口` 返回的数据, 预估可获得的数字货币数量 // 用户法币接口做简单的验证 let priceData = await queryPrice({ crypto, network, fiat, amount: fiatAmount, payWayCode, country }) + if (!priceData.success) { + throw new ZError(14, priceData.returnMsg || 'fetch price error') + } logger.debug('pirce data::', JSON.stringify(priceData)) let amountEstimate = priceData.data.cryptoAmount let record = new PayRecord({ @@ -101,19 +105,15 @@ class AlchemyController extends BaseController { if (!token || !chain) { throw new ZError(11, 'token or network not found') } - if ( - (chain.toLowerCase() === 'agor' || chain.toLowerCase() === 'eth') && - (token.toLowerCase() === 'ceg' || token.toLowerCase() === 'cec') - ) { - return { price: 1 } + // ceg价格固定为0.1 + if ((chain.toLowerCase() === 'agor' || chain.toLowerCase() === 'eth') && token.toLowerCase() === 'ceg') { + return { price: 0.1 } } if ((chain.toLowerCase() === 'agor' || chain.toLowerCase() === 'eth') && token.toLowerCase() === 'agor') { token = 'ETH' - chain = 'ETH' - } - if (token.toLowerCase() === 'ceg') { - return { price: 1 } + chain = 'ARBITRUM' } + let data = { crypto: token, network: chain, @@ -131,4 +131,12 @@ class AlchemyController extends BaseController { } return result.data } + + @role(ROLE_ANON) + @router('post /pay/alchemy/estimateGas') + async estimateGas(req, res) { + let { crypto, network } = req.params + let gas = await new GasSvr().estimateGas({ crypto, network }) + return { gas } + } } diff --git a/src/controllers/alchemyout.controller.ts b/src/controllers/alchemyout.controller.ts index 48962f1..267c148 100644 --- a/src/controllers/alchemyout.controller.ts +++ b/src/controllers/alchemyout.controller.ts @@ -9,6 +9,7 @@ import { TransferRecord } from 'modules/TransferRecord' import { reportPayResult } from 'service/game.svr' import { PriceSvr } from 'service/price.svr' import { OrderCacheSvr } from 'service/ordercache.svr' +import { GasSvr } from 'service/gas.svr' let errorRes = function (msg: string) { logger.info(`error res: ${msg}`) @@ -108,7 +109,8 @@ class AlchemyOutController extends BaseController { if (!checkSha1Sign(req.headers)) { return errorRes('sign error') } - let gas = 36000 * 0.0000000001 + let gas = await new GasSvr().estimateGas({ crypto, network: 'ARBITRUM' }) + gas = gas * 0.0000000001 let price = await new PriceSvr().fetchPrice({ crypto: 'ETH', network: 'ARBITRUM', fiat: 'USD' }) let networkFee = gas * parseFloat(price) let result = { diff --git a/src/service/chain.svr.ts b/src/service/chain.svr.ts index 144c914..8b1eedf 100644 --- a/src/service/chain.svr.ts +++ b/src/service/chain.svr.ts @@ -29,3 +29,71 @@ export async function fetchGasPrice(data) { } return axios(reqConfig) } +/** + success: { + "jsonrpc": "2.0", + "id": 1, + "result": "0x77b49" +} +error: { + "error": { + "code": 3, + "data": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001d45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000", + "message": "execution reverted: ERC20: insufficient allowance" + }, + "id": 1, + "jsonrpc": "2.0" +} + */ +export async function erc20TransferGas() { + let url = process.env.CHAIN_RPC_URL + let address = process.env.CHAIN_CEG_ADDRESS + let walletAddress = process.env.CHAIN_WALLET_ADDRESS + let reqData = { + jsonrpc: '2.0', + id: 1, + method: 'eth_estimateGas', + params: [ + { + data: '0x23b872dd0000000000000000000000009c257b7830566e889cd2eef906541f130d4a75f600000000000000000000000042448c6a38c08637218d8327b748f213fc2c02310000000000000000000000000000000000000000000000000000000000000000', + from: walletAddress, + to: address, + }, + ], + } + let reqConfig: any = { + method: 'post', + url, + headers: { + 'Content-Type': 'application/json', + }, + data: JSON.stringify(reqData), + } + return axios(reqConfig) +} + +export async function ethTransferGas() { + let url = process.env.CHAIN_RPC_URL + let walletAddress = process.env.CHAIN_WALLET_ADDRESS + let reqData = { + jsonrpc: '2.0', + id: 1, + method: 'eth_estimateGas', + params: [ + { + from: walletAddress, + to: walletAddress, + value: '0x0', + }, + ], + } + let reqConfig: any = { + method: 'post', + url, + headers: { + 'Content-Type': 'application/json', + }, + data: JSON.stringify(reqData), + } + return axios(reqConfig) +} diff --git a/src/service/gas.svr.ts b/src/service/gas.svr.ts new file mode 100644 index 0000000..e56add9 --- /dev/null +++ b/src/service/gas.svr.ts @@ -0,0 +1,60 @@ +import axios from 'axios' +import { singleton } from 'decorators/singleton' +import { queryPrice } from './alchemy.svr' +import * as schedule from 'node-schedule' +import logger from 'logger/logger' +import { erc20TransferGas, ethTransferGas } from './chain.svr' + +@singleton +export class GasSvr { + private priceMap: Map = new Map() + + public async estimateGas(data: any) { + let { crypto, network } = data + network = network || 'arbitrum' + let key = `${crypto}_${network}` + let price = this.priceMap.get(key) + if (!price) { + await this.refreshOne({ crypto, network }) + price = this.priceMap.get(key) + } + return price + } + + private async refreshOne(data) { + let { crypto, network } = data + let key = `${crypto}_${network}` + try { + let res + if (crypto.toLowerCase() === 'ceg') { + res = await erc20TransferGas() + } else if (crypto.toLowerCase() === 'eth' || crypto.toLowerCase() === 'agor') { + res = await ethTransferGas() + } + let priceData = res.data + if (!priceData.error) { + let gas = parseInt(priceData.result) * +process.env.CHAIN_GAS_BOOST + this.priceMap.set(key, gas) + } + } catch (e) { + logger.debug('update eth gas error: ' + e.message || e) + } + } + + private async refreshAll() { + for (let key of this.priceMap.keys()) { + let [crypto, chain] = key.split('_') + let data = { + crypto, + network: chain, + } + await this.refreshOne(data) + } + } + + scheduleAll() { + const job = schedule.scheduleJob('*/30 * * * * *', async () => { + this.refreshAll() + }) + } +} diff --git a/src/service/price.svr.ts b/src/service/price.svr.ts index 306b2c8..6a57766 100644 --- a/src/service/price.svr.ts +++ b/src/service/price.svr.ts @@ -33,14 +33,21 @@ export class PriceSvr { let key = `${crypto}_${network}_${fiat}` try { let priceData = await queryPrice(data) - this.priceMap.set(key, priceData.data.cryptoPrice + '') - } catch (e) { - logger.debug('update eth price with alchemy error: ' + e.message || e) - try { + if (priceData.success && priceData.data) { + this.priceMap.set(key, priceData.data.cryptoPrice + '') + } else if (crypto.toLowerCase() === 'eth') { let price = await this.queryEthPrice(crypto) this.priceMap.set(key, price + '') - } catch (e) { - logger.debug('update eth price with cryptocompare error: ' + e.message || e) + } + } catch (e) { + logger.debug('update eth price with alchemy error: ' + e.message || e) + if (crypto.toLowerCase() === 'eth') { + try { + let price = await this.queryEthPrice(crypto) + this.priceMap.set(key, price + '') + } catch (e) { + logger.debug('update eth price with cryptocompare error: ' + e.message || e) + } } } }