将utils移至公共模块
This commit is contained in:
parent
be9c8df67d
commit
1b94010249
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ dist
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
.env.development
|
.env.development
|
||||||
|
packages
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "packages/zutils"]
|
||||||
|
path = packages/zutils
|
||||||
|
url = git@git.kingsome.cn:zhanghongliang/zutils.git
|
@ -341,7 +341,7 @@ body:
|
|||||||
|
|
||||||
#### Request
|
#### Request
|
||||||
|
|
||||||
- URL:`/api/user/checkin/list?tag=`
|
- URL:`/api/user/checkin/list/:tag`
|
||||||
- 方法:`GET`
|
- 方法:`GET`
|
||||||
- 头部:
|
- 头部:
|
||||||
- Authorization: Bearer JWT_token
|
- Authorization: Bearer JWT_token
|
||||||
|
@ -40,7 +40,8 @@
|
|||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"siwe": "^2.1.4",
|
"siwe": "^2.1.4",
|
||||||
"tracer": "^1.1.6",
|
"tracer": "^1.1.6",
|
||||||
"web3": "^1.7.4"
|
"web3": "^1.7.4",
|
||||||
|
"zutils": "link:packages/zutils"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dotenv": "^8.2.0",
|
"@types/dotenv": "^8.2.0",
|
||||||
@ -57,6 +58,6 @@
|
|||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"tsconfig-paths": "^3.9.0",
|
"tsconfig-paths": "^3.9.0",
|
||||||
"tslint": "^6.1.1",
|
"tslint": "^6.1.1",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
packages/zutils
Submodule
1
packages/zutils
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 5aa4ffe02480098342c0deca0f9e641c56ce84f0
|
@ -1,6 +1,6 @@
|
|||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'decorators/singleton'
|
||||||
import { LotteryStats } from 'models/LotteryStats'
|
import { LotteryStats } from 'models/LotteryStats'
|
||||||
import { formatDate } from 'utils/date.util'
|
import { formatDate } from 'zutils/utils/date.util'
|
||||||
|
|
||||||
const EXPIRE_TIME = 1000 * 60 * 60
|
const EXPIRE_TIME = 1000 * 60 * 60
|
||||||
@singleton
|
@singleton
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { SyncLocker } from 'common/SyncLocker'
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { ZError } from 'common/ZError'
|
|
||||||
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
||||||
import { role, router } from 'decorators/router'
|
import { role, router } from 'decorators/router'
|
||||||
import { ActivityInfo } from 'models/ActivityInfo'
|
import { ActivityInfo } from 'models/ActivityInfo'
|
||||||
import { ActivityUser } from 'models/ActivityUser'
|
import { ActivityUser } from 'models/ActivityUser'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
import { RedisClient } from 'redis/RedisClient'
|
||||||
import { rankKey } from 'services/rank.svr'
|
import { rankKey } from 'services/rank.svr'
|
||||||
import { yesterday } from 'utils/date.util'
|
import { yesterday } from 'zutils/utils/date.util'
|
||||||
|
import { ZError } from 'zutils'
|
||||||
|
|
||||||
const MAX_LIMIT = 50
|
const MAX_LIMIT = 50
|
||||||
export default class ActivityController extends BaseController {
|
export default class ActivityController extends BaseController {
|
||||||
|
@ -6,7 +6,7 @@ import { FastifyRequest } from 'fastify'
|
|||||||
import { ActivityItem } from 'models/ActivityItem'
|
import { ActivityItem } from 'models/ActivityItem'
|
||||||
import { TokenClaimHistory } from 'models/TokenClaimHistory'
|
import { TokenClaimHistory } from 'models/TokenClaimHistory'
|
||||||
import { queryStakeList } from 'services/chain.svr'
|
import { queryStakeList } from 'services/chain.svr'
|
||||||
import { sign } from 'utils/chain.util'
|
import { sign } from 'zutils/utils/chain.util'
|
||||||
|
|
||||||
export default class ChainController extends BaseController {
|
export default class ChainController extends BaseController {
|
||||||
@router('get /api/stake/list')
|
@router('get /api/stake/list')
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { EMPTY_REWARD, ITEM_FRAME } from 'common/Constants'
|
import { EMPTY_REWARD } from 'common/Constants'
|
||||||
import { LotteryCache } from 'common/LotteryCache'
|
import { LotteryCache } from 'common/LotteryCache'
|
||||||
import { SyncLocker } from 'common/SyncLocker'
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { ZError } from 'common/ZError'
|
import { ZError } from 'common/ZError'
|
||||||
@ -7,11 +7,10 @@ import { FUSION_CFG } from 'configs/fusion'
|
|||||||
import { ALL_ITEMS } from 'configs/items'
|
import { ALL_ITEMS } from 'configs/items'
|
||||||
import { LOTTERY_CFG } from 'configs/lottery'
|
import { LOTTERY_CFG } from 'configs/lottery'
|
||||||
import { router } from 'decorators/router'
|
import { router } from 'decorators/router'
|
||||||
import { FastifyRequest } from 'fastify'
|
|
||||||
import { ActivityItem } from 'models/ActivityItem'
|
import { ActivityItem } from 'models/ActivityItem'
|
||||||
import { LotteryRecord } from 'models/LotteryRecord'
|
import { LotteryRecord } from 'models/LotteryRecord'
|
||||||
import { updateRankScore } from 'services/rank.svr'
|
import { updateRankScore } from 'services/rank.svr'
|
||||||
import { formatDate } from 'utils/date.util'
|
import { formatDate } from 'zutils/utils/date.util'
|
||||||
|
|
||||||
const ROUND = 1000000
|
const ROUND = 1000000
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
||||||
import { SyncLocker } from 'common/SyncLocker'
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { ZError } from 'common/ZError'
|
import { ZError } from 'zutils'
|
||||||
import { BOOST_CFG } from 'configs/boost'
|
import { BOOST_CFG } from 'configs/boost'
|
||||||
import { role, router } from 'decorators/router'
|
import { role, router } from 'decorators/router'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
@ -12,9 +12,9 @@ import { RedisClient } from 'redis/RedisClient'
|
|||||||
import { queryCheckInList } from 'services/chain.svr'
|
import { queryCheckInList } from 'services/chain.svr'
|
||||||
import { rankKey } from 'services/rank.svr'
|
import { rankKey } from 'services/rank.svr'
|
||||||
import { SiweMessage } from 'siwe'
|
import { SiweMessage } from 'siwe'
|
||||||
import { nextday, yesterday } from 'utils/date.util'
|
import { nextday } from 'zutils/utils/date.util'
|
||||||
import { checkParamsNeeded } from 'utils/net.util'
|
import { checkParamsNeeded } from 'zutils/utils/net.util'
|
||||||
import { aesDecrypt, base58ToHex } from 'utils/security.util'
|
import { aesDecrypt, base58ToHex } from 'zutils/utils/security.util'
|
||||||
|
|
||||||
const LOGIN_TIP = 'This signature is just to verify your identity'
|
const LOGIN_TIP = 'This signature is just to verify your identity'
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { router } from 'decorators/router'
|
|||||||
import { TaskCfg, TaskTypeEnum } from 'models/ActivityInfo'
|
import { TaskCfg, TaskTypeEnum } from 'models/ActivityInfo'
|
||||||
import { TaskStatus, TaskStatusEnum } from 'models/ActivityUser'
|
import { TaskStatus, TaskStatusEnum } from 'models/ActivityUser'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { formatDate } from 'utils/date.util'
|
import { formatDate } from 'zutils/utils/date.util'
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const prod = process.env.NODE_ENV === 'production'
|
const prod = process.env.NODE_ENV === 'production'
|
||||||
|
@ -2,7 +2,7 @@ import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType,
|
|||||||
import { dbconn } from 'decorators/dbconn'
|
import { dbconn } from 'decorators/dbconn'
|
||||||
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
import { genRandomString, sha512 } from 'utils/security.util'
|
import { genRandomString, sha512 } from 'zutils/utils/security.util'
|
||||||
import { PlatEnum } from '../enums/PlatEnum'
|
import { PlatEnum } from '../enums/PlatEnum'
|
||||||
/**
|
/**
|
||||||
* copy from wallet-svr
|
* copy from wallet-svr
|
||||||
|
@ -13,7 +13,7 @@ import { dbconn } from 'decorators/dbconn'
|
|||||||
import findOrCreate from 'mongoose-findorcreate'
|
import findOrCreate from 'mongoose-findorcreate'
|
||||||
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
import { convert } from 'utils/number.util'
|
import { convert } from 'zutils/utils/number.util'
|
||||||
|
|
||||||
const alphabet = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI'
|
const alphabet = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI'
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { plugin, ReturnModelType } from '@typegoose/typegoose'
|
|||||||
import findOrCreate from 'mongoose-findorcreate'
|
import findOrCreate from 'mongoose-findorcreate'
|
||||||
import { Connection } from 'mongoose'
|
import { Connection } from 'mongoose'
|
||||||
import { ObjectId } from 'bson'
|
import { ObjectId } from 'bson'
|
||||||
import { isTrue } from '../utils/string.util'
|
import { isTrue } from 'zutils/utils/string.util'
|
||||||
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'
|
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'
|
||||||
|
|
||||||
const jsonExcludeKeys = ['updatedAt', '__v']
|
const jsonExcludeKeys = ['updatedAt', '__v']
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
|
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
|
||||||
import { dbconn } from 'decorators/dbconn'
|
import { dbconn } from 'decorators/dbconn'
|
||||||
import { BaseModule } from '../Base'
|
import { BaseModule } from '../Base'
|
||||||
import { formatDate, yesterday } from 'utils/date.util'
|
import { formatDate, yesterday } from 'zutils/utils/date.util'
|
||||||
|
|
||||||
@dbconn('chain')
|
@dbconn('chain')
|
||||||
@index({ from: 1 }, { unique: false })
|
@index({ from: 1 }, { unique: false })
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { hmacSha256 } from 'utils/security.util'
|
import { hmacSha256 } from 'zutils/utils/security.util'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const SECRET_KEY = process.env.HASH_SALT
|
const SECRET_KEY = process.env.HASH_SALT
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ScoreRecord } from 'models/ScoreRecord'
|
import { ScoreRecord } from 'models/ScoreRecord'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
import { RedisClient } from 'redis/RedisClient'
|
||||||
import { formatDate } from 'utils/date.util'
|
import { formatDate } from 'zutils/utils/date.util'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新排行榜
|
* 更新排行榜
|
||||||
|
@ -1,218 +0,0 @@
|
|||||||
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}`
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
import { recoverTypedSignature, SignTypedDataVersion } from '@metamask/eth-sig-util'
|
|
||||||
import { soliditySha3, toWei } from 'web3-utils'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
|
|
||||||
export function recoverTypedSignatureV4(signObj: any, signature: string) {
|
|
||||||
return recoverTypedSignature({
|
|
||||||
data: signObj,
|
|
||||||
signature,
|
|
||||||
version: SignTypedDataVersion.V4,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatAddress(address: string) {
|
|
||||||
if (address.length >= 10) {
|
|
||||||
return address.substring(0, 8) + '...' + address.substring(address.length - 8)
|
|
||||||
} else if (address.length > 0 && address.length < 10) {
|
|
||||||
return address
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildLoginSignMsg(nonce: string, tips: string) {
|
|
||||||
const signMsg = {
|
|
||||||
tips,
|
|
||||||
nonce,
|
|
||||||
}
|
|
||||||
const signObj = {
|
|
||||||
types: {
|
|
||||||
EIP712Domain: [
|
|
||||||
{ name: 'name', type: 'string' },
|
|
||||||
{ name: 'version', type: 'string' },
|
|
||||||
],
|
|
||||||
set: [
|
|
||||||
{ name: 'tips', type: 'string' },
|
|
||||||
{ name: 'nonce', type: 'string' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
primaryType: 'set',
|
|
||||||
domain: {
|
|
||||||
name: 'Auth',
|
|
||||||
version: '1',
|
|
||||||
},
|
|
||||||
message: signMsg,
|
|
||||||
}
|
|
||||||
return signObj
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sign = async ({
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
amount,
|
|
||||||
saltNonce,
|
|
||||||
}: {
|
|
||||||
user: string
|
|
||||||
token: string
|
|
||||||
amount: number | string
|
|
||||||
saltNonce?: string
|
|
||||||
}) => {
|
|
||||||
const web3 = new Web3()
|
|
||||||
let privateKey = process.env.SIGN_PRIVATE_KEY
|
|
||||||
const acc = web3.eth.accounts.privateKeyToAccount(privateKey)
|
|
||||||
const account = web3.eth.accounts.wallet.add(acc)
|
|
||||||
const executor = account.address
|
|
||||||
const amountBn = toWei(amount + '')
|
|
||||||
const chainId = process.env.CHAIN
|
|
||||||
const claimContract = process.env.CLAIM_CONTRACT
|
|
||||||
const startTime = (Date.now() / 1000) | 0
|
|
||||||
saltNonce = saltNonce || ((Math.random() * 1000) | 0) + ''
|
|
||||||
let signStr = soliditySha3.apply(this, [user, token, claimContract, chainId, amountBn, startTime, saltNonce])
|
|
||||||
let signature = await web3.eth.sign(signStr, executor)
|
|
||||||
signature = signature.replace(/00$/, '1b').replace(/01$/, '1c')
|
|
||||||
return { token, amount: amountBn, startTime, saltNonce, signature }
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
// format the date to the format we want
|
|
||||||
export const formatDate = (date: Date): string => {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = (date.getMonth() + 1 + '').padStart(2, '0')
|
|
||||||
const day = (date.getDate() + '').padStart(2, '0')
|
|
||||||
return `${year}${month}${day}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// get formated datestring of yesterday
|
|
||||||
export const yesterday = (date?: Date) => {
|
|
||||||
date = date || new Date()
|
|
||||||
date.setDate(date.getDate() - 1)
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
export const nextday = (date?: Date) => {
|
|
||||||
date = date || new Date()
|
|
||||||
date.setDate(date.getDate() + 1)
|
|
||||||
return date
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
import { ZError } from 'common/ZError'
|
|
||||||
|
|
||||||
const TIMEOUT_ERROR = new Error('timeout')
|
|
||||||
|
|
||||||
const hexRe = /^[0-9A-Fa-f]+$/gu
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute fetch and verify that the response was successful.
|
|
||||||
*
|
|
||||||
* @param request - Request information.
|
|
||||||
* @param options - Fetch options.
|
|
||||||
* @returns The fetch response.
|
|
||||||
*/
|
|
||||||
export async function successfulFetch(request: string, options?: RequestInit) {
|
|
||||||
const response = await fetch(request, options)
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute fetch and return object response.
|
|
||||||
*
|
|
||||||
* @param request - The request information.
|
|
||||||
* @param options - The fetch options.
|
|
||||||
* @returns The fetch response JSON data.
|
|
||||||
*/
|
|
||||||
export async function handleFetch(request: string, options?: RequestInit) {
|
|
||||||
const response = await successfulFetch(request, options)
|
|
||||||
const object = await response.json()
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute fetch and return object response, log if known error thrown, otherwise rethrow error.
|
|
||||||
*
|
|
||||||
* @param request - the request options object
|
|
||||||
* @param request.url - The request url to query.
|
|
||||||
* @param request.options - The fetch options.
|
|
||||||
* @param request.timeout - Timeout to fail request
|
|
||||||
* @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context
|
|
||||||
* @returns The fetch response JSON data or undefined (if error occurs).
|
|
||||||
*/
|
|
||||||
export async function fetchWithErrorHandling({
|
|
||||||
url,
|
|
||||||
options,
|
|
||||||
timeout,
|
|
||||||
errorCodesToCatch,
|
|
||||||
}: {
|
|
||||||
url: string
|
|
||||||
options?: RequestInit
|
|
||||||
timeout?: number
|
|
||||||
errorCodesToCatch?: number[]
|
|
||||||
}) {
|
|
||||||
let result
|
|
||||||
try {
|
|
||||||
if (timeout) {
|
|
||||||
result = Promise.race([
|
|
||||||
await handleFetch(url, options),
|
|
||||||
new Promise<Response>((_, reject) =>
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(TIMEOUT_ERROR)
|
|
||||||
}, timeout),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
result = await handleFetch(url, options)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logOrRethrowError(e, errorCodesToCatch)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch that fails after timeout.
|
|
||||||
*
|
|
||||||
* @param url - Url to fetch.
|
|
||||||
* @param options - Options to send with the request.
|
|
||||||
* @param timeout - Timeout to fail request.
|
|
||||||
* @returns Promise resolving the request.
|
|
||||||
*/
|
|
||||||
export async function timeoutFetch(url: string, options?: RequestInit, timeout = 500): Promise<Response> {
|
|
||||||
return Promise.race([
|
|
||||||
successfulFetch(url, options),
|
|
||||||
new Promise<Response>((_, reject) =>
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(TIMEOUT_ERROR)
|
|
||||||
}, timeout),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method to log if error is a common fetch error and otherwise rethrow it.
|
|
||||||
*
|
|
||||||
* @param error - Caught error that we should either rethrow or log to console
|
|
||||||
* @param codesToCatch - array of error codes for errors we want to catch and log in a particular context
|
|
||||||
*/
|
|
||||||
function logOrRethrowError(error: any, codesToCatch: number[] = []) {
|
|
||||||
if (!error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const includesErrorCodeToCatch = codesToCatch.some(code =>
|
|
||||||
error.message.includes(`Fetch failed with status '${code}'`),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
error instanceof Error &&
|
|
||||||
(includesErrorCodeToCatch || error.message.includes('Failed to fetch') || error === TIMEOUT_ERROR)
|
|
||||||
) {
|
|
||||||
console.error(error)
|
|
||||||
} else {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateHeader() {
|
|
||||||
let random = function (start, end) {
|
|
||||||
return (Math.random() * (end - start) + start) | 0
|
|
||||||
}
|
|
||||||
let getIp = function () {
|
|
||||||
return `${random(1, 254)}.${random(1, 254)}.${random(1, 254)}.${random(1, 254)}`
|
|
||||||
}
|
|
||||||
let time = Date.now()
|
|
||||||
let useragent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${
|
|
||||||
(70 + Math.random() * 10) | 0
|
|
||||||
}.0.4324.${(Math.random() * 100) | 0} Safari/537.36`
|
|
||||||
const ip = getIp()
|
|
||||||
return {
|
|
||||||
'Refresh-Token': (time -= 5000),
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'User-Agent': useragent,
|
|
||||||
'X-Forwarded-For': ip,
|
|
||||||
'X-Real-IP': ip,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const checkParamsNeeded = (...args) => {
|
|
||||||
args.forEach(arg => {
|
|
||||||
if (!arg) {
|
|
||||||
throw new ZError(10, 'params mismatch')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
export const ONE_DAY = 24 * 60 * 60 * 1000
|
|
||||||
export const NFT_BEGIN_DAY = new Date(2023, 4, 8)
|
|
||||||
|
|
||||||
export const NFT_TYPE = {
|
|
||||||
badge1: 100, //2022NFT购买用户奖励徽章
|
|
||||||
badge2: 101,
|
|
||||||
badge3: 102,
|
|
||||||
badge4: 103,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MINT_CHANNEL = {
|
|
||||||
claim: '01', // 2022购买用户claim
|
|
||||||
}
|
|
||||||
|
|
||||||
// calc days between two Date
|
|
||||||
export function daysBetween(date1: Date, date2: Date) {
|
|
||||||
// hours*minutes*seconds*milliseconds
|
|
||||||
const diffInMs = Math.abs(date1.getTime() - date2.getTime())
|
|
||||||
const diffInDays = Math.round(diffInMs / ONE_DAY)
|
|
||||||
return diffInDays
|
|
||||||
}
|
|
@ -1,278 +0,0 @@
|
|||||||
import Web3 from 'web3'
|
|
||||||
import { BN } from 'ethereumjs-util'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts some token minimal unit to render format string, showing 5 decimals
|
|
||||||
*
|
|
||||||
* @param {Number|String|BN} tokenValue - Token value to convert
|
|
||||||
* @param {Number} decimals - Token decimals to convert
|
|
||||||
* @param {Number} decimalsToShow - Decimals to 5
|
|
||||||
* @returns {String} - Number of token minimal unit, in render format
|
|
||||||
* If value is less than 5 precision decimals will show '< 0.00001'
|
|
||||||
*/
|
|
||||||
export function renderFromTokenMinimalUnit(tokenValue, decimals, decimalsToShow = 5) {
|
|
||||||
const minimalUnit = fromTokenMinimalUnit(tokenValue || 0, decimals)
|
|
||||||
const minimalUnitNumber = parseFloat(minimalUnit)
|
|
||||||
let renderMinimalUnit
|
|
||||||
if (minimalUnitNumber < 0.00001 && minimalUnitNumber > 0) {
|
|
||||||
renderMinimalUnit = '< 0.00001'
|
|
||||||
} else {
|
|
||||||
const base = Math.pow(10, decimalsToShow)
|
|
||||||
renderMinimalUnit = (Math.round(minimalUnitNumber * base) / base).toString()
|
|
||||||
}
|
|
||||||
return renderMinimalUnit
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Converts token minimal unit to readable string value
|
|
||||||
*
|
|
||||||
* @param {number|string|Object} minimalInput - Token minimal unit to convert
|
|
||||||
* @param {string} decimals - Token decimals to convert
|
|
||||||
* @returns {string} - String containing the new number
|
|
||||||
*/
|
|
||||||
export function fromTokenMinimalUnit(minimalInput, decimals) {
|
|
||||||
minimalInput = addHexPrefix(Number(minimalInput).toString(16))
|
|
||||||
let minimal = safeNumberToBN(minimalInput)
|
|
||||||
const negative = minimal.lt(new BN(0))
|
|
||||||
const base = Web3.utils.toBN(Math.pow(10, decimals).toString())
|
|
||||||
|
|
||||||
if (negative) {
|
|
||||||
minimal = minimal.mul(new BN(-1))
|
|
||||||
}
|
|
||||||
let fraction = minimal.mod(base).toString(10)
|
|
||||||
while (fraction.length < decimals) {
|
|
||||||
fraction = '0' + fraction
|
|
||||||
}
|
|
||||||
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1]
|
|
||||||
const whole = minimal.div(base).toString(10)
|
|
||||||
let value = '' + whole + (fraction === '0' ? '' : '.' + fraction)
|
|
||||||
if (negative) {
|
|
||||||
value = '-' + value
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts wei to render format string, showing 5 decimals
|
|
||||||
*
|
|
||||||
* @param {Number|String|BN} value - Wei to convert
|
|
||||||
* @param {Number} decimalsToShow - Decimals to 5
|
|
||||||
* @returns {String} - Number of token minimal unit, in render format
|
|
||||||
* If value is less than 5 precision decimals will show '< 0.00001'
|
|
||||||
*/
|
|
||||||
export function renderFromWei(value, decimalsToShow = 5) {
|
|
||||||
let renderWei = '0'
|
|
||||||
// avoid undefined
|
|
||||||
if (value) {
|
|
||||||
const wei = Web3.utils.fromWei(value)
|
|
||||||
const weiNumber = parseFloat(wei)
|
|
||||||
if (weiNumber < 0.00001 && weiNumber > 0) {
|
|
||||||
renderWei = '< 0.00001'
|
|
||||||
} else {
|
|
||||||
const base = Math.pow(10, decimalsToShow)
|
|
||||||
renderWei = (Math.round(weiNumber * base) / base).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return renderWei
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts token BN value to hex string number to be sent
|
|
||||||
*
|
|
||||||
* @param {Object} value - BN instance to convert
|
|
||||||
* @param {number} decimals - Decimals to be considered on the conversion
|
|
||||||
* @returns {string} - String of the hex token value
|
|
||||||
*/
|
|
||||||
export function calcTokenValueToSend(value, decimals) {
|
|
||||||
return value ? (value * Math.pow(10, decimals)).toString(16) : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if a string is a valid decimal
|
|
||||||
*
|
|
||||||
* @param {string} value - String to check
|
|
||||||
* @returns {boolean} - True if the string is a valid decimal
|
|
||||||
*/
|
|
||||||
export function isDecimal(value) {
|
|
||||||
return Number.isFinite(parseFloat(value)) && !Number.isNaN(parseFloat(value)) && !isNaN(+value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a BN object from a string
|
|
||||||
*
|
|
||||||
* @param {string} value - Some numeric value represented as a string
|
|
||||||
* @returns {Object} - BN instance
|
|
||||||
*/
|
|
||||||
export function toBN(value) {
|
|
||||||
return Web3.utils.toBN(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prefixes a hex string with '0x' or '-0x' and returns it. Idempotent.
|
|
||||||
*
|
|
||||||
* @param {string} str - The string to prefix.
|
|
||||||
* @returns {string} The prefixed string.
|
|
||||||
*/
|
|
||||||
export const addHexPrefix = (str: string) => {
|
|
||||||
if (typeof str !== 'string' || str.match(/^-?0x/u)) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str.match(/^-?0X/u)) {
|
|
||||||
return str.replace('0X', '0x')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str.startsWith('-')) {
|
|
||||||
return str.replace('-', '-0x')
|
|
||||||
}
|
|
||||||
|
|
||||||
return `0x${str}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps 'numberToBN' method to avoid potential undefined and decimal values
|
|
||||||
*
|
|
||||||
* @param {number|string} value - number
|
|
||||||
* @returns {Object} - The converted value as BN instance
|
|
||||||
*/
|
|
||||||
export function safeNumberToBN(value: number | string) {
|
|
||||||
const safeValue = fastSplit(value.toString()) || '0'
|
|
||||||
return numberToBN(safeValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a fast string split and returns the first item of the string based on the divider provided
|
|
||||||
*
|
|
||||||
* @param {number|string} value - number/string to be splitted
|
|
||||||
* @param {string} divider - string value to use to split the string (default '.')
|
|
||||||
* @returns {string} - the selected splitted element
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function fastSplit(value, divider = '.') {
|
|
||||||
value += ''
|
|
||||||
const [from, to] = [value.indexOf(divider), 0]
|
|
||||||
return value.substring(from, to) || value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stripHexPrefix(str: string) {
|
|
||||||
if (typeof str !== 'string') {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
return str.slice(0, 2) === '0x' ? str.slice(2) : str
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberToBN(arg) {
|
|
||||||
if (typeof arg === 'string' || typeof arg === 'number') {
|
|
||||||
var multiplier = Web3.utils.toBN(1); // eslint-disable-line
|
|
||||||
var formattedString = String(arg).toLowerCase().trim()
|
|
||||||
var isHexPrefixed = formattedString.substr(0, 2) === '0x' || formattedString.substr(0, 3) === '-0x'
|
|
||||||
var stringArg = stripHexPrefix(formattedString); // eslint-disable-line
|
|
||||||
if (stringArg.substr(0, 1) === '-') {
|
|
||||||
stringArg = stripHexPrefix(stringArg.slice(1))
|
|
||||||
multiplier = Web3.utils.toBN(-1)
|
|
||||||
}
|
|
||||||
stringArg = stringArg === '' ? '0' : stringArg
|
|
||||||
|
|
||||||
if (
|
|
||||||
(!stringArg.match(/^-?[0-9]+$/) && stringArg.match(/^[0-9A-Fa-f]+$/)) ||
|
|
||||||
stringArg.match(/^[a-fA-F]+$/) ||
|
|
||||||
(isHexPrefixed === true && stringArg.match(/^[0-9A-Fa-f]+$/))
|
|
||||||
) {
|
|
||||||
return Web3.utils.toBN(stringArg).mul(multiplier)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((stringArg.match(/^-?[0-9]+$/) || stringArg === '') && isHexPrefixed === false) {
|
|
||||||
return Web3.utils.toBN(stringArg).mul(multiplier)
|
|
||||||
}
|
|
||||||
} else if (typeof arg === 'object' && arg.toString && !arg.pop && !arg.push) {
|
|
||||||
if (arg.toString(10).match(/^-?[0-9]+$/) && (arg.mul || arg.dividedToIntegerBy)) {
|
|
||||||
return Web3.utils.toBN(arg.toString(10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
'[number-to-bn] while converting number ' +
|
|
||||||
JSON.stringify(arg) +
|
|
||||||
' to BN.js instance, error: invalid number value. Value must be an integer, hex string, BN or BigNumber instance. Note, decimals are not supported.',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkRadixLegal(radix) {
|
|
||||||
return radix >= 2 && radix <= 62
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将letter转为纯数字
|
|
||||||
* @param {string} letter
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
function transformCharToNum(letter, base) {
|
|
||||||
if (base <= 36) {
|
|
||||||
letter = letter.toLowerCase()
|
|
||||||
}
|
|
||||||
if (letter >= '0' && letter <= '9') {
|
|
||||||
return parseInt(letter)
|
|
||||||
}
|
|
||||||
if (letter >= 'a' && letter <= 'z') {
|
|
||||||
return letter.charCodeAt(0) - 'a'.charCodeAt(0) + 10
|
|
||||||
}
|
|
||||||
if (letter >= 'A' && letter <= 'Z') {
|
|
||||||
return letter.charCodeAt(0) - 'A'.charCodeAt(0) + 36
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将数字转为进制里的字母
|
|
||||||
* @param {number} num
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function transformNumToChar(num, alphabet) {
|
|
||||||
alphabet = alphabet || '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
||||||
return alphabet.charAt(num)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将num从base进制转为to指定的进制
|
|
||||||
* @param {string} numStr 要转换的数字字符串
|
|
||||||
* @param {number} base num的进制
|
|
||||||
* @param {number} to 转换后的进制
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export function convert({
|
|
||||||
numStr,
|
|
||||||
base,
|
|
||||||
to,
|
|
||||||
alphabet,
|
|
||||||
}: {
|
|
||||||
numStr: string
|
|
||||||
base: number
|
|
||||||
to: number
|
|
||||||
alphabet?: string
|
|
||||||
}): string {
|
|
||||||
// 当base和to相等 或 base和to超出转换范围,则原样返回
|
|
||||||
if (base === to || !checkRadixLegal(base) || !checkRadixLegal(to)) {
|
|
||||||
return numStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先转成10进制
|
|
||||||
let p = 0
|
|
||||||
let number10 = 0
|
|
||||||
while (p < numStr.length) {
|
|
||||||
number10 *= base
|
|
||||||
number10 += transformCharToNum(numStr.charAt(p), base)
|
|
||||||
p++
|
|
||||||
}
|
|
||||||
// 若要转换的正好是进制,则直接返回
|
|
||||||
if (to === 10) {
|
|
||||||
return number10.toString()
|
|
||||||
}
|
|
||||||
let result = ''
|
|
||||||
let cur
|
|
||||||
while (number10) {
|
|
||||||
cur = number10 % to
|
|
||||||
result = transformNumToChar(cur, alphabet) + result
|
|
||||||
number10 = Math.floor(number10 / to)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
type RetryOptions = {
|
|
||||||
maxRetries: number
|
|
||||||
whitelistErrors: Error[]
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 使用:
|
|
||||||
* retry(() => fetch("https://example.com"), { maxRetries: 3, whitelistErrors: [] })
|
|
||||||
* .then((response) => console.log(response))
|
|
||||||
* .catch((error) => console.error(error));
|
|
||||||
* @param promiseFn
|
|
||||||
* @param options
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function retry<T>(promiseFn: () => Promise<T>, options: RetryOptions): Promise<T> {
|
|
||||||
let retries = 0
|
|
||||||
let defaultOptions = {
|
|
||||||
maxRetries: 3,
|
|
||||||
whitelistErrors: [],
|
|
||||||
}
|
|
||||||
Object.assign(defaultOptions, options)
|
|
||||||
const { maxRetries, whitelistErrors } = options
|
|
||||||
|
|
||||||
const retryPromise = async (): Promise<T> => {
|
|
||||||
try {
|
|
||||||
return await promiseFn()
|
|
||||||
} catch (err) {
|
|
||||||
if (
|
|
||||||
retries < maxRetries &&
|
|
||||||
whitelistErrors.some(whitelistedError => err instanceof whitelistedError.constructor)
|
|
||||||
) {
|
|
||||||
retries++
|
|
||||||
return retryPromise()
|
|
||||||
}
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retryPromise()
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 构建一个promise, 在
|
|
||||||
* usage:
|
|
||||||
* function delay(ms: number): Promise<void> {
|
|
||||||
const deferred = new Deferred<void>();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
deferred.resolve();
|
|
||||||
}, ms);
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("start");
|
|
||||||
|
|
||||||
delay(1000).then(() => {
|
|
||||||
console.log("after 1 second");
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("end");
|
|
||||||
*/
|
|
||||||
export class Deferred<T = any> {
|
|
||||||
private _resolve!: (value: T | PromiseLike<T>) => void
|
|
||||||
private _reject!: (reason?: any) => void
|
|
||||||
|
|
||||||
public readonly promise: Promise<T>
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.promise = new Promise<T>((resolve, reject) => {
|
|
||||||
this._resolve = resolve
|
|
||||||
this._reject = reject
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public resolve(value: T | PromiseLike<T>): void {
|
|
||||||
this._resolve(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public reject(reason?: any): void {
|
|
||||||
this._reject(reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
public then<TResult1 = T, TResult2 = never>(
|
|
||||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
|
|
||||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,
|
|
||||||
): Promise<TResult1 | TResult2> {
|
|
||||||
return this.promise.then(onfulfilled, onrejected)
|
|
||||||
}
|
|
||||||
|
|
||||||
public catch<TResult = never>(
|
|
||||||
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,
|
|
||||||
): Promise<T | TResult> {
|
|
||||||
return this.promise.catch(onrejected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 简单限流的 Promise 队列
|
|
||||||
* usage:
|
|
||||||
const q = new PromiseQueue();
|
|
||||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].forEach((v) => {
|
|
||||||
q.add(
|
|
||||||
() =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log(v);
|
|
||||||
resolve();
|
|
||||||
}, 1000);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
export class PromiseQueue {
|
|
||||||
private readonly concurrency: number
|
|
||||||
private _current: number = 0
|
|
||||||
private _list: (() => Promise<any>)[] = []
|
|
||||||
|
|
||||||
constructor({ concurrency = 2 }: { concurrency: number }) {
|
|
||||||
this.concurrency = concurrency
|
|
||||||
}
|
|
||||||
|
|
||||||
add(promiseFn: () => Promise<any>) {
|
|
||||||
this._list.push(promiseFn)
|
|
||||||
this.loadNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
loadNext() {
|
|
||||||
if (this._list.length === 0 || this.concurrency === this._current) return
|
|
||||||
this._current++
|
|
||||||
const fn = this._list.shift()!
|
|
||||||
const promise = fn.call(this)
|
|
||||||
promise.then(this.onLoaded.bind(this)).catch(this.onLoaded.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoaded() {
|
|
||||||
this._current--
|
|
||||||
this.loadNext()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
import crypto from 'crypto'
|
|
||||||
import CryptoJS from 'crypto-js'
|
|
||||||
|
|
||||||
export function hmac(input, key, out) {
|
|
||||||
return out
|
|
||||||
? crypto.createHmac('sha1', key).update(input).digest(out)
|
|
||||||
: crypto.createHmac('sha1', key).update(input).digest('hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
export function genRandomString(length) {
|
|
||||||
return crypto
|
|
||||||
.randomBytes(Math.ceil(length / 2))
|
|
||||||
.toString('hex')
|
|
||||||
.slice(0, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sha512(password, salt) {
|
|
||||||
let hash = crypto.createHmac('sha512', salt)
|
|
||||||
hash.update(password)
|
|
||||||
let value = hash.digest('hex')
|
|
||||||
return {
|
|
||||||
salt: salt,
|
|
||||||
passwordHash: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sha1(str) {
|
|
||||||
const md5sum = crypto.createHash('sha1')
|
|
||||||
md5sum.update(str)
|
|
||||||
str = md5sum.digest('hex')
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hmacSha256(str: string, key: any) {
|
|
||||||
const md5sum = crypto.createHmac('sha256', key)
|
|
||||||
md5sum.update(str)
|
|
||||||
const data = md5sum.digest('hex')
|
|
||||||
console.log(`HmacSHA256 rawContent is [${str}], key is [${key}], hash result is [${data}]`)
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
export function md5(str) {
|
|
||||||
const md5sum = crypto.createHash('md5')
|
|
||||||
md5sum.update(str)
|
|
||||||
str = md5sum.digest('hex')
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSign(secretKey, paramStr, timestamp) {
|
|
||||||
paramStr = `${paramStr}:${timestamp}:${secretKey}`
|
|
||||||
return sha1(paramStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkSign({
|
|
||||||
secretKey,
|
|
||||||
data,
|
|
||||||
sign,
|
|
||||||
signKeys,
|
|
||||||
}: {
|
|
||||||
secretKey: string
|
|
||||||
data: {}
|
|
||||||
sign: string
|
|
||||||
signKeys: string[]
|
|
||||||
}) {
|
|
||||||
signKeys.sort()
|
|
||||||
let signStr = ''
|
|
||||||
for (let key of signKeys) {
|
|
||||||
if (signStr.length > 0) {
|
|
||||||
signStr += '&'
|
|
||||||
}
|
|
||||||
signStr += `${key}=${data[key]}`
|
|
||||||
}
|
|
||||||
console.log(signStr)
|
|
||||||
let sign1 = hmacSha256(signStr, secretKey)
|
|
||||||
return sign1 === sign
|
|
||||||
}
|
|
||||||
|
|
||||||
// export function aesDecrypt(encrypted: string, key: string) {
|
|
||||||
// let bytes = aes.decrypt(encrypted, key)
|
|
||||||
// var originalText = bytes.toString(enc.Utf8);
|
|
||||||
// return originalText
|
|
||||||
// }
|
|
||||||
|
|
||||||
export const aesEncrypt = (plaintText, key) => {
|
|
||||||
key = CryptoJS.SHA1(key).toString().substring(0, 16)
|
|
||||||
key = CryptoJS.enc.Base64.parse(key)
|
|
||||||
let encryptedData = CryptoJS.AES.encrypt(plaintText, key, {
|
|
||||||
mode: CryptoJS.mode.ECB,
|
|
||||||
padding: CryptoJS.pad.Pkcs7,
|
|
||||||
})
|
|
||||||
|
|
||||||
return encryptedData.toString(CryptoJS.format.Hex)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const aesDecrypt = (encryptedDataHexStr, key) => {
|
|
||||||
key = CryptoJS.SHA1(key).toString().substring(0, 16)
|
|
||||||
key = CryptoJS.enc.Base64.parse(key)
|
|
||||||
let encryptedHex = CryptoJS.enc.Hex.parse(encryptedDataHexStr)
|
|
||||||
let encryptedBase64 = CryptoJS.enc.Base64.stringify(encryptedHex)
|
|
||||||
|
|
||||||
var decryptedData = CryptoJS.AES.decrypt(encryptedBase64, key, {
|
|
||||||
mode: CryptoJS.mode.ECB,
|
|
||||||
padding: CryptoJS.pad.Pkcs7,
|
|
||||||
})
|
|
||||||
|
|
||||||
return decryptedData.toString(CryptoJS.enc.Utf8)
|
|
||||||
}
|
|
||||||
|
|
||||||
const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
||||||
|
|
||||||
export const hexToBase58 = (hexString: string) => {
|
|
||||||
const bytes = hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
|
|
||||||
let base58String = ''
|
|
||||||
|
|
||||||
let num = BigInt('0x' + hexString)
|
|
||||||
while (num > BigInt(0)) {
|
|
||||||
const remainder = num % BigInt(58)
|
|
||||||
num = num / BigInt(58)
|
|
||||||
base58String = base58Alphabet[Number(remainder)] + base58String
|
|
||||||
}
|
|
||||||
|
|
||||||
return base58String
|
|
||||||
}
|
|
||||||
|
|
||||||
export const base58ToHex = (base58String: string) => {
|
|
||||||
const base58Length = base58String.length
|
|
||||||
let num = BigInt(0)
|
|
||||||
let leadingZeros = 0
|
|
||||||
for (let i = 0; i < base58Length; i++) {
|
|
||||||
const charIndex = base58Alphabet.indexOf(base58String[i])
|
|
||||||
if (charIndex === -1) {
|
|
||||||
throw new Error('Invalid Base58 string')
|
|
||||||
}
|
|
||||||
|
|
||||||
num = num * BigInt(58) + BigInt(charIndex)
|
|
||||||
}
|
|
||||||
return num.toString(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const hexToBase32 = (hexString: string) => {
|
|
||||||
const bytes = hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
|
|
||||||
const base32Alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
|
|
||||||
let base32String = ''
|
|
||||||
|
|
||||||
let num = BigInt('0x' + hexString)
|
|
||||||
while (num > BigInt(0)) {
|
|
||||||
const remainder = num % BigInt(32)
|
|
||||||
num = num / BigInt(32)
|
|
||||||
base32String = base32Alphabet[Number(remainder)] + base32String
|
|
||||||
}
|
|
||||||
|
|
||||||
return base32String
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* 根据key升序生成 key1=val1&key2=val2的字符串
|
|
||||||
* @param {object} data 需要处理的对象
|
|
||||||
* @param {boolean} ignoreNull 是否过滤空值(空格或者null值不参与拼接)
|
|
||||||
* @param splitChar 连接的字符, 默认是&
|
|
||||||
* @param equalChar =
|
|
||||||
*/
|
|
||||||
export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string = '&', equalChar = '=') {
|
|
||||||
const keys = Object.keys(data)
|
|
||||||
keys.sort()
|
|
||||||
let result = ''
|
|
||||||
let i = 0
|
|
||||||
for (let key of keys) {
|
|
||||||
if (ignoreNull && !data[key]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (i++ > 0) result += splitChar
|
|
||||||
result += `${key}${equalChar}${data[key]}`
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将key1=val&key2=val的字符串组装成对象
|
|
||||||
* @param str key1=val&key2=val的字符串
|
|
||||||
* @param splitChar 连接的字符, 默认是&
|
|
||||||
* @param equalChar =
|
|
||||||
*/
|
|
||||||
export function keyValToObject(str: string, splitChar: string = '&', equalChar = '='): {} {
|
|
||||||
let result = {}
|
|
||||||
if (!str) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
let arrs = str.split(splitChar)
|
|
||||||
for (let sub of arrs) {
|
|
||||||
let subArr = sub.split(equalChar)
|
|
||||||
result[subArr[0]] = subArr[1]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断传入的值是否为true
|
|
||||||
* @param {Object} obj 传入值为'true','TRUE',1,'1','on','ON','YES','yes'时,返回true,其他值均返回false
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
export function isTrue(obj) {
|
|
||||||
return (
|
|
||||||
obj === 'true' ||
|
|
||||||
obj === 'TRUE' ||
|
|
||||||
obj === 'True' ||
|
|
||||||
obj === 'on' ||
|
|
||||||
obj === 'ON' ||
|
|
||||||
obj === true ||
|
|
||||||
obj === 1 ||
|
|
||||||
obj === '1' ||
|
|
||||||
obj === 'YES' ||
|
|
||||||
obj === 'yes'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证ObjectId格式是否正确
|
|
||||||
* @param {string} id
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
export function isObjectId(id: string): boolean {
|
|
||||||
//mongoose.Types.ObjectId.isValid(id)
|
|
||||||
return /^[a-fA-F0-9]{24}$/.test(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 10进制 -> 62进制
|
|
||||||
* @param {string | number} number
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export function string10to62(number: string | number) {
|
|
||||||
const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ'.split('')
|
|
||||||
const radix = chars.length
|
|
||||||
let qutient = +number
|
|
||||||
const arr = []
|
|
||||||
do {
|
|
||||||
const mod = qutient % radix
|
|
||||||
qutient = (qutient - mod) / radix
|
|
||||||
arr.unshift(chars[mod])
|
|
||||||
} while (qutient)
|
|
||||||
return arr.join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 62进制 -> 10 进制
|
|
||||||
* @param {string} numberCode
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
export function string62to10(numberCode: string) {
|
|
||||||
const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ'
|
|
||||||
const radix = chars.length
|
|
||||||
numberCode = numberCode + ''
|
|
||||||
const len = numberCode.length
|
|
||||||
let i = 0
|
|
||||||
let originNumber = 0
|
|
||||||
while (i < len) {
|
|
||||||
originNumber += Math.pow(radix, i++) * (chars.indexOf(numberCode.charAt(len - i)) || 0)
|
|
||||||
}
|
|
||||||
return originNumber
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
import { renderFromTokenMinimalUnit } from './number.util'
|
|
||||||
import { asciiToHex } from 'web3-utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes IPFS protocol prefix from input string.
|
|
||||||
*
|
|
||||||
* @param ipfsUrl - An IPFS url (e.g. ipfs://{content id})
|
|
||||||
* @returns IPFS content identifier and (possibly) path in a string
|
|
||||||
* @throws Will throw if the url passed is not IPFS.
|
|
||||||
*/
|
|
||||||
export function removeIpfsProtocolPrefix(ipfsUrl: string) {
|
|
||||||
if (ipfsUrl.startsWith('ipfs://ipfs/')) {
|
|
||||||
return ipfsUrl.replace('ipfs://ipfs/', '')
|
|
||||||
} else if (ipfsUrl.startsWith('ipfs://')) {
|
|
||||||
return ipfsUrl.replace('ipfs://', '')
|
|
||||||
}
|
|
||||||
// this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true)
|
|
||||||
throw new Error('this method should not be used with non ipfs urls')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts content identifier and path from an input string.
|
|
||||||
*
|
|
||||||
* @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix
|
|
||||||
* @returns IFPS content identifier (cid) and sub path as string.
|
|
||||||
* @throws Will throw if the url passed is not ipfs.
|
|
||||||
*/
|
|
||||||
export function getIpfsCIDv1AndPath(ipfsUrl: string): {
|
|
||||||
cid: string
|
|
||||||
path?: string
|
|
||||||
} {
|
|
||||||
const url = removeIpfsProtocolPrefix(ipfsUrl)
|
|
||||||
|
|
||||||
// check if there is a path
|
|
||||||
// (CID is everything preceding first forward slash, path is everything after)
|
|
||||||
const index = url.indexOf('/')
|
|
||||||
const cid = index !== -1 ? url.substring(0, index) : url
|
|
||||||
const path = index !== -1 ? url.substring(index) : undefined
|
|
||||||
//TODO:
|
|
||||||
// We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats)
|
|
||||||
// because most cid v0s appear to be incompatible with IPFS subdomains
|
|
||||||
// return {
|
|
||||||
// cid: CID.parse(cid).toV1().toString(),
|
|
||||||
// path,
|
|
||||||
// };
|
|
||||||
return {
|
|
||||||
cid,
|
|
||||||
path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds URL protocol prefix to input URL string if missing.
|
|
||||||
*
|
|
||||||
* @param urlString - An IPFS URL.
|
|
||||||
* @returns A URL with a https:// prepended.
|
|
||||||
*/
|
|
||||||
export function addUrlProtocolPrefix(urlString: string): string {
|
|
||||||
if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) {
|
|
||||||
return `https://${urlString}`
|
|
||||||
}
|
|
||||||
return urlString
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats URL correctly for use retrieving assets hosted on IPFS.
|
|
||||||
*
|
|
||||||
* @param ipfsGateway - The users preferred IPFS gateway (full URL or just host).
|
|
||||||
* @param ipfsUrl - The IFPS URL pointed at the asset.
|
|
||||||
* @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not.
|
|
||||||
* @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS.
|
|
||||||
*/
|
|
||||||
export function getFormattedIpfsUrl(ipfsGateway: string, ipfsUrl: string, subdomainSupported: boolean): string {
|
|
||||||
const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway))
|
|
||||||
if (subdomainSupported) {
|
|
||||||
const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl)
|
|
||||||
return `${protocol}//${cid}.ipfs.${host}${path || ''}`
|
|
||||||
}
|
|
||||||
const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl)
|
|
||||||
return `${origin}/ipfs/${cidAndPath}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the given code corresponds to a smart contract.
|
|
||||||
*
|
|
||||||
* @param code - The potential smart contract code.
|
|
||||||
* @returns Whether the code was smart contract code or not.
|
|
||||||
*/
|
|
||||||
export function isSmartContractCode(code: string) {
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!code) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
|
|
||||||
const smartContractCode = code !== '0x' && code !== '0x0'
|
|
||||||
return smartContractCode
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatAddress(address: string) {
|
|
||||||
if (address.length >= 10) {
|
|
||||||
return address.substring(0, 6) + '...' + address.substring(address.length - 4)
|
|
||||||
} else if (address.length > 0 && address.length < 10) {
|
|
||||||
return address
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatMoney(balance: number | string, symbol: string) {
|
|
||||||
if (balance === '-') {
|
|
||||||
return `- ${symbol}`
|
|
||||||
}
|
|
||||||
let money = renderFromTokenMinimalUnit(balance, 18, 4)
|
|
||||||
return `${money} ${symbol}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成随机的bytes32的字符串
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function generateRandomBytes32() {
|
|
||||||
const v1 = (Math.random() * 9000000 + 1000000) | 0
|
|
||||||
const v2 = (Math.random() * 900000 + 100000) | 0
|
|
||||||
return asciiToHex(v1 + '' + v2)
|
|
||||||
}
|
|
12
yarn.lock
12
yarn.lock
@ -4590,10 +4590,10 @@ typedarray-to-buffer@^3.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
typescript@^4.1.3:
|
typescript@^5.3.3:
|
||||||
version "4.9.5"
|
version "5.3.3"
|
||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
|
||||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
|
||||||
|
|
||||||
ultron@~1.1.0:
|
ultron@~1.1.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
@ -5050,3 +5050,7 @@ yn@3.1.1:
|
|||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
||||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||||
|
|
||||||
|
"zutils@link:packages/zutils":
|
||||||
|
version "0.0.0"
|
||||||
|
uid ""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user