move some method to communal project
This commit is contained in:
parent
3dad368141
commit
69adf77a93
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
|
@ -50,7 +50,8 @@
|
|||||||
"nodemailer": "^6.9.1",
|
"nodemailer": "^6.9.1",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"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",
|
||||||
|
1
packages/zutils
Submodule
1
packages/zutils
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit d1fccbac2657731057ab788c89fba8b2f50da78f
|
@ -4,11 +4,10 @@ import * as dotenv from 'dotenv'
|
|||||||
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
||||||
dotenv.config({ path: envFile })
|
dotenv.config({ path: envFile })
|
||||||
import { IncomingMessage, Server, ServerResponse } from 'http'
|
import { IncomingMessage, Server, ServerResponse } from 'http'
|
||||||
import { RouterMap } from 'decorators/router'
|
|
||||||
import { mongoose } from '@typegoose/typegoose'
|
import { mongoose } from '@typegoose/typegoose'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
|
||||||
import { PriceSvr } from 'service/price.service'
|
import { PriceSvr } from 'service/price.service'
|
||||||
|
import { RouterMap, ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
const zReqParserPlugin = require('plugins/zReqParser')
|
const zReqParserPlugin = require('plugins/zReqParser')
|
||||||
|
|
||||||
@ -99,7 +98,7 @@ export class ApiServer {
|
|||||||
logger.log(`DB Connection Error: ${err.message}`)
|
logger.log(`DB Connection Error: ${err.message}`)
|
||||||
}
|
}
|
||||||
let opts = { url: process.env.REDIS }
|
let opts = { url: process.env.REDIS }
|
||||||
new RedisClient(opts)
|
new ZRedisClient(opts)
|
||||||
logger.log('REDIS Connected')
|
logger.log('REDIS Connected')
|
||||||
}
|
}
|
||||||
private initSchedules() {
|
private initSchedules() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'zutils'
|
||||||
import { TaskType } from 'models/RequestTask'
|
import { TaskType } from 'models/RequestTask'
|
||||||
import { ConfirmQueue } from 'queue/confirm.queue'
|
import { ConfirmQueue } from 'queue/confirm.queue'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
@ -8,7 +8,7 @@ import { ERC721Reactor } from './ERC721Reactor'
|
|||||||
import { HttpRetryProvider } from './HttpRetryProvider'
|
import { HttpRetryProvider } from './HttpRetryProvider'
|
||||||
import { WalletReactor } from './WalletReactor'
|
import { WalletReactor } from './WalletReactor'
|
||||||
import { DistributorReactor } from './DistributorReactor'
|
import { DistributorReactor } from './DistributorReactor'
|
||||||
import { fromTokenMinimalUnit, safeNumberToBN, toBN } from 'utils/number.util'
|
import { fromTokenMinimalUnit, safeNumberToBN } from 'zutils/utils/number.util'
|
||||||
import { AllChains } from './allchain'
|
import { AllChains } from './allchain'
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import { IPriceData } from 'structs/PriceData'
|
import { IPriceData } from 'structs/PriceData'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { timeoutFetch } from 'utils/net.util'
|
import { timeoutFetch } from 'zutils/utils/net.util'
|
||||||
import { getFormattedIpfsUrl } from 'utils/wallet.util'
|
import { getFormattedIpfsUrl } from 'zutils/utils/wallet.util'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { Contract } from 'web3-eth-contract'
|
import { Contract } from 'web3-eth-contract'
|
||||||
import { Account } from 'web3-core'
|
import { Account } from 'web3-core'
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import deepmerge from 'deepmerge'
|
import deepmerge from 'deepmerge'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { generateHeader } from 'utils/net.util'
|
import { generateHeader } from 'zutils/utils/net.util'
|
||||||
import { retry } from 'utils/promise.util'
|
import { retry } from 'zutils/utils/promise.util'
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
retry: {
|
retry: {
|
||||||
|
@ -2,7 +2,7 @@ import { Contract } from 'web3-eth-contract'
|
|||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { Account } from 'web3-core'
|
import { Account } from 'web3-core'
|
||||||
import { ZERO_BYTES32 } from 'common/Constants'
|
import { ZERO_BYTES32 } from 'common/Constants'
|
||||||
import { generateRandomBytes32 } from 'utils/wallet.util'
|
import { generateRandomBytes32 } from 'zutils/utils/wallet.util'
|
||||||
const abi = require('../../config/abis/BEMultiSigWallet.json').abi
|
const abi = require('../../config/abis/BEMultiSigWallet.json').abi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,22 +1,11 @@
|
|||||||
import fetch, { Response, RequestInit } from 'node-fetch'
|
import { timeoutFetch } from 'zutils/utils/net.util'
|
||||||
import { retry } from 'utils/promise.util'
|
import { retry } from 'zutils/utils/promise.util'
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT = 30000
|
const DEFAULT_TIMEOUT = 30000
|
||||||
const AbortController = globalThis.AbortController
|
|
||||||
|
|
||||||
const request = async (url: string, options: RequestInit) => {
|
const request = async (url: string, options: any) => {
|
||||||
options.timeout = options.timeout || DEFAULT_TIMEOUT
|
options.timeout = options.timeout || DEFAULT_TIMEOUT
|
||||||
const controller = new AbortController()
|
return timeoutFetch(url, options, options.timeout)
|
||||||
let res = Promise.race([
|
|
||||||
await fetch(url, { ...options, signal: controller.signal }),
|
|
||||||
new Promise<Response>((_, reject) =>
|
|
||||||
setTimeout(() => {
|
|
||||||
controller.abort()
|
|
||||||
reject(new Error('timeout'))
|
|
||||||
}, options.timeout),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestChain = async (rpc: string, method: string, params: any) => {
|
const requestChain = async (rpc: string, method: string, params: any) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { singleton } from '../decorators/singleton'
|
import { singleton } from 'zutils'
|
||||||
import Clock from './ClockTimer'
|
import Clock from './ClockTimer'
|
||||||
import { Delayed } from './Delayed'
|
import { Delayed } from './Delayed'
|
||||||
|
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
type Callback<T> = () => Promise<T>
|
|
||||||
|
|
||||||
export type AsyncQueue<T = void> = {
|
|
||||||
push: (task: Callback<T>) => Promise<T>
|
|
||||||
flush: () => Promise<void>
|
|
||||||
size: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that each callback pushed onto the queue is executed in series.
|
|
||||||
* Such a quetie 😻
|
|
||||||
* @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple
|
|
||||||
* tasks are pushed onto the queue while there is an active task, only the
|
|
||||||
* last one will be executed, once the active task has completed.
|
|
||||||
* e.g. in the below example, only 0 and 3 will be executed.
|
|
||||||
* ```
|
|
||||||
* const queue = createAsyncQueue({ dedupeConcurrent: true })
|
|
||||||
* queue.push(async () => console.log(0)) // returns 0
|
|
||||||
* queue.push(async () => console.log(1)) // returns 3
|
|
||||||
* queue.push(async () => console.log(2)) // returns 3
|
|
||||||
* queue.push(async () => console.log(3)) // returns 3
|
|
||||||
* ```
|
|
||||||
* */
|
|
||||||
export function createAsyncQueue<T = void>(opts = { dedupeConcurrent: false }): AsyncQueue<T> {
|
|
||||||
const { dedupeConcurrent } = opts
|
|
||||||
let queue: Callback<T>[] = []
|
|
||||||
let running: Promise<void> | undefined
|
|
||||||
let nextPromise = new DeferredPromise<T>()
|
|
||||||
const push = (task: Callback<T>) => {
|
|
||||||
let taskPromise = new DeferredPromise<T>()
|
|
||||||
if (dedupeConcurrent) {
|
|
||||||
queue = []
|
|
||||||
if (nextPromise.started) nextPromise = new DeferredPromise<T>()
|
|
||||||
taskPromise = nextPromise
|
|
||||||
}
|
|
||||||
queue.push(() => {
|
|
||||||
taskPromise.started = true
|
|
||||||
task().then(taskPromise.resolve).catch(taskPromise.reject)
|
|
||||||
return taskPromise.promise
|
|
||||||
})
|
|
||||||
if (!running) running = start()
|
|
||||||
return taskPromise.promise
|
|
||||||
}
|
|
||||||
const start = async () => {
|
|
||||||
while (queue.length) {
|
|
||||||
const task = queue.shift()!
|
|
||||||
await task().catch(() => {})
|
|
||||||
}
|
|
||||||
running = undefined
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
push,
|
|
||||||
flush: () => running || Promise.resolve(),
|
|
||||||
get size() {
|
|
||||||
return queue.length
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createAsyncQueues = <T = void>(opts = { dedupeConcurrent: false }) => {
|
|
||||||
const queues: { [queueId: string]: AsyncQueue<T> } = {}
|
|
||||||
const push = (queueId: string, task: Callback<T>) => {
|
|
||||||
if (!queues[queueId]) queues[queueId] = createAsyncQueue<T>(opts)
|
|
||||||
return queues[queueId].push(task)
|
|
||||||
}
|
|
||||||
const flush = (queueId: string) => {
|
|
||||||
if (!queues[queueId]) queues[queueId] = createAsyncQueue<T>(opts)
|
|
||||||
return queues[queueId].flush()
|
|
||||||
}
|
|
||||||
return { push, flush }
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeferredPromise<T = void, E = any> {
|
|
||||||
started = false
|
|
||||||
resolve: (x: T | PromiseLike<T>) => void = () => {}
|
|
||||||
reject: (x: E) => void = () => {}
|
|
||||||
promise: Promise<T>
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.promise = new Promise<T>((res, rej) => {
|
|
||||||
this.resolve = res
|
|
||||||
this.reject = rej
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// function main() {
|
|
||||||
// const queue = createAsyncQueue()
|
|
||||||
// queue.push(async () => {
|
|
||||||
// console.log(0)
|
|
||||||
// }) // returns 0
|
|
||||||
// queue.push(async () => {
|
|
||||||
// console.log(1)
|
|
||||||
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// setTimeout(() => {
|
|
||||||
// console.log('12')
|
|
||||||
// resolve()
|
|
||||||
// }, 1000)
|
|
||||||
// })
|
|
||||||
// }) // returns 3
|
|
||||||
// queue.push(async () => console.log(2)) // returns 3
|
|
||||||
// queue.push(async () => console.log(3)) // returns 3
|
|
||||||
// console.log('hi')
|
|
||||||
// }
|
|
||||||
|
|
||||||
// main()
|
|
@ -1,13 +0,0 @@
|
|||||||
import { FastifyError } from 'fastify'
|
|
||||||
|
|
||||||
export class ZError implements FastifyError {
|
|
||||||
code: string
|
|
||||||
statusCode?: number
|
|
||||||
message: string
|
|
||||||
name: string
|
|
||||||
|
|
||||||
constructor(statusCode: number, message: string) {
|
|
||||||
this.statusCode = statusCode
|
|
||||||
this.message = message
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import fastify = require('fastify')
|
|
||||||
|
|
||||||
export const ROLE_ANON = 'anon'
|
|
||||||
class BaseController {
|
|
||||||
aotoRoute(req: fastify.FastifyRequest, res) {}
|
|
||||||
}
|
|
||||||
export default BaseController
|
|
@ -1,11 +1,9 @@
|
|||||||
import BaseController from 'common/base.controller'
|
|
||||||
import { role, router } from 'decorators/router'
|
|
||||||
import { ChainTask, ChainTaskClass } from 'models/ChainTask'
|
import { ChainTask, ChainTaskClass } from 'models/ChainTask'
|
||||||
import { RequestTask } from 'models/RequestTask'
|
import { RequestTask } from 'models/RequestTask'
|
||||||
import { ChainQueue } from 'queue/chain.queue'
|
import { ChainQueue } from 'queue/chain.queue'
|
||||||
import { DocumentType } from '@typegoose/typegoose'
|
import { DocumentType } from '@typegoose/typegoose'
|
||||||
import { BlockChain } from 'chain/BlockChain'
|
import { BlockChain } from 'chain/BlockChain'
|
||||||
import { ZError } from 'common/ZError'
|
import { BaseController, role, router, ZError } from 'zutils'
|
||||||
|
|
||||||
class ChainController extends BaseController {
|
class ChainController extends BaseController {
|
||||||
@role('anon')
|
@role('anon')
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { ZError } from 'common/ZError'
|
|
||||||
import BaseController from 'common/base.controller'
|
|
||||||
import { role, router } from 'decorators/router'
|
|
||||||
import { CheckIn } from 'models/CheckIn'
|
import { CheckIn } from 'models/CheckIn'
|
||||||
import { NftHolder } from 'models/NftHolder'
|
import { NftHolder } from 'models/NftHolder'
|
||||||
import { getMonthBegin, getNDayAgo } from 'utils/date.util'
|
import { BaseController, role, router, ZError } from 'zutils'
|
||||||
|
import { getMonthBegin, getNDayAgo } from 'zutils/utils/date.util'
|
||||||
|
|
||||||
class TaskController extends BaseController {
|
class TaskController extends BaseController {
|
||||||
@role('anon')
|
@role('anon')
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { BlockChain } from 'chain/BlockChain'
|
import { BlockChain } from 'chain/BlockChain'
|
||||||
import { ZError } from 'common/ZError'
|
import { BaseController, role, router, ZError } from 'zutils'
|
||||||
import BaseController from 'common/base.controller'
|
|
||||||
import { role, router } from 'decorators/router'
|
|
||||||
|
|
||||||
class TokenController extends BaseController {
|
class TokenController extends BaseController {
|
||||||
@role('anon')
|
@role('anon')
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import { singleton } from './singleton'
|
import { singleton } from 'zutils'
|
||||||
|
|
||||||
@singleton
|
@singleton
|
||||||
export class NoJsonClass {
|
export class NoJsonClass {
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
import BaseController from '../common/base.controller'
|
|
||||||
|
|
||||||
export class RouterData {
|
|
||||||
target?: any
|
|
||||||
method?: string
|
|
||||||
path?: string
|
|
||||||
fun?: Function
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RouterMap {
|
|
||||||
static decoratedRouters: Map<
|
|
||||||
Function,
|
|
||||||
{
|
|
||||||
roles?: string[]
|
|
||||||
permissions?: string[][]
|
|
||||||
data?: RouterData[]
|
|
||||||
depts?: string[]
|
|
||||||
}
|
|
||||||
> = new Map()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function router(route?: string) {
|
|
||||||
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
|
||||||
if (!route) {
|
|
||||||
const controller = target.constructor.name
|
|
||||||
const controllerName = controller.toLowerCase().replace('.controller', '')
|
|
||||||
route = 'all ' + ['', controllerName, name].join('/')
|
|
||||||
}
|
|
||||||
const split = route.split(' ')
|
|
||||||
if (split.length > 2) {
|
|
||||||
throw new Error('路由中只允许一个空格')
|
|
||||||
}
|
|
||||||
const [method, path] = split
|
|
||||||
// @ts-ignore
|
|
||||||
const key = target[name]
|
|
||||||
let routerData = new RouterData()
|
|
||||||
routerData.target = target
|
|
||||||
routerData.method = method
|
|
||||||
routerData.path = path
|
|
||||||
// @ts-ignore
|
|
||||||
routerData.fun = target[name]
|
|
||||||
|
|
||||||
if (RouterMap.decoratedRouters.has(key)) {
|
|
||||||
let objCurrent = RouterMap.decoratedRouters.get(key)
|
|
||||||
if (!objCurrent.data) {
|
|
||||||
objCurrent.data = [routerData]
|
|
||||||
} else {
|
|
||||||
objCurrent.data.push(routerData)
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
|
||||||
} else {
|
|
||||||
let routerObj = {
|
|
||||||
data: [routerData],
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], routerObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function role(roles?: string | string[]) {
|
|
||||||
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
|
||||||
let roleList: string[] = []
|
|
||||||
if (roles) {
|
|
||||||
if (Array.isArray(roles)) {
|
|
||||||
roleList = roles
|
|
||||||
} else {
|
|
||||||
roleList = [roles]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
const key = target[name]
|
|
||||||
let roleObj = { roles: roleList }
|
|
||||||
if (RouterMap.decoratedRouters.has(key)) {
|
|
||||||
let objCurrent = RouterMap.decoratedRouters.get(key)
|
|
||||||
Object.assign(objCurrent, roleObj)
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], roleObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function permission(permissions?: string | string[]) {
|
|
||||||
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
|
||||||
let permissionList: string[][] = [[]]
|
|
||||||
if (permissions) {
|
|
||||||
if (Array.isArray(permissions)) {
|
|
||||||
let arr = []
|
|
||||||
for (let sub of permissions) {
|
|
||||||
arr.push(sub.split(':'))
|
|
||||||
}
|
|
||||||
permissionList = arr
|
|
||||||
} else {
|
|
||||||
permissionList = [permissions.split(':')]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
const key = target[name]
|
|
||||||
let permissionObj = { permissions: permissionList }
|
|
||||||
if (RouterMap.decoratedRouters.has(key)) {
|
|
||||||
let objCurrent = RouterMap.decoratedRouters.get(key)
|
|
||||||
Object.assign(objCurrent, permissionObj)
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], permissionObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有dept修饰器的, 需要验证部门id是否存在
|
|
||||||
*/
|
|
||||||
export function dept(depts?: string | string[]) {
|
|
||||||
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
|
||||||
let deptList: string[] = []
|
|
||||||
if (depts) {
|
|
||||||
if (Array.isArray(depts)) {
|
|
||||||
deptList = depts
|
|
||||||
} else {
|
|
||||||
deptList = [depts]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
const key = target[name]
|
|
||||||
let deptObj = { depts: deptList }
|
|
||||||
if (RouterMap.decoratedRouters.has(key)) {
|
|
||||||
let objCurrent = RouterMap.decoratedRouters.get(key)
|
|
||||||
Object.assign(objCurrent, deptObj)
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
RouterMap.decoratedRouters.set(target[name], deptObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* 单例化一个class
|
|
||||||
* 使用方法:
|
|
||||||
* @singleton
|
|
||||||
* class Test {}
|
|
||||||
* new Test() === new Test() // returns `true`
|
|
||||||
* 也可以不使用 decorator
|
|
||||||
* const TestSingleton = singleton(Test)
|
|
||||||
* new TestSingleton() === new TestSingleton() //returns 'true'
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const SINGLETON_KEY = Symbol()
|
|
||||||
|
|
||||||
export type Singleton<T extends new (...args: any[]) => any> = T & {
|
|
||||||
[SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never
|
|
||||||
}
|
|
||||||
export const singleton = <T extends new (...args: any[]) => any>(classTarget: T) =>
|
|
||||||
new Proxy(classTarget, {
|
|
||||||
construct(target: Singleton<T>, argumentsList, newTarget) {
|
|
||||||
// Skip proxy for children
|
|
||||||
if (target.prototype !== newTarget.prototype) {
|
|
||||||
return Reflect.construct(target, argumentsList, newTarget)
|
|
||||||
}
|
|
||||||
if (!target[SINGLETON_KEY]) {
|
|
||||||
target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)
|
|
||||||
}
|
|
||||||
return target[SINGLETON_KEY]
|
|
||||||
},
|
|
||||||
})
|
|
@ -1,6 +1,5 @@
|
|||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
|
||||||
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
||||||
dotenv.config({ path: envFile })
|
dotenv.config({ path: envFile })
|
||||||
|
|
||||||
@ -10,6 +9,7 @@ import 'common/Extend'
|
|||||||
import { AllChains, IChain } from 'chain/allchain'
|
import { AllChains, IChain } from 'chain/allchain'
|
||||||
import { EventBatchSvr } from 'service/event.batch.service'
|
import { EventBatchSvr } from 'service/event.batch.service'
|
||||||
import { IEventCfg } from 'interface/IEventCfg'
|
import { IEventCfg } from 'interface/IEventCfg'
|
||||||
|
import { ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
let svrs: any[] = []
|
let svrs: any[] = []
|
||||||
let lock = false
|
let lock = false
|
||||||
@ -55,7 +55,7 @@ async function parseAllEvents() {
|
|||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
let opts = { url: process.env.REDIS }
|
let opts = { url: process.env.REDIS }
|
||||||
new RedisClient(opts)
|
new ZRedisClient(opts)
|
||||||
logger.info('REDIS Connected')
|
logger.info('REDIS Connected')
|
||||||
await initEventSvrs()
|
await initEventSvrs()
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
|
@ -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'
|
||||||
import { logger } from '@typegoose/typegoose/lib/logSettings'
|
import { logger } from '@typegoose/typegoose/lib/logSettings'
|
||||||
|
|
||||||
@dbconn()
|
@dbconn()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
|
||||||
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
||||||
dotenv.config({ path: envFile })
|
dotenv.config({ path: envFile })
|
||||||
import { EventSyncSvr } from 'service/event.sync.service'
|
import { EventSyncSvr } from 'service/event.sync.service'
|
||||||
@ -8,6 +7,7 @@ import { NftTransferEvent } from 'models/NftTransferEvent'
|
|||||||
import { FtTransferEvent } from 'models/FtTransferEvent'
|
import { FtTransferEvent } from 'models/FtTransferEvent'
|
||||||
|
|
||||||
import 'common/Extend'
|
import 'common/Extend'
|
||||||
|
import { ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
let svrs: any[] = []
|
let svrs: any[] = []
|
||||||
let lock = false
|
let lock = false
|
||||||
@ -52,7 +52,7 @@ async function parseAllEvents() {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let opts = { url: process.env.REDIS }
|
let opts = { url: process.env.REDIS }
|
||||||
new RedisClient(opts)
|
new ZRedisClient(opts)
|
||||||
logger.info('REDIS Connected')
|
logger.info('REDIS Connected')
|
||||||
await initEventSvrs()
|
await initEventSvrs()
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { AsyncQueue, createAsyncQueue } from 'common/AsyncQueue'
|
import { AsyncQueue, createAsyncQueue, singleton } from 'zutils'
|
||||||
import { singleton } from 'decorators/singleton'
|
|
||||||
import { DocumentType } from '@typegoose/typegoose'
|
import { DocumentType } from '@typegoose/typegoose'
|
||||||
import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
||||||
import { BlockChain } from 'chain/BlockChain'
|
import { BlockChain } from 'chain/BlockChain'
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { AsyncQueue, createAsyncQueue } from 'common/AsyncQueue'
|
import { AsyncQueue, createAsyncQueue, singleton } from 'zutils'
|
||||||
import { singleton } from 'decorators/singleton'
|
|
||||||
import { DocumentType } from '@typegoose/typegoose'
|
import { DocumentType } from '@typegoose/typegoose'
|
||||||
import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
|
@ -1,306 +0,0 @@
|
|||||||
import { resolveCname } from 'dns'
|
|
||||||
import redis from 'redis'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { singleton } from '../decorators/singleton'
|
|
||||||
|
|
||||||
type Callback = (...args: any[]) => void
|
|
||||||
|
|
||||||
@singleton
|
|
||||||
export class RedisClient {
|
|
||||||
public pub: redis.RedisClient
|
|
||||||
public sub: redis.RedisClient
|
|
||||||
|
|
||||||
protected subscribeAsync: any
|
|
||||||
protected unsubscribeAsync: any
|
|
||||||
protected publishAsync: any
|
|
||||||
|
|
||||||
protected subscriptions: { [channel: string]: Callback[] } = {}
|
|
||||||
|
|
||||||
protected smembersAsync: any
|
|
||||||
protected sismemberAsync: any
|
|
||||||
protected hgetAsync: any
|
|
||||||
protected hlenAsync: any
|
|
||||||
protected pubsubAsync: any
|
|
||||||
protected incrAsync: any
|
|
||||||
protected decrAsync: any
|
|
||||||
|
|
||||||
constructor(opts?: redis.ClientOpts) {
|
|
||||||
this.sub = redis.createClient(opts)
|
|
||||||
this.pub = redis.createClient(opts)
|
|
||||||
|
|
||||||
// no listener limit
|
|
||||||
this.sub.setMaxListeners(0)
|
|
||||||
|
|
||||||
// create promisified pub/sub methods.
|
|
||||||
this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub)
|
|
||||||
this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub)
|
|
||||||
|
|
||||||
this.publishAsync = promisify(this.pub.publish).bind(this.pub)
|
|
||||||
|
|
||||||
// create promisified redis methods.
|
|
||||||
this.smembersAsync = promisify(this.pub.smembers).bind(this.pub)
|
|
||||||
this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub)
|
|
||||||
this.hlenAsync = promisify(this.pub.hlen).bind(this.pub)
|
|
||||||
this.hgetAsync = promisify(this.pub.hget).bind(this.pub)
|
|
||||||
this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub)
|
|
||||||
this.decrAsync = promisify(this.pub.decr).bind(this.pub)
|
|
||||||
this.incrAsync = promisify(this.pub.incr).bind(this.pub)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async subscribe(topic: string, callback: Callback) {
|
|
||||||
if (!this.subscriptions[topic]) {
|
|
||||||
this.subscriptions[topic] = []
|
|
||||||
}
|
|
||||||
|
|
||||||
this.subscriptions[topic].push(callback)
|
|
||||||
|
|
||||||
if (this.sub.listeners('message').length === 0) {
|
|
||||||
this.sub.addListener('message', this.handleSubscription)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.subscribeAsync(topic)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public async unsubscribe(topic: string, callback?: Callback) {
|
|
||||||
if (callback) {
|
|
||||||
const index = this.subscriptions[topic].indexOf(callback)
|
|
||||||
this.subscriptions[topic].splice(index, 1)
|
|
||||||
} else {
|
|
||||||
this.subscriptions[topic] = []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.subscriptions[topic].length === 0) {
|
|
||||||
await this.unsubscribeAsync(topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public async publish(topic: string, data: any) {
|
|
||||||
if (data === undefined) {
|
|
||||||
data = false
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.publishAsync(topic, JSON.stringify(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
public async exists(roomId: string): Promise<boolean> {
|
|
||||||
return (await this.pubsubAsync('channels', roomId)).length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setex(key: string, value: string, seconds: number) {
|
|
||||||
return new Promise(resolve => this.pub.setex(key, seconds, value, resolve))
|
|
||||||
}
|
|
||||||
|
|
||||||
public async expire(key: string, seconds: number) {
|
|
||||||
return new Promise(resolve => this.pub.expire(key, seconds, resolve))
|
|
||||||
}
|
|
||||||
|
|
||||||
public async get(key: string): Promise<string | null> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.get(key, (err, data: string | null) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async set(key: string, val: string) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.pub.set(key, val, () => {
|
|
||||||
resolve && resolve('')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async del(roomId: string) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.pub.del(roomId, resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async sadd(key: string, value: any) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.pub.sadd(key, value, resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async smembers(key: string): Promise<string[]> {
|
|
||||||
return await this.smembersAsync(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async sismember(key: string, field: string): Promise<number> {
|
|
||||||
return await this.sismemberAsync(key, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async srem(key: string, value: any) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.pub.srem(key, value, resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async scard(key: string) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.scard(key, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
public async srandmember(key: string) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.srandmember(key, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async sinter(...keys: string[]) {
|
|
||||||
return new Promise<string[]>((resolve, reject) => {
|
|
||||||
this.pub.sinter(...keys, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async zadd(key: string, value: any, member: string) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.pub.zadd(key, value, member, resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
public async zrangebyscore(key: string, min: number, max: number) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async zcard(key: string) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.zcard(key, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async zcount(key: string, min: number, max: number) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.zcount(key, min, max, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async zrevrank(key: string, member: string) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.zrevrank(key, member, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async zscore(key: string, member: string) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.zscore(key, member, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async zrevrange(key: string, start: number, end: number) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.zrevrange(key, start, end, 'withscores', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async hset(key: string, field: string, value: string) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.pub.hset(key, field, value, resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async hincrby(key: string, field: string, value: number) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.pub.hincrby(key, field, value, resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async hget(key: string, field: string) {
|
|
||||||
return await this.hgetAsync(key, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async hgetall(key: string) {
|
|
||||||
return new Promise<{ [key: string]: string }>((resolve, reject) => {
|
|
||||||
this.pub.hgetall(key, (err, values) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(values)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async hdel(key: string, field: string) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.pub.hdel(key, field, (err, ok) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve(ok)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async hlen(key: string): Promise<number> {
|
|
||||||
return await this.hlenAsync(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async incr(key: string): Promise<number> {
|
|
||||||
return await this.incrAsync(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async decr(key: string): Promise<number> {
|
|
||||||
return await this.decrAsync(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handleSubscription = (channel: string, message: string) => {
|
|
||||||
if (this.subscriptions[channel]) {
|
|
||||||
for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) {
|
|
||||||
this.subscriptions[channel][i](JSON.parse(message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { BlockChain } from 'chain/BlockChain'
|
import { BlockChain } from 'chain/BlockChain'
|
||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'zutils'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import * as schedule from 'node-schedule'
|
import * as schedule from 'node-schedule'
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
|
||||||
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
||||||
dotenv.config({ path: envFile })
|
dotenv.config({ path: envFile })
|
||||||
const scriptions = require('../config/scription_list.json')
|
const scriptions = require('../config/scription_list.json')
|
||||||
@ -11,6 +10,7 @@ import { BlockSyncSvr } from 'service/block.sync.service'
|
|||||||
import { IScriptionCfg } from 'interface/IScriptionCfg'
|
import { IScriptionCfg } from 'interface/IScriptionCfg'
|
||||||
import { buildScriptionFilters } from 'utils/block.util'
|
import { buildScriptionFilters } from 'utils/block.util'
|
||||||
import { CheckIn } from 'models/CheckIn'
|
import { CheckIn } from 'models/CheckIn'
|
||||||
|
import { ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
let svrs: any[] = []
|
let svrs: any[] = []
|
||||||
let lock = false
|
let lock = false
|
||||||
@ -69,7 +69,7 @@ async function parseAllEvents() {
|
|||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
let opts = { url: process.env.REDIS }
|
let opts = { url: process.env.REDIS }
|
||||||
new RedisClient(opts)
|
new ZRedisClient(opts)
|
||||||
logger.info('REDIS Connected')
|
logger.info('REDIS Connected')
|
||||||
await initEventSvrs()
|
await initEventSvrs()
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
|
@ -2,9 +2,10 @@ import { IChain } from 'chain/allchain'
|
|||||||
import { retryEthBlockNumber } from 'chain/chain.api'
|
import { retryEthBlockNumber } from 'chain/chain.api'
|
||||||
import { IScriptionCfg } from 'interface/IScriptionCfg'
|
import { IScriptionCfg } from 'interface/IScriptionCfg'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
|
||||||
import { getPastBlocksIter } from 'utils/block.util'
|
import { getPastBlocksIter } from 'utils/block.util'
|
||||||
import { formatDate } from 'utils/date.util'
|
import { ZRedisClient } from 'zutils'
|
||||||
|
import { formatDate } from 'zutils/utils/date.util'
|
||||||
|
|
||||||
export class BlockSyncSvr {
|
export class BlockSyncSvr {
|
||||||
chainCfg: IChain
|
chainCfg: IChain
|
||||||
@ -28,7 +29,7 @@ export class BlockSyncSvr {
|
|||||||
|
|
||||||
async execute() {
|
async execute() {
|
||||||
let currentBlock = await retryEthBlockNumber(this.rpc)
|
let currentBlock = await retryEthBlockNumber(this.rpc)
|
||||||
let blockStr = await new RedisClient().get(this.redisKey)
|
let blockStr = await new ZRedisClient().get(this.redisKey)
|
||||||
if (blockStr) {
|
if (blockStr) {
|
||||||
this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock)
|
this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock)
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@ import { batchEthLogs, ethGetLogs, retryEthBlockNumber } from 'chain/chain.api'
|
|||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { GeneralEvent } from 'models/GeneralEvent'
|
import { GeneralEvent } from 'models/GeneralEvent'
|
||||||
|
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
import { decodeEvent, getTopics } from 'zutils/utils/chain.util'
|
||||||
import { decodeEvent, getTopics } from 'utils/event.util'
|
|
||||||
|
|
||||||
import { NftHolder } from 'models/NftHolder'
|
import { NftHolder } from 'models/NftHolder'
|
||||||
import { TokenHolder } from 'models/TokenHolder'
|
import { TokenHolder } from 'models/TokenHolder'
|
||||||
import { NftStake } from 'models/NftStake'
|
import { NftStake } from 'models/NftStake'
|
||||||
import { IEventCfg } from 'interface/IEventCfg'
|
import { IEventCfg } from 'interface/IEventCfg'
|
||||||
|
import { ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
let eventProcessers = {
|
let eventProcessers = {
|
||||||
NftHolder: NftHolder,
|
NftHolder: NftHolder,
|
||||||
@ -31,7 +31,7 @@ export class EventBatchSvr {
|
|||||||
for (let cfg of this.eventCfgs) {
|
for (let cfg of this.eventCfgs) {
|
||||||
this.fromBlock = Math.min(this.fromBlock, cfg.fromBlock)
|
this.fromBlock = Math.min(this.fromBlock, cfg.fromBlock)
|
||||||
if (!cfg.topic) {
|
if (!cfg.topic) {
|
||||||
cfg.topic = getTopics(cfg)
|
cfg.topic = getTopics(cfg.abi)
|
||||||
}
|
}
|
||||||
this.processer.set((cfg.address + cfg.topic).toLowerCase(), cfg)
|
this.processer.set((cfg.address + cfg.topic).toLowerCase(), cfg)
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ export class EventBatchSvr {
|
|||||||
async execute() {
|
async execute() {
|
||||||
let currentBlock = await retryEthBlockNumber(this.rpc)
|
let currentBlock = await retryEthBlockNumber(this.rpc)
|
||||||
let toBlock = parseInt(currentBlock.result, 16)
|
let toBlock = parseInt(currentBlock.result, 16)
|
||||||
let blockStr = await new RedisClient().get(this.redisKey)
|
let blockStr = await new ZRedisClient().get(this.redisKey)
|
||||||
if (blockStr) {
|
if (blockStr) {
|
||||||
this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock)
|
this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock)
|
||||||
}
|
}
|
||||||
@ -105,9 +105,9 @@ export class EventBatchSvr {
|
|||||||
for (let cfg of this.eventCfgs) {
|
for (let cfg of this.eventCfgs) {
|
||||||
cfg.fromBlock = nextBlock
|
cfg.fromBlock = nextBlock
|
||||||
const redisKey = this.buildRedisKey(cfg)
|
const redisKey = this.buildRedisKey(cfg)
|
||||||
await new RedisClient().set(redisKey, cfg.fromBlock + '')
|
await new ZRedisClient().set(redisKey, cfg.fromBlock + '')
|
||||||
}
|
}
|
||||||
await new RedisClient().set(this.redisKey, nextBlock + '')
|
await new ZRedisClient().set(this.redisKey, nextBlock + '')
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRedisKey(cfg: IEventCfg) {
|
buildRedisKey(cfg: IEventCfg) {
|
||||||
@ -116,7 +116,7 @@ export class EventBatchSvr {
|
|||||||
|
|
||||||
async fixBlockNumber(cfg: IEventCfg) {
|
async fixBlockNumber(cfg: IEventCfg) {
|
||||||
const redisKey = this.buildRedisKey(cfg)
|
const redisKey = this.buildRedisKey(cfg)
|
||||||
let blockStr = await new RedisClient().get(redisKey)
|
let blockStr = await new ZRedisClient().get(redisKey)
|
||||||
if (blockStr) {
|
if (blockStr) {
|
||||||
cfg.fromBlock = Math.max(parseInt(blockStr), cfg.fromBlock)
|
cfg.fromBlock = Math.max(parseInt(blockStr), cfg.fromBlock)
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ export class EventBatchSvr {
|
|||||||
}
|
}
|
||||||
event.chain = this.chainCfg.id + ''
|
event.chain = this.chainCfg.id + ''
|
||||||
event.event = cfg.event
|
event.event = cfg.event
|
||||||
let result = decodeEvent(cfg, event)
|
let result = decodeEvent(cfg.abi, event)
|
||||||
// cfg.fromBlock = Math.max (parseInt(event.blockNumber, 16) + 1, cfg.fromBlock)
|
// cfg.fromBlock = Math.max (parseInt(event.blockNumber, 16) + 1, cfg.fromBlock)
|
||||||
event.decodedData = result
|
event.decodedData = result
|
||||||
const record = await GeneralEvent.saveEvent(event)
|
const record = await GeneralEvent.saveEvent(event)
|
||||||
|
@ -2,10 +2,10 @@ import assert from 'assert'
|
|||||||
import { AllChains } from 'chain/allchain'
|
import { AllChains } from 'chain/allchain'
|
||||||
import { HttpRetryProvider } from 'chain/HttpRetryProvider'
|
import { HttpRetryProvider } from 'chain/HttpRetryProvider'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
|
||||||
|
|
||||||
import { clearTimeCache, getPastEventsIter, processEvents } from 'utils/contract.util'
|
import { clearTimeCache, getPastEventsIter, processEvents } from 'utils/contract.util'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
|
import { ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
export class EventSyncSvr {
|
export class EventSyncSvr {
|
||||||
web3: Web3
|
web3: Web3
|
||||||
@ -47,7 +47,7 @@ export class EventSyncSvr {
|
|||||||
|
|
||||||
async parseEvents() {
|
async parseEvents() {
|
||||||
let currentBlock = await this.web3.eth.getBlockNumber()
|
let currentBlock = await this.web3.eth.getBlockNumber()
|
||||||
let blockStr = await new RedisClient().get(this.blockKey)
|
let blockStr = await new ZRedisClient().get(this.blockKey)
|
||||||
if (blockStr) {
|
if (blockStr) {
|
||||||
this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock)
|
this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
import { ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
export function queryNftTxCount(address: string) {
|
export function queryNftTxCount(address: string) {
|
||||||
let data = {
|
let data = {
|
||||||
@ -67,7 +67,7 @@ export class ExploreNftTxSvr {
|
|||||||
|
|
||||||
async parseEvents() {
|
async parseEvents() {
|
||||||
logger.info(`query nft tx list:: address: ${this.address}`)
|
logger.info(`query nft tx list:: address: ${this.address}`)
|
||||||
let blockStr = await new RedisClient().get(this.blockKey)
|
let blockStr = await new ZRedisClient().get(this.blockKey)
|
||||||
if (blockStr) {
|
if (blockStr) {
|
||||||
this.lastBlock = Math.max(parseInt(blockStr), this.lastBlock)
|
this.lastBlock = Math.max(parseInt(blockStr), this.lastBlock)
|
||||||
}
|
}
|
||||||
@ -116,6 +116,6 @@ export class ExploreNftTxSvr {
|
|||||||
}
|
}
|
||||||
page += 1
|
page += 1
|
||||||
}
|
}
|
||||||
new RedisClient().set(this.blockKey, maxBlock + '')
|
new ZRedisClient().set(this.blockKey, maxBlock + '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'zutils'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { hmacSha256 } from 'utils/security.util'
|
import { hmacSha256 } from 'zutils/utils/security.util'
|
||||||
|
|
||||||
const REPORT_TASK_URI = '/api/internal/update_task'
|
const REPORT_TASK_URI = '/api/internal/update_task'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'zutils'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { createTransport, Transporter } from 'nodemailer'
|
import { createTransport, Transporter } from 'nodemailer'
|
||||||
import Mail from 'nodemailer/lib/mailer'
|
import Mail from 'nodemailer/lib/mailer'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BlockChain } from 'chain/BlockChain'
|
import { BlockChain } from 'chain/BlockChain'
|
||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'zutils'
|
||||||
import * as schedule from 'node-schedule'
|
import * as schedule from 'node-schedule'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { batchEthBlocks } from 'chain/chain.api'
|
import { batchEthBlocks } from 'chain/chain.api'
|
||||||
import { IScriptionCfg } from 'interface/IScriptionCfg'
|
import { IScriptionCfg } from 'interface/IScriptionCfg'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
import { ZRedisClient } from 'zutils'
|
||||||
import { utf8ToHex } from './string.util'
|
import { utf8ToHex } from 'zutils/utils/string.util'
|
||||||
|
|
||||||
const MAX_BATCH_AMOUNT = +process.env.MAX_BLOCK_BATCH_AMOUNT
|
const MAX_BATCH_AMOUNT = +process.env.MAX_BLOCK_BATCH_AMOUNT
|
||||||
const REQUEST_INTERVAL = 0.5 * 1000
|
const REQUEST_INTERVAL = 0.5 * 1000
|
||||||
@ -80,7 +80,7 @@ export async function getPastBlocks({
|
|||||||
if (retryCount > 0) {
|
if (retryCount > 0) {
|
||||||
blocks.sort((a, b) => parseInt(a.number) - parseInt(b.number))
|
blocks.sort((a, b) => parseInt(a.number) - parseInt(b.number))
|
||||||
}
|
}
|
||||||
await new RedisClient().set(redisKey, blockNumber + amount + '')
|
await new ZRedisClient().set(redisKey, blockNumber + amount + '')
|
||||||
await new Promise(resolve => setTimeout(resolve, REQUEST_INTERVAL))
|
await new Promise(resolve => setTimeout(resolve, REQUEST_INTERVAL))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.log(e.message || e)
|
logger.log(e.message || e)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { toBN } from './number.util'
|
import { toBN } from 'zutils/utils/number.util'
|
||||||
import { BN } from 'ethereumjs-util'
|
import { BN } from 'ethereumjs-util'
|
||||||
import { RedisClient } from 'redis/RedisClient'
|
import { ZRedisClient } from 'zutils'
|
||||||
|
|
||||||
const ONE = toBN(1)
|
const ONE = toBN(1)
|
||||||
const TWO = toBN(2)
|
const TWO = toBN(2)
|
||||||
@ -192,10 +192,10 @@ export function* getPastEventsIter({
|
|||||||
yield getPastEvents({ contract, event, fromBlock: from, toBlock: to, options })
|
yield getPastEvents({ contract, event, fromBlock: from, toBlock: to, options })
|
||||||
from = to.add(ONE)
|
from = to.add(ONE)
|
||||||
to = to.add(queryRange)
|
to = to.add(queryRange)
|
||||||
yield new RedisClient().set(redisKey, from + '')
|
yield new ZRedisClient().set(redisKey, from + '')
|
||||||
}
|
}
|
||||||
yield getPastEvents({ contract, event, fromBlock: from, toBlock: toBlockBN, options })
|
yield getPastEvents({ contract, event, fromBlock: from, toBlock: toBlockBN, options })
|
||||||
yield new RedisClient().set(redisKey, toBlockBN.add(ONE) + '')
|
yield new ZRedisClient().set(redisKey, toBlockBN.add(ONE) + '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processEvents(web3, iterator, processedEvent) {
|
export async function processEvents(web3, iterator, processedEvent) {
|
||||||
|
@ -1,39 +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 begin of one day
|
|
||||||
export const getDayBegin = (date: Date): Date => {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = date.getMonth()
|
|
||||||
const day = date.getDate()
|
|
||||||
return new Date(year, month, day)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get begin of n day ago
|
|
||||||
export const getNDayAgo = (n: number, begin: boolean): Date => {
|
|
||||||
const date = new Date(Date.now() - n * 24 * 60 * 60 * 1000)
|
|
||||||
if (begin) {
|
|
||||||
return getDayBegin(date)
|
|
||||||
} else {
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get begin of this month
|
|
||||||
export const getMonthBegin = (date: Date): Date => {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = date.getMonth()
|
|
||||||
return new Date(year, month, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get formated datestring of yesterday
|
|
||||||
export const yesterday = () => {
|
|
||||||
const date = new Date()
|
|
||||||
date.setDate(date.getDate() - 1)
|
|
||||||
return date
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
//@ts-ignore
|
|
||||||
import { keccak256, _jsonInterfaceMethodToString, AbiInput } from 'web3-utils'
|
|
||||||
import web3abi from 'web3-eth-abi'
|
|
||||||
import { IEventCfg } from 'interface/IEventCfg'
|
|
||||||
|
|
||||||
export const getTopics = (cfg: IEventCfg) => {
|
|
||||||
// let abi = cfg.abi
|
|
||||||
// let topic = `${abi.name}(`
|
|
||||||
// for (let item of abi.inputs) {
|
|
||||||
// topic += item.type + ','
|
|
||||||
// }
|
|
||||||
// topic = topic.slice(0, -1)
|
|
||||||
// topic += ')'
|
|
||||||
return keccak256(_jsonInterfaceMethodToString(cfg.abi))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const decodeEvent = (cfg: IEventCfg, eventData: { data: string; topics: string[] }) => {
|
|
||||||
const abiInputs = cfg.abi.inputs
|
|
||||||
let result = web3abi.decodeLog(abiInputs, eventData.data, eventData.topics.slice(1))
|
|
||||||
let decodedData: any = {}
|
|
||||||
for (let i = 0; i < abiInputs.length; i++) {
|
|
||||||
const input: AbiInput = abiInputs[i]
|
|
||||||
if (input.type === 'tuple[]') {
|
|
||||||
// @ts-ignore
|
|
||||||
decodedData[input.name] = result[i].map(item => {
|
|
||||||
let itemData = {}
|
|
||||||
for (let j = 0; j < input.components.length; j++) {
|
|
||||||
const component = input.components[j]
|
|
||||||
itemData[component.name] = item[j]
|
|
||||||
}
|
|
||||||
return itemData
|
|
||||||
})
|
|
||||||
} else if (input.type === 'tuple') {
|
|
||||||
let itemData = {}
|
|
||||||
for (let j = 0; j < input.components.length; j++) {
|
|
||||||
const component = input.components[j]
|
|
||||||
itemData[component.name] = result[i][j]
|
|
||||||
}
|
|
||||||
decodedData[input.name] = itemData
|
|
||||||
} else {
|
|
||||||
decodedData[input.name] = result[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return decodedData
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
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',
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,198 +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.',
|
|
||||||
)
|
|
||||||
}
|
|
@ -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,74 +0,0 @@
|
|||||||
import crypto from 'crypto'
|
|
||||||
|
|
||||||
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(text: string, secret: string) {
|
|
||||||
const mac = crypto.createHmac('sha256', secret)
|
|
||||||
const data = mac.update(text).digest('hex').toLowerCase()
|
|
||||||
console.log(`HmacSHA256 rawContent is [${text}], key is [${secret}], 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
|
|
||||||
}
|
|
@ -1,123 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hexToUtf8(hexString) {
|
|
||||||
// Remove any leading "0x" prefix and split into pairs of characters
|
|
||||||
let _hexString = hexString.replace(/^0x/, '')
|
|
||||||
let buffer = Buffer.from(_hexString, 'hex')
|
|
||||||
return buffer.toString('utf8')
|
|
||||||
}
|
|
||||||
|
|
||||||
export function utf8ToHex(utf8String) {
|
|
||||||
// Create a Buffer object from the UTF-8 string
|
|
||||||
const buffer = Buffer.from(utf8String, 'utf8')
|
|
||||||
|
|
||||||
// Convert the Buffer object to a hex string
|
|
||||||
const hexString = buffer.toString('hex')
|
|
||||||
|
|
||||||
return hexString
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -1349,6 +1349,11 @@ crypto-browserify@3.12.0:
|
|||||||
randombytes "^2.0.0"
|
randombytes "^2.0.0"
|
||||||
randomfill "^1.0.3"
|
randomfill "^1.0.3"
|
||||||
|
|
||||||
|
crypto-js@^4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
||||||
|
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
||||||
|
|
||||||
d@1, d@^1.0.1:
|
d@1, d@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmmirror.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
|
resolved "https://registry.npmmirror.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
|
||||||
@ -4706,3 +4711,7 @@ yn@3.1.1:
|
|||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
resolved "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
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