功能: 添加任务调度和批量Web3请求。

- 添加用于调度和取消时间锁定任务的方法
- 添加batchMint方法,以一次性铸造多个代币
- 在Constants.ts中添加常量“ZERO_BYTES32”和“MAX_BATCH_REQ_COUNT”
- 更新RequestTask以正确生成多个请求的数据和值数组
- 在RequestTask.ts中将“ReqTaskStatus.WAIT_CONFIRM”更改为“ReqTaskStatus.WAIT_EXEC”。
This commit is contained in:
zhl 2023-04-07 15:13:31 +08:00
parent 0a6b7b69ff
commit 5475acdf80
11 changed files with 3090 additions and 2169 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,13 @@
import { singleton } from 'decorators/singleton' import { singleton } from 'decorators/singleton'
import logger from 'logger/logger' import logger from 'logger/logger'
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'
import { TransactionReceipt, AddedAccount } from 'web3-core' import { TransactionReceipt, AddedAccount } from 'web3-core'
import { ERC20Reactor } from './ERC20Reactor' import { ERC20Reactor } from './ERC20Reactor'
import { ERC721Reactor } from './ERC721Reactor' import { ERC721Reactor } from './ERC721Reactor'
import { HttpRetryProvider } from './HttpRetryProvider' import { HttpRetryProvider } from './HttpRetryProvider'
import { WalletReactor } from './WalletReactor'
@singleton @singleton
export class BlockChain { export class BlockChain {
@ -14,6 +16,7 @@ export class BlockChain {
private accountMaster: AddedAccount private accountMaster: AddedAccount
public erc20Reactor: ERC20Reactor public erc20Reactor: ERC20Reactor
public erc721Reactor: ERC721Reactor public erc721Reactor: ERC721Reactor
public walletReactor: WalletReactor
public confirmQueue: ConfirmQueue public confirmQueue: ConfirmQueue
public currentBlockNum: number = 0 public currentBlockNum: number = 0
@ -32,6 +35,10 @@ export class BlockChain {
web3: this.web3, web3: this.web3,
address: process.env.CHAIN_NFT_ADDRESS, address: process.env.CHAIN_NFT_ADDRESS,
}) })
this.walletReactor = new WalletReactor({
web3: this.web3,
address: process.env.CHAIN_WALLET_ADDRESS,
})
} }
public async getContractInstance(address: string, abi: any) { public async getContractInstance(address: string, abi: any) {
@ -56,4 +63,27 @@ export class BlockChain {
this.currentBlockNum = await this.web3.eth.getBlockNumber() this.currentBlockNum = await this.web3.eth.getBlockNumber()
logger.debug(`update block num: ${this.currentBlockNum}`) logger.debug(`update block num: ${this.currentBlockNum}`)
} }
public async generateFunAbi(reqData: any) {
let taskType = reqData.type
let abi
switch (taskType) {
case TaskType.MINT_FT:
abi = await this.erc20Reactor.mint(reqData)
break
case TaskType.MINT_NFT:
abi = await this.erc721Reactor.batchMint(reqData)
break
case TaskType.TRANSFER_FT:
abi = await this.erc20Reactor.transfer(reqData)
break
case TaskType.TRANSFER_NFT:
abi = await this.erc721Reactor.transfer(reqData)
break
case TaskType.MINT_PRESALE_BOX:
abi = await this.erc721Reactor.mint(reqData)
break
}
return abi
}
} }

View File

