diff --git a/src/controllers/alchemyout.controller.ts b/src/controllers/alchemyout.controller.ts index dce732a..0247a56 100644 --- a/src/controllers/alchemyout.controller.ts +++ b/src/controllers/alchemyout.controller.ts @@ -2,7 +2,7 @@ import logger from 'logger/logger' import BaseController, { ROLE_ANON } from 'common/base.controller' import { ZError } from 'common/ZError' import { role, router } from 'decorators/router' -import { checkPayResultSign, checkSha1Sign, checkSimpleSign } from 'service/alchemy.svr' +import { calcNetworkFee, checkPayResultSign, checkSha1Sign, checkSimpleSign } from 'service/alchemy.svr' import { PayRecord, PayStatus } from 'modules/PayRecord' import { TransferQueue } from 'queue/transfer.queue' import { TransferRecord } from 'modules/TransferRecord' @@ -11,6 +11,7 @@ import { PriceSvr } from 'service/price.svr' import { OrderCacheSvr } from 'service/ordercache.svr' import { GasSvr } from 'service/gas.svr' import { parse } from 'dotenv' +import { toBigInt, toWei, fromWei, EtherUnits, toBigWei } from 'utils/number.util' let errorRes = function (msg: string) { logger.info(`error res: ${msg}`) @@ -110,19 +111,13 @@ class AlchemyOutController extends BaseController { if (!checkSha1Sign(req.headers)) { return errorRes('sign error') } - 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 priceToSend = '0.1' if (crypto.toLowerCase() === 'eth') { priceToSend = price + '' } // 20230717 沟通, gas费需要换算成目标货币 - let networkFee = gas - if (crypto.toLowerCase() === 'ceg') { - networkFee = networkFee * parseFloat(price) * 10 - } - + let { networkFee } = await calcNetworkFee(crypto, priceToSend, price) let result = { direct: 1, data: { @@ -130,7 +125,7 @@ class AlchemyOutController extends BaseController { networkList: [ { network: 'ARBITRUM', - networkFee: networkFee.toFixed(6), + networkFee: fromWei(networkFee), }, ], }, @@ -189,13 +184,10 @@ class AlchemyOutController extends BaseController { } record.cryptoAmount = record.network.toLowerCase() === 'agor' && record.crypto.toLowerCase() === 'agor' ? '0.001' : cryptoAmount - let gas = await new GasSvr().estimateGas({ crypto, network: 'ARBITRUM' }) - gas = gas * 0.0000000001 // 20230718 沟通, cryptoAmount包含了gas费, 需要减去后再发送 - let networkFee = gas - let price = await new PriceSvr().fetchPrice({ crypto: 'ETH', network: 'ARBITRUM', fiat: 'USD' }) - networkFee = (networkFee * parseFloat(price)) / parseFloat(cryptoPrice) - record.cryptoSend = (parseFloat(cryptoAmount) - networkFee).toFixed(10) + let { networkFee } = await calcNetworkFee(crypto, cryptoPrice) + record.cryptoSend = fromWei(toBigWei(cryptoAmount) - networkFee) + record.networkFee = fromWei(networkFee) record.cryptoPrice = cryptoPrice record.usdtAdmount = usdtAmount await record.save() diff --git a/src/controllers/internal.controller.ts b/src/controllers/internal.controller.ts index 7691e49..d96fde4 100644 --- a/src/controllers/internal.controller.ts +++ b/src/controllers/internal.controller.ts @@ -18,7 +18,7 @@ const notify = async function (record: DocumentType, subTask: Do let data: any = { orderNo: record.outOrderId, // AlchemyPay订单号 crypto: record.crypto, // 用户购买的数字货币 - cryptoAmount: record.cryptoAmount, //用户的提币数量 + cryptoAmount: record.cryptoSend, //用户的提币数量 cryptoPrice: record.cryptoPrice, // 实时的价格/USDT CEG锚定USDT txHash: record.txHash, // 给用户转账的hash network: record.network, // 用户购买的数字货币对应的网络 diff --git a/src/modules/PayRecord.ts b/src/modules/PayRecord.ts index 8a2db73..9f1b80e 100644 --- a/src/modules/PayRecord.ts +++ b/src/modules/PayRecord.ts @@ -61,6 +61,9 @@ export class PayRecordClass extends BaseModule { @prop() public cryptoSend?: string + @prop() + public networkFee?: string + // 开始创建记录时, 估算的可获取加密货币数量 @prop() public cryptoAmountEstimate?: string diff --git a/src/service/alchemy.svr.ts b/src/service/alchemy.svr.ts index cdd260f..33cf4f2 100644 --- a/src/service/alchemy.svr.ts +++ b/src/service/alchemy.svr.ts @@ -3,6 +3,9 @@ import { hmacsha256, sha1 } from 'utils/security.util' import crypto from 'crypto' import { generateKVStr } from 'utils/net.util' import logger from 'logger/logger' +import { GasSvr } from './gas.svr' +import { toBigInt, toBigWei } from 'utils/number.util' +import { PriceSvr } from './price.svr' export function createSimpleSign(data: any) { let timestamp = Date.now() @@ -221,3 +224,21 @@ export async function queryFiat() { let response = await axios(config) return response.data } +/** + * 计算发币需要的gas费, 换算成对应的数字货币 + * 返回的数值需要fromGwei转换成wei + * @param cryptoPrice + * @returns + */ +export async function calcNetworkFee(crypto: string, cryptoPrice: string, ethPrice?: string) { + let gasLimit = await new GasSvr().estimateGas({ crypto, network: 'ARBITRUM' }) + // let gas = gas * 0.0000000001 + const gasPriceArb = '100,000,000' // arbitrum one 上gasprice固定为0.1Gwei + let eth = toBigInt(gasLimit) * toBigInt(gasPriceArb) + // 20230718 沟通, cryptoAmount包含了gas费, 需要减去后再发送 + if (!ethPrice) { + ethPrice = await new PriceSvr().fetchPrice({ crypto: 'ETH', network: 'ARBITRUM', fiat: 'USD' }) + } + let networkFee = (eth * toBigWei(ethPrice)) / toBigWei(cryptoPrice) + return { networkFee, ethPrice } +} diff --git a/src/service/gas.svr.ts b/src/service/gas.svr.ts index e56add9..b66ee26 100644 --- a/src/service/gas.svr.ts +++ b/src/service/gas.svr.ts @@ -33,7 +33,7 @@ export class GasSvr { } let priceData = res.data if (!priceData.error) { - let gas = parseInt(priceData.result) * +process.env.CHAIN_GAS_BOOST + let gas = ((priceData.result | 1) * +process.env.CHAIN_GAS_BOOST) | 1 this.priceMap.set(key, gas) } } catch (e) { diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..8d38017 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,30 @@ +import { GasSvr } from 'service/gas.svr' +import { PriceSvr } from 'service/price.svr' +import { fromWei, toBigInt, toBigWei } from 'utils/number.util' + +async function main() { + let cryptoPrice = '0.1' + let cryptoAmount = '10.305' + let crypto = 'CEG' + let gasLimit = '1,031,856' + // let gas = gas * 0.0000000001 + let gas = toBigWei(gasLimit) / toBigInt('10,000,000,000') + // 20230718 沟通, cryptoAmount包含了gas费, 需要减去后再发送 + + let price = '1,980.2' + let networkFee = (gas * toBigWei(price)) / toBigWei(cryptoPrice) + console.log('networkFee::' + fromWei(networkFee)) + let cryptoSend = fromWei(toBigWei(cryptoAmount) - networkFee) + console.log(cryptoSend) + + // let gas = gas * 0.0000000001 + const gasPriceArb = '100,000,000' // arbitrum one 上gasprice固定为0.1Gwei + let eth = toBigInt(gasLimit) * toBigInt(gasPriceArb) + let ethPrice = '1,980.2' + let networkFee2 = (eth * toBigWei(ethPrice)) / toBigWei(cryptoPrice) + console.log('networkFee2::' + fromWei(networkFee2)) + let cryptoSend2 = fromWei(toBigWei(cryptoAmount) - networkFee2) + console.log(fromWei(toBigWei(cryptoSend2))) +} + +main() diff --git a/src/utils/number.util.ts b/src/utils/number.util.ts new file mode 100644 index 0000000..cd5733f --- /dev/null +++ b/src/utils/number.util.ts @@ -0,0 +1,218 @@ +export declare type HexString = string +export declare type Numbers = number | bigint | string | HexString + +const isHexStrict = hex => typeof hex === 'string' && /^((-)?0x[0-9a-f]+|(0x))$/i.test(hex) +export declare type ValidInputTypes = Uint8Array | bigint | string | number | boolean +export const isHex = (hex: ValidInputTypes): boolean => + typeof hex === 'number' || + typeof hex === 'bigint' || + (typeof hex === 'string' && /^((-0x|0x|-)?[0-9a-f]+|(0x))$/i.test(hex)) +const base = BigInt(10) +const expo10 = (expo: number) => base ** BigInt(expo) + +export const ethUnitMap = { + noether: BigInt('0'), + wei: BigInt(1), + kwei: expo10(3), + Kwei: expo10(3), + babbage: expo10(3), + femtoether: expo10(3), + mwei: expo10(6), + Mwei: expo10(6), + lovelace: expo10(6), + picoether: expo10(6), + gwei: expo10(9), + Gwei: expo10(9), + shannon: expo10(9), + nanoether: expo10(9), + nano: expo10(9), + szabo: expo10(12), + microether: expo10(12), + micro: expo10(12), + finney: expo10(15), + milliether: expo10(15), + milli: expo10(15), + ether: expo10(18), + kether: expo10(21), + grand: expo10(21), + mether: expo10(24), + gether: expo10(27), + tether: expo10(30), +} + +export type EtherUnits = keyof typeof ethUnitMap + +/** + * Converts value to it's number representation + */ +export const hexToNumber = (value: string): bigint | number => { + if (!isHexStrict(value)) { + throw new Error('Invalid hex string') + } + + const [negative, hexValue] = value.startsWith('-') ? [true, value.slice(1)] : [false, value] + const num = BigInt(hexValue) + + if (num > Number.MAX_SAFE_INTEGER) { + return negative ? -num : num + } + + if (num < Number.MIN_SAFE_INTEGER) { + return num + } + + return negative ? -1 * Number(num) : Number(num) +} + +export const toNumber = (value: Numbers): number | bigint => { + if (typeof value === 'number') { + return value + } + + if (typeof value === 'bigint') { + return value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER ? Number(value) : value + } + + if (typeof value === 'string' && isHexStrict(value)) { + return hexToNumber(value) + } + + try { + return toNumber(BigInt(value)) + } catch { + throw new Error('ivalid number: ' + value) + } +} + +/** + * Auto converts any given value into it's bigint representation + * + * @param value - The value to convert + * @returns - Returns the value in bigint representation + + * @example + * ```ts + * console.log(web3.utils.toBigInt(1)); + * > 1n + * ``` + */ +export const toBigInt = (value: unknown): bigint => { + if (typeof value === 'number') { + return BigInt(value) + } + + if (typeof value === 'bigint') { + return value + } + + // isHex passes for dec, too + if (typeof value === 'string' && isHex(value)) { + return BigInt(value) + } + + if (typeof value === 'string' && value.indexOf(',') >= 0) { + return BigInt(value.replace(/,/g, '')) + } + + throw new Error('invalid number' + value) +} + +export const toBigWei = (number: Numbers, unit: EtherUnits = 'ether'): bigint => { + return toBigInt(toWei(number, unit)) +} + +export const toWei = (number: Numbers, unit: EtherUnits = 'ether'): string => { + const denomination = ethUnitMap[unit] + + if (!denomination) { + throw new Error('error unit: ' + unit) + } + + // if value is decimal e.g. 24.56 extract `integer` and `fraction` part + // to avoid `fraction` to be null use `concat` with empty string + typeof number === 'string' && number.indexOf(',') >= 0 && (number = number.replace(/,/g, '')) + const [integer, fraction] = String(typeof number === 'string' && !isHexStrict(number) ? number : toNumber(number)) + .split('.') + .concat('') + + // join the value removing `.` from + // 24.56 -> 2456 + const value = BigInt(`${integer}${fraction}`) + + // multiply value with denomination + // 2456 * 1000000 -> 2456000000 + const updatedValue = value * denomination + + // count number of zeros in denomination + const numberOfZerosInDenomination = denomination.toString().length - 1 + + // check which either `fraction` or `denomination` have lower number of zeros + const decimals = Math.min(fraction.length, numberOfZerosInDenomination) + + if (decimals === 0) { + return updatedValue.toString() + } + + // Add zeros to make length equal to required decimal points + // If string is larger than decimal points required then remove last zeros + return updatedValue.toString().padStart(decimals, '0').slice(0, -decimals) +} + +/** + * Takes a number of wei and converts it to any other ether unit. + * @param number - The value in wei + * @param unit - The unit to convert to + * @returns - Returns the converted value in the given unit + * + * @example + * ```ts + * console.log(web3.utils.fromWei("1", "ether")); + * > 0.000000000000000001 + * + * console.log(web3.utils.fromWei("1", "shannon")); + * > 0.000000001 + * ``` + */ +export const fromWei = (number: Numbers, unit: EtherUnits = 'ether'): string => { + const denomination = ethUnitMap[unit] + + if (!denomination) { + throw new Error('invalid unit: ' + unit) + } + + // value in wei would always be integer + // 13456789, 1234 + const value = String(toNumber(number)) + + // count number of zeros in denomination + // 1000000 -> 6 + const numberOfZerosInDenomination = denomination.toString().length - 1 + + if (numberOfZerosInDenomination <= 0) { + return value.toString() + } + + // pad the value with required zeros + // 13456789 -> 13456789, 1234 -> 001234 + const zeroPaddedValue = value.padStart(numberOfZerosInDenomination, '0') + + // get the integer part of value by counting number of zeros from start + // 13456789 -> '13' + // 001234 -> '' + const integer = zeroPaddedValue.slice(0, -numberOfZerosInDenomination) + + // get the fraction part of value by counting number of zeros backward + // 13456789 -> '456789' + // 001234 -> '001234' + const fraction = zeroPaddedValue.slice(-numberOfZerosInDenomination).replace(/\.?0+$/, '') + + if (integer === '') { + return `0.${fraction}` + } + + if (fraction === '') { + return integer + } + + return `${integer}.${fraction}` +}