功能: 添加任务调度和批量Web3请求。
- 添加用于调度和取消时间锁定任务的方法 - 添加batchMint方法,以一次性铸造多个代币 - 在Constants.ts中添加常量“ZERO_BYTES32”和“MAX_BATCH_REQ_COUNT” - 更新RequestTask以正确生成多个请求的数据和值数组 - 在RequestTask.ts中将“ReqTaskStatus.WAIT_CONFIRM”更改为“ReqTaskStatus.WAIT_EXEC”。
This commit is contained in:
parent
0a6b7b69ff
commit
5475acdf80
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,11 +1,13 @@
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import logger from 'logger/logger'
|
||||
import { TaskType } from 'models/RequestTask'
|
||||
import { ConfirmQueue } from 'queue/confirm.queue'
|
||||
import Web3 from 'web3'
|
||||
import { TransactionReceipt, AddedAccount } from 'web3-core'
|
||||
import { ERC20Reactor } from './ERC20Reactor'
|
||||
import { ERC721Reactor } from './ERC721Reactor'
|
||||
import { HttpRetryProvider } from './HttpRetryProvider'
|
||||
import { WalletReactor } from './WalletReactor'
|
||||
|
||||
@singleton
|
||||
export class BlockChain {
|
||||
@ -14,6 +16,7 @@ export class BlockChain {
|
||||
private accountMaster: AddedAccount
|
||||
public erc20Reactor: ERC20Reactor
|
||||
public erc721Reactor: ERC721Reactor
|
||||
public walletReactor: WalletReactor
|
||||
public confirmQueue: ConfirmQueue
|
||||
public currentBlockNum: number = 0
|
||||
|
||||
@ -32,6 +35,10 @@ export class BlockChain {
|
||||
web3: this.web3,
|
||||
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) {
|
||||
@ -56,4 +63,27 @@ export class BlockChain {
|
||||
this.currentBlockNum = await this.web3.eth.getBlockNumber()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,7 @@ export class ERC20Reactor {
|
||||
amount,
|
||||
account,
|
||||
gas,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
address: string
|
||||
from: string
|
||||
@ -122,19 +123,38 @@ export class ERC20Reactor {
|
||||
account?: string
|
||||
amount: number | string
|
||||
gas?: number
|
||||
encodeABI: boolean
|
||||
}) {
|
||||
const contract = new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address })
|
||||
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({
|
||||
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
|
||||
? new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address })
|
||||
: this.contract
|
||||
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 })
|
||||
return contract.methods.mint(to, amountBN).send({ gas: (gas * 1.1) | 0 })
|
||||
}
|
||||
|
@ -300,6 +300,7 @@ export class ERC721Reactor {
|
||||
tokenId,
|
||||
account,
|
||||
gas,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
address?: string
|
||||
from: string
|
||||
@ -307,20 +308,37 @@ export class ERC721Reactor {
|
||||
tokenId: string
|
||||
account?: string
|
||||
gas?: number
|
||||
encodeABI?: boolean
|
||||
}) {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address })
|
||||
: this.contract
|
||||
if (encodeABI) {
|
||||
return contract.methods.safeTransferFrom(from, to, tokenId).encodeABI()
|
||||
}
|
||||
return contract.methods.safeTransferFrom(from, to, tokenId).send({
|
||||
from,
|
||||
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
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
if (encodeABI) {
|
||||
return contract.methods.mint(to, tokenId).encodeABI()
|
||||
}
|
||||
let gas = await contract.methods.mint(to, tokenId).estimateGas({ gas: 1000000 })
|
||||
return contract.methods.mint(to, tokenId).send({ gas: (gas * 1.1) | 0 })
|
||||
}
|
||||
@ -330,16 +348,21 @@ export class ERC721Reactor {
|
||||
address,
|
||||
to,
|
||||
tokenIds,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
account?: string
|
||||
address?: string
|
||||
to: string
|
||||
tokenIds: string[]
|
||||
encodeABI?: boolean
|
||||
}) {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address })
|
||||
: this.contract
|
||||
// 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 })
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,24 @@
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
import { Account } from 'web3-core'
|
||||
import { ZERO_BYTES32 } from 'common/Constants'
|
||||
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 {
|
||||
private web3: Web3
|
||||
private contract: Contract
|
||||
@ -13,4 +29,97 @@ export class WalletReactor {
|
||||
this.account = this.web3.eth.accounts.wallet[0]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,5 @@
|
||||
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
|
||||
export const MAX_BATCH_REQ_COUNT = 50
|
||||
|
@ -1,5 +1,8 @@
|
||||
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 { generateRandomBytes32 } from 'utils/wallet.util'
|
||||
|
||||
import { BaseModule } from './Base'
|
||||
import { ReqTaskStatus, RequestTask, RequestTaskClass } from './RequestTask'
|
||||
@ -13,6 +16,7 @@ export enum TaskStatus {
|
||||
}
|
||||
|
||||
@dbconn()
|
||||
@index({ taskId: 1 }, { unique: false })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'chain_task', timestamps: true },
|
||||
options: { allowMixed: Severity.ALLOW },
|
||||
@ -110,20 +114,32 @@ export class ChainTaskClass extends BaseModule {
|
||||
let chainTask = await ChainTask.insertOrUpdate({ taskId }, { name, desc, starter, data })
|
||||
let subTasks: any = []
|
||||
if (chainTask.newRecord) {
|
||||
let subTask
|
||||
let index = 0
|
||||
for (let sub of data) {
|
||||
let subType = sub.type
|
||||
let subTask = new RequestTask({
|
||||
if (!subTask || subTask.reqDatas.length >= MAX_BATCH_REQ_COUNT) {
|
||||
index += 1
|
||||
subTask = new RequestTask({
|
||||
taskId,
|
||||
index,
|
||||
chainTaskId: chainTask.id,
|
||||
taskType: subType,
|
||||
reqData: sub,
|
||||
reqDatas: [],
|
||||
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()
|
||||
chainTask.tasks.pushOnce(subTask.id)
|
||||
subTasks.push(subTask)
|
||||
}
|
||||
}
|
||||
chainTask.newRecord = false
|
||||
await chainTask.save()
|
||||
return subTasks
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ export enum ReqTaskStatus {
|
||||
NOTSTART = 0,
|
||||
PEDING = 1,
|
||||
WAIT_CONFIRM = 2,
|
||||
SUCCESS = 3,
|
||||
WAIT_EXEC = 3,
|
||||
SUCCESS = 4,
|
||||
REVERT = 8,
|
||||
ERROR = 9,
|
||||
}
|
||||
@ -34,17 +35,15 @@ export class RequestTaskClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public chainTaskId!: string
|
||||
|
||||
@prop({ enum: TaskType, default: TaskType.UNKNOW })
|
||||
public taskType: TaskType
|
||||
@prop({ default: 0 })
|
||||
public index: number
|
||||
|
||||
@prop({ required: true, default: 0 })
|
||||
public tryCount: number
|
||||
|
||||
@prop({ required: true, default: 0 })
|
||||
public maxTryCount: number
|
||||
/**
|
||||
* address: string
|
||||
*/
|
||||
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
public reqData: any
|
||||
|
||||
@ -60,6 +59,26 @@ export class RequestTaskClass extends BaseModule {
|
||||
@prop()
|
||||
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()
|
||||
public txHash: string
|
||||
/**
|
||||
@ -77,28 +96,10 @@ export class RequestTaskClass extends BaseModule {
|
||||
public errMsg: any[]
|
||||
|
||||
public async requestChain(this: DocumentType<RequestTaskClass>) {
|
||||
let result
|
||||
let self = this
|
||||
self.blockReq = new BlockChain().currentBlockNum
|
||||
await self.save()
|
||||
|
||||
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
|
||||
}
|
||||
let result = await new BlockChain().walletReactor.beginSchedule(self, 60)
|
||||
logger.info(result)
|
||||
let { transactionHash } = result
|
||||
self.txHash = transactionHash
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { BlockChain } from 'chain/BlockChain'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import { ChainTask } from 'models/ChainTask'
|
||||
import { ChainQueue } from 'queue/chain.queue'
|
||||
@ -9,6 +10,9 @@ export class TaskSvr {
|
||||
let data = await new WechatWorkService().parseOneTask(spNo)
|
||||
let subTasks = await ChainTask.parseWxApprovalInfo(data)
|
||||
for (let subTask of subTasks) {
|
||||
let { scheduleId } = new BlockChain().walletReactor.genOperation(subTask)
|
||||
subTask.scheduleId = scheduleId
|
||||
await subTask.save()
|
||||
new ChainQueue().addTaskToQueue(subTask)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { renderFromTokenMinimalUnit } from "./number.util";
|
||||
import { renderFromTokenMinimalUnit } from './number.util'
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export function removeIpfsProtocolPrefix(ipfsUrl: string) {
|
||||
if (ipfsUrl.startsWith("ipfs://ipfs/")) {
|
||||
return ipfsUrl.replace("ipfs://ipfs/", "");
|
||||
} else if (ipfsUrl.startsWith("ipfs://")) {
|
||||
return ipfsUrl.replace("ipfs://", "");
|
||||
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");
|
||||
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.
|
||||
*/
|
||||
export function getIpfsCIDv1AndPath(ipfsUrl: string): {
|
||||
cid: string;
|
||||
path?: string;
|
||||
cid: string
|
||||
path?: string
|
||||
} {
|
||||
const url = removeIpfsProtocolPrefix(ipfsUrl);
|
||||
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;
|
||||
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
|
||||
@ -45,7 +45,7 @@ export function getIpfsCIDv1AndPath(ipfsUrl: string): {
|
||||
return {
|
||||
cid,
|
||||
path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,9 +56,9 @@ export function getIpfsCIDv1AndPath(ipfsUrl: string): {
|
||||
*/
|
||||
export function addUrlProtocolPrefix(urlString: string): string {
|
||||
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.
|
||||
* @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));
|
||||
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 { cid, path } = getIpfsCIDv1AndPath(ipfsUrl)
|
||||
return `${protocol}//${cid}.ipfs.${host}${path || ''}`
|
||||
}
|
||||
const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl);
|
||||
return `${origin}/ipfs/${cidAndPath}`;
|
||||
const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl)
|
||||
return `${origin}/ipfs/${cidAndPath}`
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given code corresponds to a smart contract.
|
||||
*
|
||||
@ -93,11 +88,11 @@ export function getFormattedIpfsUrl(
|
||||
export function isSmartContractCode(code: string) {
|
||||
/* istanbul ignore if */
|
||||
if (!code) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
// Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
|
||||
const smartContractCode = code !== '0x' && code !== '0x0';
|
||||
return smartContractCode;
|
||||
const smartContractCode = code !== '0x' && code !== '0x0'
|
||||
return smartContractCode
|
||||
}
|
||||
|
||||
export function formatAddress(address: string) {
|
||||
@ -112,8 +107,18 @@ export function formatAddress(address: string) {
|
||||
|
||||
export function formatMoney(balance: number | string, symbol: string) {
|
||||
if (balance === '-') {
|
||||
return `- ${symbol}`;
|
||||
return `- ${symbol}`
|
||||
}
|
||||
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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user