@ -115,6 +115,7 @@ export class ERC20Reactor {
amount, amount,
account, account,
gas, gas,
encodeABI = false,
}: { }: {
address: string address: string
from: string from: string
@ -122,19 +123,38 @@ export class ERC20Reactor {
account?: string account?: string
amount: number | string amount: number | string
gas?: number gas?: number
encodeABI: boolean
}) { }) {
const contract = new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address }) const contract = new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address })
const amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + '')) const amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
if (encodeABI) {
return contract.methods.transfer(to, amountBN).encodeABI()
}
return contract.methods.transferFrom(from, to, amountBN).send({ return contract.methods.transferFrom(from, to, amountBN).send({
gas: gas || 1000000, gas: gas || 1000000,
}) })
} }
async mint({ address, to, amount, account }: { account?: string; address?: string; to: string; amount: string }) { async mint({
address,
to,
amount,
account,
encodeABI = false,
}: {
account?: string
address?: string
to: string
amount: string
encodeABI: boolean
}) {
const contract = address const contract = address
? new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address }) ? new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address })
: this.contract : this.contract
let amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + '')) let amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
if (encodeABI) {
return contract.methods.mint(to, amountBN).encodeABI()
}
let gas = await contract.methods.mint(to, amountBN).estimateGas({ gas: 1000000 }) let gas = await contract.methods.mint(to, amountBN).estimateGas({ gas: 1000000 })
return contract.methods.mint(to, amountBN).send({ gas: (gas * 1.1) | 0 }) return contract.methods.mint(to, amountBN).send({ gas: (gas * 1.1) | 0 })
} }

View File

@ -300,6 +300,7 @@ export class ERC721Reactor {
tokenId, tokenId,
account, account,
gas, gas,
encodeABI = false,
}: { }: {
address?: string address?: string
from: string from: string
@ -307,20 +308,37 @@ export class ERC721Reactor {
tokenId: string tokenId: string
account?: string account?: string
gas?: number gas?: number
encodeABI?: boolean
}) { }) {
const contract = address const contract = address
? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address }) ? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address })
: this.contract : this.contract
if (encodeABI) {
return contract.methods.safeTransferFrom(from, to, tokenId).encodeABI()
}
return contract.methods.safeTransferFrom(from, to, tokenId).send({ return contract.methods.safeTransferFrom(from, to, tokenId).send({
from, from,
gas: gas || 1000000, gas: gas || 1000000,
}) })
} }
async mint({ address, to, tokenId }: { address?: string; to: string; tokenId: string }) { async mint({
address,
to,
tokenId,
encodeABI = false,
}: {
address?: string
to: string
tokenId: string
encodeABI?: boolean
}) {
const contract = address const contract = address
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address }) ? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
: this.contract : this.contract
if (encodeABI) {
return contract.methods.mint(to, tokenId).encodeABI()
}
let gas = await contract.methods.mint(to, tokenId).estimateGas({ gas: 1000000 }) let gas = await contract.methods.mint(to, tokenId).estimateGas({ gas: 1000000 })
return contract.methods.mint(to, tokenId).send({ gas: (gas * 1.1) | 0 }) return contract.methods.mint(to, tokenId).send({ gas: (gas * 1.1) | 0 })
} }
@ -330,16 +348,21 @@ export class ERC721Reactor {
address, address,
to, to,
tokenIds, tokenIds,
encodeABI = false,
}: { }: {
account?: string account?: string
address?: string address?: string
to: string to: string
tokenIds: string[] tokenIds: string[]
encodeABI?: boolean
}) { }) {
const contract = address const contract = address
? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address }) ? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address })
: this.contract : this.contract
// let gas = await contract.methods.batchMint(to, tokenIds, configIds).estimateGas({ gas: 1000000 }) // let gas = await contract.methods.batchMint(to, tokenIds, configIds).estimateGas({ gas: 1000000 })
if (encodeABI) {
return contract.methods.batchMint(to, tokenIds).encodeABI()
}
return contract.methods.batchMint(to, tokenIds).send({ gas: 1000000 }) return contract.methods.batchMint(to, tokenIds).send({ gas: 1000000 })
} }

View File

@ -1,8 +1,24 @@
import { Contract } from 'web3-eth-contract' 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'
const abi = require('abis/BEMultiSigWallet.json').abi const abi = require('abis/BEMultiSigWallet.json').abi
/**
* ["address", "uint256", "bytes", "uint256", "bytes32"],
* [target, value, data, predecessor, salt]
* data: method.encodeABI
* salt: generateRandomBytes32();
*/
export interface IOperationData {
scheduleId?: string
targets: string[]
values: string[]
datas: string[]
predecessor: string
salt: string
transactionHash?: string
}
export class WalletReactor { export class WalletReactor {
private web3: Web3 private web3: Web3
private contract: Contract private contract: Contract
@ -13,4 +29,97 @@ export class WalletReactor {
this.account = this.web3.eth.accounts.wallet[0] this.account = this.web3.eth.accounts.wallet[0]
this.contract = new this.web3.eth.Contract(abi, address, { from: this.account.address }) this.contract = new this.web3.eth.Contract(abi, address, { from: this.account.address })
} }
/**
* timelock的任务, proposer角色
* @param {*} seconds delay的秒数
* @returns
*/
async beginSchedule(operation: IOperationData, seconds: number) {
// let operation: any = this.genOperation(contractAddress, 0, data, ZERO_BYTES32, salt)
let res = await this.contract.methods
.scheduleBatch(
operation.targets,
operation.values,
operation.datas,
operation.predecessor,
operation.salt,
seconds,
)
.send({ gas: 1000000 })
operation.transactionHash = res.transactionHash
return operation
}
/**
* , proposer角色
* @param {bytes32} id beginSchedule返回的id
* @returns
*/
async cancelSchedule(id) {
let res = await this.contract.methods.cancel(id).send({ gas: 1000000 })
return res
}
/**
*
* @param {bytes32} scheduleId beginSchedule返回的id
* @returns
*/
async querySchedule(scheduleId: string) {
let instance = this.contract
return this.makeBatchRequest([
instance.methods.isOperation(scheduleId).call(),
instance.methods.isOperationPending(scheduleId).call(),
instance.methods.isOperationReady(scheduleId).call(),
instance.methods.isOperationDone(scheduleId).call(),
instance.methods.getTimestamp(scheduleId).call(),
])
}
/**
* schedule的任务, executor的role
* @returns
*/
async executeSchedule(operation: IOperationData) {
let res = await this.contract.methods
.executeBatch(operation.targets, operation.values, operation.datas, operation.predecessor, operation.salt)
.send({ gas: 1000000 })
return res
}
/**
* schedule的请求数据
* @param {address} targets contract的address
* @param {uint256} values ,
* @param {bytes} datas contract.methods.methodName(params).encodeABI()
* @param {bytes32} predecessor that specifies a dependency between operations, , bytes32(0)
* @param {bytes32} salt ,
* @returns
*/
genOperation({ targets, values, datas, predecessor, salt }: IOperationData): IOperationData {
const scheduleId = this.web3.utils.keccak256(
this.web3.eth.abi.encodeParameters(
['[address]', '[uint256]', '[bytes]', 'uint256', 'bytes32'],
[targets, values, datas, predecessor, salt],
),
)
return { scheduleId, targets, values, datas, predecessor, salt }
}
makeBatchRequest(calls: any[], callFrom?: any) {
let batch = new this.web3.BatchRequest()
let promises = calls.map(call => {
return new Promise((resolve, reject) => {
let request = call.request({ from: callFrom }, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
batch.add(request)
})
})
batch.execute()
return Promise.all(promises)
}
} }

View File

@ -1 +1,5 @@
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'
export const MAX_BATCH_REQ_COUNT = 50

View File

@ -1,5 +1,8 @@
import { getModelForClass, index, modelOptions, mongoose, pre, prop, Ref, Severity } from '@typegoose/typegoose' import { getModelForClass, index, modelOptions, mongoose, pre, prop, Ref, Severity } from '@typegoose/typegoose'
import { BlockChain } from 'chain/BlockChain'
import { MAX_BATCH_REQ_COUNT, ZERO_BYTES32 } from 'common/Constants'
import { dbconn } from 'decorators/dbconn' import { dbconn } from 'decorators/dbconn'
import { generateRandomBytes32 } from 'utils/wallet.util'
import { BaseModule } from './Base' import { BaseModule } from './Base'
import { ReqTaskStatus, RequestTask, RequestTaskClass } from './RequestTask' import { ReqTaskStatus, RequestTask, RequestTaskClass } from './RequestTask'
@ -13,6 +16,7 @@ export enum TaskStatus {
} }
@dbconn() @dbconn()
@index({ taskId: 1 }, { unique: false })
@modelOptions({ @modelOptions({
schemaOptions: { collection: 'chain_task', timestamps: true }, schemaOptions: { collection: 'chain_task', timestamps: true },
options: { allowMixed: Severity.ALLOW }, options: { allowMixed: Severity.ALLOW },
@ -110,20 +114,32 @@ export class ChainTaskClass extends BaseModule {
let chainTask = await ChainTask.insertOrUpdate({ taskId }, { name, desc, starter, data }) let chainTask = await ChainTask.insertOrUpdate({ taskId }, { name, desc, starter, data })
let subTasks: any = [] let subTasks: any = []
if (chainTask.newRecord) { if (chainTask.newRecord) {
let subTask
let index = 0
for (let sub of data) { for (let sub of data) {
let subType = sub.type if (!subTask || subTask.reqDatas.length >= MAX_BATCH_REQ_COUNT) {
let subTask = new RequestTask({ index += 1
subTask = new RequestTask({
taskId, taskId,
index,
chainTaskId: chainTask.id, chainTaskId: chainTask.id,
taskType: subType, reqDatas: [],
reqData: sub,
maxTryCount, maxTryCount,
predecessor: ZERO_BYTES32,
salt: generateRandomBytes32(),
}) })
subTasks.push(subTasks)
}
subTask.reqDatas.push(sub)
subTask.targets.push(sub.address)
let abi = await new BlockChain().generateFunAbi(sub)
subTask.datas.push(abi)
subTask.values.push('0')
await subTask.save() await subTask.save()
chainTask.tasks.pushOnce(subTask.id) chainTask.tasks.pushOnce(subTask.id)
subTasks.push(subTask)
} }
} }
chainTask.newRecord = false
await chainTask.save() await chainTask.save()
return subTasks return subTasks
} }

View File

@ -17,7 +17,8 @@ export enum ReqTaskStatus {
NOTSTART = 0, NOTSTART = 0,
PEDING = 1, PEDING = 1,
WAIT_CONFIRM = 2, WAIT_CONFIRM = 2,
SUCCESS = 3, WAIT_EXEC = 3,
SUCCESS = 4,
REVERT = 8, REVERT = 8,
ERROR = 9, ERROR = 9,
} }
@ -34,17 +35,15 @@ export class RequestTaskClass extends BaseModule {
@prop({ required: true }) @prop({ required: true })
public chainTaskId!: string public chainTaskId!: string
@prop({ enum: TaskType, default: TaskType.UNKNOW }) @prop({ default: 0 })
public taskType: TaskType public index: number
@prop({ required: true, default: 0 }) @prop({ required: true, default: 0 })
public tryCount: number public tryCount: number
@prop({ required: true, default: 0 }) @prop({ required: true, default: 0 })
public maxTryCount: number public maxTryCount: number
/**
* address: string
*/
@prop({ type: mongoose.Schema.Types.Mixed }) @prop({ type: mongoose.Schema.Types.Mixed })
public reqData: any public reqData: any
@ -60,6 +59,26 @@ export class RequestTaskClass extends BaseModule {
@prop() @prop()
public endTime: number public endTime: number
//begin for schedule
@prop()
public scheduleId: string
@prop({ type: () => [String] })
public targets: string[]
@prop({ type: () => [String] })
public datas: string[]
@prop({ type: () => [String] })
public values: string[]
@prop()
public predecessor: string
@prop()
public salt: string
//end for schedule
@prop() @prop()
public txHash: string public txHash: string
/** /**
@ -77,28 +96,10 @@ export class RequestTaskClass extends BaseModule {
public errMsg: any[] public errMsg: any[]
public async requestChain(this: DocumentType<RequestTaskClass>) { public async requestChain(this: DocumentType<RequestTaskClass>) {
let result
let self = this let self = this
self.blockReq = new BlockChain().currentBlockNum self.blockReq = new BlockChain().currentBlockNum
await self.save() await self.save()
let result = await new BlockChain().walletReactor.beginSchedule(self, 60)
switch (self.taskType) {
case TaskType.MINT_FT:
result = await new BlockChain().erc20Reactor.mint(self.reqData)
break
case TaskType.MINT_NFT:
result = await new BlockChain().erc721Reactor.batchMint(self.reqData)
break
case TaskType.TRANSFER_FT:
result = await new BlockChain().erc20Reactor.transfer(self.reqData)
break
case TaskType.TRANSFER_NFT:
result = await new BlockChain().erc721Reactor.transfer(self.reqData)
break
case TaskType.MINT_PRESALE_BOX:
result = await new BlockChain().erc721Reactor.mint(self.reqData)
break
}
logger.info(result) logger.info(result)
let { transactionHash } = result let { transactionHash } = result
self.txHash = transactionHash self.txHash = transactionHash

View File

@ -1,3 +1,4 @@
import { BlockChain } from 'chain/BlockChain'
import { singleton } from 'decorators/singleton' import { singleton } from 'decorators/singleton'
import { ChainTask } from 'models/ChainTask' import { ChainTask } from 'models/ChainTask'
import { ChainQueue } from 'queue/chain.queue' import { ChainQueue } from 'queue/chain.queue'
@ -9,6 +10,9 @@ export class TaskSvr {
let data = await new WechatWorkService().parseOneTask(spNo) let data = await new WechatWorkService().parseOneTask(spNo)
let subTasks = await ChainTask.parseWxApprovalInfo(data) let subTasks = await ChainTask.parseWxApprovalInfo(data)
for (let subTask of subTasks) { for (let subTask of subTasks) {
let { scheduleId } = new BlockChain().walletReactor.genOperation(subTask)
subTask.scheduleId = scheduleId
await subTask.save()
new ChainQueue().addTaskToQueue(subTask) new ChainQueue().addTaskToQueue(subTask)
} }
} }

View File

@ -1,4 +1,4 @@
import { renderFromTokenMinimalUnit } from "./number.util"; import { renderFromTokenMinimalUnit } from './number.util'
/** /**
* Removes IPFS protocol prefix from input string. * Removes IPFS protocol prefix from input string.
@ -8,13 +8,13 @@ import { renderFromTokenMinimalUnit } from "./number.util";
* @throws Will throw if the url passed is not IPFS. * @throws Will throw if the url passed is not IPFS.
*/ */
export function removeIpfsProtocolPrefix(ipfsUrl: string) { export function removeIpfsProtocolPrefix(ipfsUrl: string) {
if (ipfsUrl.startsWith("ipfs://ipfs/")) { if (ipfsUrl.startsWith('ipfs://ipfs/')) {
return ipfsUrl.replace("ipfs://ipfs/", ""); return ipfsUrl.replace('ipfs://ipfs/', '')
} else if (ipfsUrl.startsWith("ipfs://")) { } else if (ipfsUrl.startsWith('ipfs://')) {
return ipfsUrl.replace("ipfs://", ""); return ipfsUrl.replace('ipfs://', '')
} }
// this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true) // 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"); throw new Error('this method should not be used with non ipfs urls')
} }
/** /**
@ -25,16 +25,16 @@ export function removeIpfsProtocolPrefix(ipfsUrl: string) {
* @throws Will throw if the url passed is not ipfs. * @throws Will throw if the url passed is not ipfs.
*/ */
export function getIpfsCIDv1AndPath(ipfsUrl: string): { export function getIpfsCIDv1AndPath(ipfsUrl: string): {
cid: string; cid: string
path?: string; path?: string
} { } {
const url = removeIpfsProtocolPrefix(ipfsUrl); const url = removeIpfsProtocolPrefix(ipfsUrl)
// check if there is a path // check if there is a path
// (CID is everything preceding first forward slash, path is everything after) // (CID is everything preceding first forward slash, path is everything after)
const index = url.indexOf("/"); const index = url.indexOf('/')
const cid = index !== -1 ? url.substring(0, index) : url; const cid = index !== -1 ? url.substring(0, index) : url
const path = index !== -1 ? url.substring(index) : undefined; const path = index !== -1 ? url.substring(index) : undefined
//TODO: //TODO:
// We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats) // 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 // because most cid v0s appear to be incompatible with IPFS subdomains
@ -45,7 +45,7 @@ export function getIpfsCIDv1AndPath(ipfsUrl: string): {
return { return {
cid, cid,
path, path,
}; }
} }
/** /**
@ -56,9 +56,9 @@ export function getIpfsCIDv1AndPath(ipfsUrl: string): {
*/ */
export function addUrlProtocolPrefix(urlString: string): string { export function addUrlProtocolPrefix(urlString: string): string {
if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) { if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) {
return `https://${urlString}`; return `https://${urlString}`
} }
return urlString; return urlString
} }
/** /**
@ -69,21 +69,16 @@ export function addUrlProtocolPrefix(urlString: string): string {
* @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not. * @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. * @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( export function getFormattedIpfsUrl(ipfsGateway: string, ipfsUrl: string, subdomainSupported: boolean): string {
ipfsGateway: string, const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway))
ipfsUrl: string,
subdomainSupported: boolean
): string {
const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway));
if (subdomainSupported) { if (subdomainSupported) {
const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl); const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl)
return `${protocol}//${cid}.ipfs.${host}${path || ""}`; return `${protocol}//${cid}.ipfs.${host}${path || ''}`
} }
const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl); const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl)
return `${origin}/ipfs/${cidAndPath}`; return `${origin}/ipfs/${cidAndPath}`
} }
/** /**
* Returns whether the given code corresponds to a smart contract. * Returns whether the given code corresponds to a smart contract.
* *
@ -93,11 +88,11 @@ export function getFormattedIpfsUrl(
export function isSmartContractCode(code: string) { export function isSmartContractCode(code: string) {
/* istanbul ignore if */ /* istanbul ignore if */
if (!code) { if (!code) {
return false; return false
} }
// Geth will return '0x', and ganache-core v2.2.1 will return '0x0' // Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
const smartContractCode = code !== '0x' && code !== '0x0'; const smartContractCode = code !== '0x' && code !== '0x0'
return smartContractCode; return smartContractCode
} }
export function formatAddress(address: string) { export function formatAddress(address: string) {
@ -112,8 +107,18 @@ export function formatAddress(address: string) {
export function formatMoney(balance: number | string, symbol: string) { export function formatMoney(balance: number | string, symbol: string) {
if (balance === '-') { if (balance === '-') {
return `- ${symbol}`; return `- ${symbol}`
} }
let money = renderFromTokenMinimalUnit(balance, 18, 4) let money = renderFromTokenMinimalUnit(balance, 18, 4)
return `${money} ${symbol}`; return `${money} ${symbol}`
}
/**
* bytes32的字符串
* @returns
*/
export function generateRandomBytes32() {
const v1 = (Math.random() * 9000000 + 1000000) | 0
const v2 = (Math.random() * 900000 + 100000) | 0
return this.web3.utils.asciiToHex(v1 + '' + v2)
} }