增加多链支持
This commit is contained in:
parent
19c2828192
commit
3d36b846be
7
config/chains.json
Normal file
7
config/chains.json
Normal file
@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": 421613,
|
||||
"key": "0xd9ed33809372932059c1ba7b336a33f406b4c55e7430daef8297134c67429d60",
|
||||
"wallet": "0xE68F149daF2F314d9960c08496D8701BC7671850"
|
||||
}
|
||||
]
|
10
config/events.json
Normal file
10
config/events.json
Normal file
@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"address": "0xE68F149daF2F314d9960c08496D8701BC7671850",
|
||||
"event": "Confirmation",
|
||||
"abi": "BEMultiSigWallet",
|
||||
"fromBlock": 30117942,
|
||||
"eventProcesser": "ScheduleConfirmEvent",
|
||||
"chain": 421613
|
||||
}
|
||||
]
|
21046
src/abis/BEBadge.json
21046
src/abis/BEBadge.json
File diff suppressed because one or more lines are too long
28228
src/abis/NFT.json
Normal file
28228
src/abis/NFT.json
Normal file
File diff suppressed because one or more lines are too long
74
src/cache/ChainCache.ts
vendored
Normal file
74
src/cache/ChainCache.ts
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import assert from 'assert'
|
||||
import { HttpRetryProvider } from 'chain/HttpRetryProvider'
|
||||
import { AllChains } from 'chain/allchain'
|
||||
import Web3 from 'web3'
|
||||
import { WalletReactor } from 'chain/WalletReactor'
|
||||
import { ConfirmQueue } from 'queue/confirm.queue'
|
||||
|
||||
const chainCfgs = require('../../config/chains.json')
|
||||
|
||||
export interface IChainCfg {
|
||||
id: number
|
||||
key: string
|
||||
wallet: string
|
||||
}
|
||||
|
||||
@singleton
|
||||
export class ChainCache {
|
||||
public providers: Map<number, Web3> = new Map()
|
||||
public wallets: Map<number, WalletReactor> = new Map()
|
||||
public confirmQueues: Map<number, ConfirmQueue> = new Map()
|
||||
constructor() {
|
||||
this.init()
|
||||
}
|
||||
|
||||
private async init() {
|
||||
for (let cfg of chainCfgs) {
|
||||
let chainData = AllChains.find(o => o.id === cfg.id)
|
||||
assert(chainData, `chain id ${cfg.id} not found`)
|
||||
let web3 = new Web3(new HttpRetryProvider(chainData.rpc.split('|')))
|
||||
web3.eth.accounts.wallet.add(cfg.key)
|
||||
this.providers.set(cfg.id, web3)
|
||||
let wallet = new WalletReactor({
|
||||
web3: web3,
|
||||
address: cfg.wallet,
|
||||
})
|
||||
this.wallets.set(cfg.id, wallet)
|
||||
this.confirmQueues.set(cfg.id, new ConfirmQueue(web3))
|
||||
}
|
||||
}
|
||||
|
||||
public get chainArray(): IChainCfg[] {
|
||||
return chainCfgs
|
||||
}
|
||||
|
||||
public getWeb3(chainId: number) {
|
||||
if (!this.providers.has(chainId)) {
|
||||
throw new Error(`web3 for chain id ${chainId} not found`)
|
||||
}
|
||||
return this.providers.get(chainId)
|
||||
}
|
||||
|
||||
public getWallet(chainId: number) {
|
||||
if (!this.wallets.has(chainId)) {
|
||||
throw new Error(`wallet for chain id ${chainId} not found`)
|
||||
}
|
||||
return this.wallets.get(chainId)
|
||||
}
|
||||
|
||||
public getConfirmQueue(chainId: number) {
|
||||
if (!this.confirmQueues.has(chainId)) {
|
||||
throw new Error(`confirm queue for chain id ${chainId} not found`)
|
||||
}
|
||||
return this.confirmQueues.get(chainId)
|
||||
}
|
||||
|
||||
public getInstances(chain: number, contract: string, abi: any) {
|
||||
let web3 = new ChainCache().getWeb3(chain)
|
||||
assert(web3, `web3 for chain id ${chain} not found`)
|
||||
let account = web3.eth.accounts.wallet[0]
|
||||
assert(account, `account for chain id ${chain} not found`)
|
||||
return { instance: new web3.eth.Contract(abi, contract, { from: account.address }), account, web3 }
|
||||
}
|
||||
}
|
@ -2,71 +2,37 @@ 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 { TransactionReceipt } from 'web3-core'
|
||||
import { ERC20Reactor } from './ERC20Reactor'
|
||||
import { ERC721Reactor } from './ERC721Reactor'
|
||||
import { HttpRetryProvider } from './HttpRetryProvider'
|
||||
import { WalletReactor } from './WalletReactor'
|
||||
import { DistributorReactor } from './DistributorReactor'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
|
||||
@singleton
|
||||
export class BlockChain {
|
||||
private web3: Web3
|
||||
instanceCacheMap: Map<string, any>
|
||||
private accountMaster: AddedAccount
|
||||
public erc20Reactor: ERC20Reactor
|
||||
public erc721Reactor: ERC721Reactor
|
||||
public walletReactor: WalletReactor
|
||||
public distributorReactor: DistributorReactor
|
||||
public confirmQueue: ConfirmQueue
|
||||
public currentBlockNum: number = 0
|
||||
|
||||
constructor() {
|
||||
const provider = new HttpRetryProvider(process.env.CHAIN_RPC_URL.split('|'))
|
||||
this.web3 = new Web3(provider)
|
||||
this.confirmQueue = new ConfirmQueue(this.web3)
|
||||
let key = process.env.CHAIN_MASTER_KEY
|
||||
this.accountMaster = this.web3.eth.accounts.wallet.add(key)
|
||||
this.instanceCacheMap = new Map()
|
||||
this.erc20Reactor = new ERC20Reactor({
|
||||
web3: this.web3,
|
||||
address: process.env.CHAIN_FT_ADDRESS,
|
||||
})
|
||||
this.erc721Reactor = new ERC721Reactor({
|
||||
web3: this.web3,
|
||||
address: process.env.CHAIN_NFT_ADDRESS,
|
||||
})
|
||||
this.walletReactor = new WalletReactor({
|
||||
web3: this.web3,
|
||||
address: process.env.CHAIN_WALLET_ADDRESS,
|
||||
})
|
||||
this.distributorReactor = new DistributorReactor({
|
||||
web3: this.web3,
|
||||
address: process.env.CHAIN_DISTRIBUTOR_ADDRESS,
|
||||
})
|
||||
new ChainCache()
|
||||
this.erc20Reactor = new ERC20Reactor()
|
||||
this.erc721Reactor = new ERC721Reactor()
|
||||
this.distributorReactor = new DistributorReactor()
|
||||
}
|
||||
|
||||
public async getContractInstance(address: string, abi: any) {
|
||||
if (!this.instanceCacheMap.has(address)) {
|
||||
const instance = new this.web3.eth.Contract(abi, address, { from: this.accountMaster.address })
|
||||
this.instanceCacheMap.set(address, instance)
|
||||
}
|
||||
return this.instanceCacheMap.get(address)
|
||||
}
|
||||
|
||||
public async getTransactionReceipt(txHash: string) {
|
||||
return this.web3.eth.getTransactionReceipt(txHash)
|
||||
}
|
||||
|
||||
public async getTxConfirms(txhash: string) {
|
||||
const receipt: TransactionReceipt = await this.getTransactionReceipt(txhash)
|
||||
const latest = await this.web3.eth.getBlockNumber()
|
||||
public async getTxConfirms(chain: number, txhash: string) {
|
||||
let web3 = new ChainCache().getWeb3(chain)
|
||||
const receipt: TransactionReceipt = await web3.eth.getTransactionReceipt(txhash)
|
||||
const latest = await web3.eth.getBlockNumber()
|
||||
return latest - receipt.blockNumber + 1
|
||||
}
|
||||
|
||||
public async updateCurrenBlockNum() {
|
||||
this.currentBlockNum = await this.web3.eth.getBlockNumber()
|
||||
public async updateCurrenBlockNum(chain: number) {
|
||||
let web3 = new ChainCache().getWeb3(chain)
|
||||
this.currentBlockNum = await web3.eth.getBlockNumber()
|
||||
// logger.debug(`update block num: ${this.currentBlockNum}`)
|
||||
}
|
||||
|
||||
@ -81,9 +47,9 @@ export class BlockChain {
|
||||
case TaskType.MINT_NFT:
|
||||
reqData.tokenId = reqData.tokenId || reqData.tokenid
|
||||
if (reqData.tokenId && !reqData.amount) {
|
||||
abi = await this.erc721Reactor.mint(reqData)
|
||||
abi = await this.erc721Reactor.mintNFT(reqData)
|
||||
} else {
|
||||
abi = await this.erc721Reactor.batchMint(reqData)
|
||||
abi = await this.erc721Reactor.mintBadge(reqData)
|
||||
}
|
||||
break
|
||||
case TaskType.TRANSFER_FT:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,67 +1,62 @@
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
import { Account } from 'web3-core'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
import { GAS_BOOST } from 'common/Constants'
|
||||
const abi = require('abis/NftDistributor.json').abi
|
||||
|
||||
export class DistributorReactor {
|
||||
private web3: Web3
|
||||
private contract: Contract
|
||||
private account: Account
|
||||
constructor({ web3, address }: { web3: Web3; address: string }) {
|
||||
this.web3 = web3
|
||||
this.account = this.web3.eth.accounts.wallet[0]
|
||||
this.contract = new this.web3.eth.Contract(abi, address, { from: this.account.address })
|
||||
}
|
||||
/**
|
||||
* 发布NFT列表
|
||||
*/
|
||||
async publishAirdropList({
|
||||
chain,
|
||||
address,
|
||||
to,
|
||||
nftList,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
address?: string
|
||||
chain: number
|
||||
address: string
|
||||
to: string
|
||||
nftList: string[]
|
||||
encodeABI?: boolean
|
||||
}) {
|
||||
const contract = address ? new this.web3.eth.Contract(abi, address, { from: this.account.address }) : this.contract
|
||||
const { instance, account } = new ChainCache().getInstances(chain, address, abi)
|
||||
if (encodeABI) {
|
||||
return contract.methods.addNFTData(to, nftList).encodeABI()
|
||||
return instance.methods.addNFTData(to, nftList).encodeABI()
|
||||
}
|
||||
let gas = await contract.methods.addNFTData(to, nftList).estimateGas({ from: this.account.address })
|
||||
let res = await contract.methods.addNFTData(to, nftList).send({ gas: gas | 0 })
|
||||
let gas = await instance.methods.addNFTData(to, nftList).estimateGas({ from: account.address })
|
||||
let res = await instance.methods.addNFTData(to, nftList).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||
return res
|
||||
}
|
||||
/**
|
||||
* mint nft to user
|
||||
*/
|
||||
async mintNft({
|
||||
chain,
|
||||
address,
|
||||
to,
|
||||
count,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
chain: number
|
||||
address?: string
|
||||
to: string
|
||||
count: number
|
||||
encodeABI?: boolean
|
||||
}) {
|
||||
const contract = address ? new this.web3.eth.Contract(abi, address, { from: this.account.address }) : this.contract
|
||||
const { instance, account } = new ChainCache().getInstances(chain, address, abi)
|
||||
const countNft = count + ''
|
||||
if (encodeABI) {
|
||||
return contract.methods.mintToUser(to, countNft).encodeABI()
|
||||
return instance.methods.mintToUser(to, countNft).encodeABI()
|
||||
}
|
||||
let gas = await contract.methods.mintToUser(to, countNft).estimateGas({ from: this.account.address })
|
||||
let res = await contract.methods.mintToUser(to, countNft).send({ gas: gas | 0 })
|
||||
let gas = await instance.methods.mintToUser(to, countNft).estimateGas({ from: account.address })
|
||||
let res = await instance.methods.mintToUser(to, countNft).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||
return res
|
||||
}
|
||||
/**
|
||||
* 查询用户可mint的数量
|
||||
*/
|
||||
async getMintableCount({ address, user }: { address?: string; user: string }) {
|
||||
const contract = address ? new this.web3.eth.Contract(abi, address, { from: this.account.address }) : this.contract
|
||||
return await contract.methods.getMintableCount(user).call()
|
||||
async getMintableCount({ chain, contract, user }: { chain: number; contract?: string; user: string }) {
|
||||
const { instance } = new ChainCache().getInstances(chain, contract, abi)
|
||||
return await instance.methods.getMintableCount(user).call()
|
||||
}
|
||||
}
|
||||
|
@ -1,171 +1,54 @@
|
||||
import { BN } from 'ethereumjs-util'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
import { GAS_BOOST } from 'common/Constants'
|
||||
import Web3 from 'web3'
|
||||
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { Account } from 'web3-core'
|
||||
|
||||
const abiFt = require('abis/ERC20.json').abi
|
||||
const abi = require('abis/ERC20.json').abi
|
||||
export class ERC20Reactor {
|
||||
private web3: Web3
|
||||
private contract: Contract
|
||||
private account: Account
|
||||
|
||||
constructor({ web3, address }: { web3: Web3; address: string }) {
|
||||
this.web3 = web3
|
||||
this.account = this.web3.eth.accounts.wallet[0]
|
||||
this.contract = new this.web3.eth.Contract(abiFt, address, { from: this.account.address })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get balance or count for current account on specific asset contract.
|
||||
*
|
||||
* @param address - Asset ERC20 contract address.
|
||||
* @param selectedAddress - Current account public address.
|
||||
* @returns Promise resolving to BN object containing balance for current account on specific asset contract.
|
||||
*/
|
||||
async getBalanceOf({ address, selectedAddress }: { address?: string; selectedAddress: string }): Promise<BN> {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiFt, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<BN>((resolve, reject) => {
|
||||
contract.methods.balanceOf(selectedAddress).call({ from: selectedAddress }, (error: Error, result: BN) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for the decimals for a given ERC20 asset.
|
||||
*
|
||||
* @param address - ERC20 asset contract string.
|
||||
* @returns Promise resolving to the 'decimals'.
|
||||
*/
|
||||
async getTokenDecimals(address?: string): Promise<string> {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiFt, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
contract.methods.decimals().call((error: Error, result: BN | string) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for symbol for a given ERC20 asset.
|
||||
*
|
||||
* @param address - ERC20 asset contract address.
|
||||
* @returns Promise resolving to the 'symbol'.
|
||||
*/
|
||||
async getTokenSymbol(address?: string): Promise<string> {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiFt, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
contract.methods.symbol().call((error: Error, result: BN | string) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Query if a contract implements an interface.
|
||||
*
|
||||
* @param address - Asset contract address.
|
||||
* @param userAddress - The public address for the currently active user's account.
|
||||
* @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair.
|
||||
*/
|
||||
async getDetails({ address, userAddress }: { address?: string; userAddress: string }): Promise<{
|
||||
standard: string
|
||||
symbol: string | undefined
|
||||
decimals: string | undefined
|
||||
balance: BN | undefined
|
||||
}> {
|
||||
const [decimals, symbol] = await Promise.all([this.getTokenDecimals(address), this.getTokenSymbol(address)])
|
||||
let balance
|
||||
if (userAddress) {
|
||||
balance = await this.getBalanceOf({ address, selectedAddress: userAddress })
|
||||
}
|
||||
return {
|
||||
decimals,
|
||||
symbol,
|
||||
balance,
|
||||
standard: 'ERC20',
|
||||
}
|
||||
}
|
||||
|
||||
async transfer({
|
||||
chain,
|
||||
address,
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
account,
|
||||
gas,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
chain: number
|
||||
address: string
|
||||
from: string
|
||||
to: string
|
||||
account?: string
|
||||
amount: number | string
|
||||
gas?: number
|
||||
encodeABI: boolean
|
||||
}) {
|
||||
const contract = new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address })
|
||||
const { instance, account } = new ChainCache().getInstances(chain, address, abi)
|
||||
const amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
|
||||
if (encodeABI) {
|
||||
return contract.methods.transfer(to, amountBN).encodeABI()
|
||||
return instance.methods.transfer(to, amountBN).encodeABI()
|
||||
}
|
||||
return contract.methods.transferFrom(from, to, amountBN).send({
|
||||
gas: gas || 1000000,
|
||||
let gas = await instance.methods.transferFrom(from, to, amountBN).estimateGas({ from: account.address })
|
||||
return instance.methods.transferFrom(from, to, amountBN).send({
|
||||
gas: (gas * GAS_BOOST) | 0,
|
||||
})
|
||||
}
|
||||
|
||||
async mint({
|
||||
chain,
|
||||
address,
|
||||
to,
|
||||
amount,
|
||||
account,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
account?: string
|
||||
chain: number
|
||||
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
|
||||
const { instance, account } = new ChainCache().getInstances(chain, address, abi)
|
||||
let amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
|
||||
if (encodeABI) {
|
||||
return contract.methods.mint(to, amountBN).encodeABI()
|
||||
return instance.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.5) | 0 })
|
||||
}
|
||||
|
||||
async getPastEvents({ address, fromBlock }: { address?: string; fromBlock: number }) {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiFt, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return contract.getPastEvents('Transfer', {
|
||||
fromBlock,
|
||||
toBlock: fromBlock + 50000,
|
||||
})
|
||||
let gas = await instance.methods.mint(to, amountBN).estimateGas({ from: account.address })
|
||||
return instance.methods.mint(to, amountBN).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||
}
|
||||
}
|
||||
|
@ -1,379 +1,76 @@
|
||||
import { timeoutFetch } from 'utils/net.util'
|
||||
import { getFormattedIpfsUrl } from 'utils/wallet.util'
|
||||
import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { Account } from 'web3-core'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
import { GAS_BOOST } from 'common/Constants'
|
||||
|
||||
export const ERC721 = 'ERC721'
|
||||
export const ERC721_INTERFACE_ID = '0x80ac58cd'
|
||||
export const ERC721_METADATA_INTERFACE_ID = '0x5b5e139f'
|
||||
export const ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63'
|
||||
|
||||
const abiNft = require('abis/BEBadge.json').abi
|
||||
const abiBadge = require('abis/BEBadge.json').abi
|
||||
const abiNFT = require('abis/NFT.json').abi
|
||||
|
||||
export class ERC721Reactor {
|
||||
private web3: Web3
|
||||
private contract: Contract
|
||||
private account: Account
|
||||
|
||||
constructor({ web3, address }: { web3: Web3; address: string }) {
|
||||
this.web3 = web3
|
||||
this.account = this.web3.eth.accounts.wallet[0]
|
||||
this.contract = new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
}
|
||||
|
||||
/**
|
||||
* Query if contract implements ERC721Metadata interface.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @returns Promise resolving to whether the contract implements ERC721Metadata interface.
|
||||
*/
|
||||
contractSupportsMetadataInterface = async (address?: string): Promise<boolean> => {
|
||||
return this.contractSupportsInterface({ address, interfaceId: ERC721_METADATA_INTERFACE_ID })
|
||||
}
|
||||
|
||||
/**
|
||||
* Query if contract implements ERC721Enumerable interface.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @returns Promise resolving to whether the contract implements ERC721Enumerable interface.
|
||||
*/
|
||||
contractSupportsEnumerableInterface = async (address?: string): Promise<boolean> => {
|
||||
return this.contractSupportsInterface({ address, interfaceId: ERC721_ENUMERABLE_INTERFACE_ID })
|
||||
}
|
||||
|
||||
/**
|
||||
* Query if contract implements ERC721 interface.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @returns Promise resolving to whether the contract implements ERC721 interface.
|
||||
*/
|
||||
contractSupportsBase721Interface = async (address?: string): Promise<boolean> => {
|
||||
return this.contractSupportsInterface({ address, interfaceId: ERC721_INTERFACE_ID })
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate assets assigned to an owner.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @param selectedAddress - Current account public address.
|
||||
* @param index - A collectible counter less than `balanceOf(selectedAddress)`.
|
||||
* @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'.
|
||||
*/
|
||||
getCollectibleTokenId = async ({
|
||||
address,
|
||||
selectedAddress,
|
||||
index,
|
||||
}: {
|
||||
address?: string
|
||||
selectedAddress: string
|
||||
index: number
|
||||
}): Promise<string> => {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
contract.methods.tokenOfOwnerByIndex(selectedAddress, index).call((error: Error, result: string) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getBalance = async ({ address, selectedAddress }: { address?: string; selectedAddress: string }): Promise<number> => {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
contract.methods.balanceOf(selectedAddress).call((error: Error, result: number) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for tokenURI for a given asset.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @param tokenId - ERC721 asset identifier.
|
||||
* @returns Promise resolving to the 'tokenURI'.
|
||||
*/
|
||||
getTokenURI = async ({ address, tokenId }: { address?: string; tokenId: string }): Promise<string> => {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
const supportsMetadata = await this.contractSupportsMetadataInterface(address)
|
||||
if (!supportsMetadata) {
|
||||
throw new Error('Contract does not support ERC721 metadata interface.')
|
||||
}
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
contract.methods.tokenURI(tokenId).call((error: Error, result: string) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for name for a given asset.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @returns Promise resolving to the 'name'.
|
||||
*/
|
||||
getAssetName = async (address?: string): Promise<string> => {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
contract.methods.name().call((error: Error, result: string) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for symbol for a given asset.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @returns Promise resolving to the 'symbol'.
|
||||
*/
|
||||
getAssetSymbol = async (address?: string): Promise<string> => {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
contract.methods.symbol().call((error: Error, result: string) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for owner for a given ERC721 asset.
|
||||
*
|
||||
* @param address - ERC721 asset contract address.
|
||||
* @param tokenId - ERC721 asset identifier.
|
||||
* @returns Promise resolving to the owner address.
|
||||
*/
|
||||
async getOwnerOf({ address, tokenId }: { address?: string; tokenId: string }): Promise<string> {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
contract.methods.ownerOf(tokenId).call((error: Error, result: string) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query if a contract implements an interface.
|
||||
*
|
||||
* @param address - Asset contract address.
|
||||
* @param interfaceId - Interface identifier.
|
||||
* @returns Promise resolving to whether the contract implements `interfaceID`.
|
||||
*/
|
||||
private contractSupportsInterface = async ({
|
||||
address,
|
||||
interfaceId,
|
||||
}: {
|
||||
address?: string
|
||||
interfaceId: string
|
||||
}): Promise<boolean> => {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
contract.methods.supportsInterface(interfaceId).call((error: Error, result: boolean) => {
|
||||
/* istanbul ignore if */
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Query if a contract implements an interface.
|
||||
*
|
||||
* @param address - Asset contract address.
|
||||
* @param ipfsGateway - The user's preferred IPFS gateway.
|
||||
* @param tokenId - tokenId of a given token in the contract.
|
||||
* @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair.
|
||||
*/
|
||||
getDetails = async ({
|
||||
address,
|
||||
ipfsGateway,
|
||||
tokenId,
|
||||
}: {
|
||||
address?: string
|
||||
ipfsGateway: string
|
||||
tokenId?: string
|
||||
}): Promise<{
|
||||
standard: string
|
||||
tokenURI: string | undefined
|
||||
symbol: string | undefined
|
||||
name: string | undefined
|
||||
image: string | undefined
|
||||
}> => {
|
||||
const isERC721 = await this.contractSupportsBase721Interface(address)
|
||||
if (!isERC721) {
|
||||
throw new Error("This isn't a valid ERC721 contract")
|
||||
}
|
||||
|
||||
let tokenURI, image, symbol, name
|
||||
|
||||
// TODO upgrade to use Promise.allSettled for name/symbol when we can refactor to use es2020 in tsconfig
|
||||
try {
|
||||
symbol = await this.getAssetSymbol(address)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
name = await this.getAssetName(address)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (tokenId) {
|
||||
try {
|
||||
tokenURI = await this.getTokenURI({ address, tokenId })
|
||||
if (tokenURI.startsWith('ipfs://')) {
|
||||
tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true)
|
||||
}
|
||||
|
||||
const response = await timeoutFetch(tokenURI)
|
||||
const object = await response.json()
|
||||
image = object ? object.image : ''
|
||||
if (image.startsWith('ipfs://')) {
|
||||
image = getFormattedIpfsUrl(ipfsGateway, image, true)
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
standard: ERC721,
|
||||
tokenURI,
|
||||
symbol,
|
||||
name,
|
||||
image,
|
||||
}
|
||||
}
|
||||
|
||||
async transfer({
|
||||
chain,
|
||||
address,
|
||||
from,
|
||||
to,
|
||||
tokenId,
|
||||
account,
|
||||
gas,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
chain: number
|
||||
address?: string
|
||||
from: string
|
||||
to: string
|
||||
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
|
||||
const { instance, account } = new ChainCache().getInstances(chain, address, abiNFT)
|
||||
if (encodeABI) {
|
||||
return contract.methods.safeTransferFrom(from, to, tokenId).encodeABI()
|
||||
return instance.methods.safeTransferFrom(from, to, tokenId).encodeABI()
|
||||
}
|
||||
return contract.methods.safeTransferFrom(from, to, tokenId).send({
|
||||
from,
|
||||
gas: gas || 1000000,
|
||||
let gas = await instance.methods.safeTransferFrom(from, to, tokenId).estimateGas({ from: account.address })
|
||||
return instance.methods.safeTransferFrom(from, to, tokenId).send({
|
||||
gas: (gas * GAS_BOOST) | 0,
|
||||
})
|
||||
}
|
||||
|
||||
async mint({
|
||||
async mintNFT({
|
||||
chain,
|
||||
address,
|
||||
to,
|
||||
tokenId,
|
||||
tokenIds,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
chain: number
|
||||
address?: string
|
||||
to: string
|
||||
tokenId: string
|
||||
tokenIds: string[]
|
||||
encodeABI?: boolean
|
||||
}) {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
const { instance, account } = new ChainCache().getInstances(chain, address, abiNFT)
|
||||
|
||||
if (encodeABI) {
|
||||
return contract.methods.mint(to, tokenId).encodeABI()
|
||||
return instance.methods.batchMint(to, tokenIds).encodeABI()
|
||||
}
|
||||
let gas = await contract.methods.mint(to, tokenId).estimateGas({ gas: 1000000 })
|
||||
return contract.methods.mint(to, tokenId).send({ gas: (gas * 1.5) | 0 })
|
||||
let gas = await instance.methods.batchMint(to, tokenIds).estimateGas({ from: account.address })
|
||||
return instance.methods.batchMint(to, tokenIds).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||
}
|
||||
|
||||
async batchMint({
|
||||
account,
|
||||
async mintBadge({
|
||||
chain,
|
||||
address,
|
||||
to,
|
||||
count,
|
||||
encodeABI = false,
|
||||
}: {
|
||||
account?: string
|
||||
chain: number
|
||||
address?: string
|
||||
to: string
|
||||
count: number
|
||||
encodeABI?: boolean
|
||||
}) {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address })
|
||||
: this.contract
|
||||
const { instance, account } = new ChainCache().getInstances(chain, address, abiBadge)
|
||||
const countStr = count + ''
|
||||
if (encodeABI) {
|
||||
return contract.methods.batchMint(to, countStr).encodeABI()
|
||||
return instance.methods.batchMint(to, countStr).encodeABI()
|
||||
}
|
||||
let gas = await contract.methods.batchMint(to, countStr).estimateGas({ from: account || this.account.address })
|
||||
return contract.methods.batchMint(to, countStr).send({ gas: (gas * 1.5) | 0 })
|
||||
}
|
||||
|
||||
async getPastEvents({ address, fromBlock }: { address?: string; fromBlock: number }) {
|
||||
const contract = address
|
||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
||||
: this.contract
|
||||
return contract.getPastEvents('BatchMint', {
|
||||
fromBlock,
|
||||
toBlock: fromBlock + 50000,
|
||||
})
|
||||
let gas = await instance.methods.batchMint(to, countStr).estimateGas({ from: account.address })
|
||||
return instance.methods.batchMint(to, countStr).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import Web3 from 'web3'
|
||||
import { Account } from 'web3-core'
|
||||
import { ZERO_BYTES32 } from 'common/Constants'
|
||||
import { generateRandomBytes32 } from 'utils/wallet.util'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
const abi = require('abis/BEMultiSigWallet.json').abi
|
||||
|
||||
/**
|
||||
@ -131,13 +132,17 @@ export class WalletReactor {
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
async updateRequired(num: number) {
|
||||
let contractAddress = [process.env.CHAIN_WALLET_ADDRESS]
|
||||
async updateRequired(chain: number, num: number) {
|
||||
let cfg = new ChainCache().chainArray.find(o => o.id === chain)
|
||||
if (!cfg) {
|
||||
throw new Error('chain not support')
|
||||
}
|
||||
let contractAddress = cfg.wallet
|
||||
let values = ['0']
|
||||
let abi = await this.contract.methods.changeRequirement(num + '').encodeABI()
|
||||
let salt = generateRandomBytes32()
|
||||
let operation: any = this.genOperation({
|
||||
targets: contractAddress,
|
||||
targets: [contractAddress],
|
||||
values,
|
||||
datas: [abi],
|
||||
predecessor: ZERO_BYTES32,
|
||||
|
308
src/chain/allchain.ts
Normal file
308
src/chain/allchain.ts
Normal file
@ -0,0 +1,308 @@
|
||||
export const AllChains = [
|
||||
{
|
||||
name: 'Ethereum Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.ankr.com/eth',
|
||||
id: 1,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://etherscan.io',
|
||||
},
|
||||
{
|
||||
name: 'Ethereum Ropsten Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
|
||||
id: 3,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://ropsten.etherscan.io',
|
||||
},
|
||||
{
|
||||
name: 'Ethereum Rinkeby Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://rinkey.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
|
||||
id: 4,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://rinkey.etherscan.io',
|
||||
},
|
||||
{
|
||||
name: 'Ethereum Goerli Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
|
||||
id: 5,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://goerli.etherscan.io',
|
||||
},
|
||||
{
|
||||
name: 'Ethereum Kovan Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://kovan.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
|
||||
id: 6,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://kovan.etherscan.io',
|
||||
},
|
||||
{
|
||||
name: 'Ubiq Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.octano.dev/',
|
||||
id: 8,
|
||||
symbol: 'UBQ',
|
||||
explorerurl: 'https://ubiqscan.io/',
|
||||
},
|
||||
{
|
||||
name: 'Elastos ETH Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://api.elastos.io/eth',
|
||||
id: 20,
|
||||
symbol: 'ELA',
|
||||
explorerurl: 'https://explorer.elaeth.io/',
|
||||
},
|
||||
{
|
||||
name: 'Cronos Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://evm-cronos.crypto.org',
|
||||
id: 25,
|
||||
symbol: 'CRO',
|
||||
explorerurl: 'https://cronos.crypto.org/explorer/',
|
||||
},
|
||||
{
|
||||
name: 'Telos EVM Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://mainnet.telos.net/evm',
|
||||
id: 40,
|
||||
symbol: 'TLOS',
|
||||
explorerurl: 'https://telos.net/',
|
||||
},
|
||||
{
|
||||
name: 'Binance Smart Chain',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.ankr.com/bsc',
|
||||
id: 56,
|
||||
symbol: 'BNB',
|
||||
explorerurl: 'https://bscscan.com',
|
||||
},
|
||||
{
|
||||
name: 'OKExChain Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://exchainrpc.okex.org',
|
||||
id: 66,
|
||||
symbol: 'OKT',
|
||||
explorerurl: 'https://www.oklink.com/okexchain',
|
||||
},
|
||||
{
|
||||
name: 'Hoo Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://http-mainnet.hoosmartchain.com',
|
||||
id: 70,
|
||||
symbol: 'HOO',
|
||||
explorerurl: 'https://hooscan.com',
|
||||
},
|
||||
{
|
||||
name: 'Binance Testnet',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://data-seed-prebsc-1-s1.binance.org:8545/',
|
||||
id: 97,
|
||||
symbol: 'BNB',
|
||||
explorerurl: 'https://testnet.bscscan.com',
|
||||
},
|
||||
{
|
||||
name: 'xDai Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.xdaichain.com/',
|
||||
id: 100,
|
||||
symbol: 'XDAI',
|
||||
explorerurl: 'https://blockscout.com/xdai/mainnet/',
|
||||
},
|
||||
{
|
||||
name: 'Fuse Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.fuse.io',
|
||||
id: 122,
|
||||
symbol: 'FUSE',
|
||||
explorerurl: 'https://explorer.fuse.io/',
|
||||
},
|
||||
{
|
||||
name: 'HECO Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://http-mainnet-node.huobichain.com/',
|
||||
id: 128,
|
||||
symbol: 'HT',
|
||||
explorerurl: 'https://hecoinfo.com/',
|
||||
},
|
||||
{
|
||||
name: 'Matic Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://polygon-rpc.com',
|
||||
id: 137,
|
||||
symbol: 'MATIC',
|
||||
explorerurl: 'https://explorer.matic.network/',
|
||||
},
|
||||
{
|
||||
name: 'Fantom Opera Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.ftm.tools/',
|
||||
id: 250,
|
||||
symbol: 'FTM',
|
||||
explorerurl: 'https://ftmscan.com',
|
||||
},
|
||||
{
|
||||
name: 'HECO Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://http-testnet.hecochain.com',
|
||||
id: 256,
|
||||
symbol: 'HT',
|
||||
explorerurl: 'https://testnet.hecoinfo.com/',
|
||||
},
|
||||
{
|
||||
name: 'KCC Mainnet',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc-mainnet.kcc.network',
|
||||
id: 321,
|
||||
symbol: 'KCS',
|
||||
explorerurl: 'https://scan.kcc.network',
|
||||
},
|
||||
{
|
||||
name: 'KCC Testnet',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://rpc-testnet.kcc.network',
|
||||
id: 322,
|
||||
symbol: 'tKCS',
|
||||
explorerurl: 'https://scan-testnet.kcc.network',
|
||||
},
|
||||
{
|
||||
name: 'Moonriver Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.moonriver.moonbeam.network',
|
||||
id: 1285,
|
||||
symbol: 'MOVR',
|
||||
explorerurl: 'https://blockscout.moonriver.moonbeam.network/',
|
||||
},
|
||||
{
|
||||
name: 'Fantom Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://rpc.testnet.fantom.network/',
|
||||
id: 4002,
|
||||
symbol: 'FTM',
|
||||
explorerurl: 'https://testnet.ftmscan.com',
|
||||
},
|
||||
{
|
||||
name: 'IoTeX Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://babel-api.mainnet.iotex.io',
|
||||
id: 4689,
|
||||
symbol: 'IOTEX',
|
||||
explorerurl: 'https://iotexscan.io/',
|
||||
},
|
||||
{
|
||||
name: 'Nahmii Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://l2.nahmii.io/',
|
||||
id: 5551,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://explorer.nahmii.io/',
|
||||
},
|
||||
{
|
||||
name: 'Nahmii Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://l2.testnet.nahmii.io/',
|
||||
id: 5553,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://explorer.testnet.nahmii.io/',
|
||||
},
|
||||
{
|
||||
name: 'Arbitrum One',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://endpoints.omniatech.io/v1/arbitrum/one/public|https://rpc.ankr.com/arbitrum',
|
||||
id: 42161,
|
||||
network: 'ARBITRUM',
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://arbiscan.io/',
|
||||
},
|
||||
{
|
||||
name: 'Celo Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.ankr.com/celo',
|
||||
id: 42220,
|
||||
symbol: 'CELO',
|
||||
explorerurl: 'https://celoscan.com',
|
||||
},
|
||||
{
|
||||
name: 'Avalanche C Chain Local RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://localhost:9650/ext/bc/C/rpc',
|
||||
id: 43112,
|
||||
symbol: 'AVAX',
|
||||
explorerurl: 'https://snowtrace.io',
|
||||
},
|
||||
{
|
||||
name: 'Avalanche FUJI Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://api.avax-test.network/ext/bc/C/rpc',
|
||||
id: 43113,
|
||||
symbol: 'AVAX',
|
||||
explorerurl: 'https://testnet.explorer.avax.network/',
|
||||
},
|
||||
{
|
||||
name: 'Avalanche C Chain Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://rpc.ankr.com/avalanche',
|
||||
id: 43114,
|
||||
symbol: 'AVAX',
|
||||
explorerurl: 'https://snowtrace.io',
|
||||
},
|
||||
{
|
||||
name: 'Matic Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://rpc-mumbai.maticvigil.com|https://matic-mumbai.chainstacklabs.com|https://polygon-testnet.public.blastapi.io|https://rpc.ankr.com/polygon_mumbai',
|
||||
id: 80001,
|
||||
symbol: 'MATIC',
|
||||
explorerurl: 'https://mumbai.polygonscan.com/',
|
||||
},
|
||||
{
|
||||
name: 'Arbitrum Goerli',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://goerli-rollup.arbitrum.io/rpc|https://endpoints.omniatech.io/v1/arbitrum/goerli/public',
|
||||
id: 421613,
|
||||
network: 'AGOR',
|
||||
symbol: 'AGOR',
|
||||
explorerurl: 'https://goerli-rollup-explorer.arbitrum.io',
|
||||
},
|
||||
{
|
||||
name: 'zkSync Era Mainnet',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://mainnet.era.zksync.io',
|
||||
id: 324,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://explorer.zksync.io',
|
||||
},
|
||||
{
|
||||
name: 'zkSync Era Testnet',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://testnet.era.zksync.dev',
|
||||
id: 280,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://goerli.explorer.zksync.io',
|
||||
},
|
||||
{
|
||||
name: 'Harmony Mainnet RPC',
|
||||
type: 'Mainnet',
|
||||
rpc: 'https://api.harmony.one/',
|
||||
id: 1666600000,
|
||||
symbol: 'ONE',
|
||||
explorerurl: 'https://explorer.harmony.one',
|
||||
},
|
||||
{
|
||||
name: 'Harmony Testnet RPC',
|
||||
type: 'Testnet',
|
||||
rpc: 'https://api.s0.b.hmny.io/',
|
||||
id: 1666700000,
|
||||
symbol: 'ONE',
|
||||
explorerurl: 'https://explorer.harmony.one',
|
||||
},
|
||||
{
|
||||
name: 'Local Testnet',
|
||||
type: 'Local',
|
||||
rpc: 'https://login-test.kingsome.cn/rpc',
|
||||
id: 1338,
|
||||
symbol: 'ETH',
|
||||
explorerurl: 'https://explorer.harmony.one',
|
||||
},
|
||||
]
|
@ -2,7 +2,12 @@ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
|
||||
export const MAX_BATCH_REQ_COUNT = 50
|
||||
// 多签钱包单次最大请求数量
|
||||
export const MAX_BATCH_REQ_COUNT = 500
|
||||
// 空投单次最大请求数量
|
||||
export const MAX_AIRDROP_COUNT = 500
|
||||
|
||||
export const GAS_BOOST = 1.2
|
||||
|
||||
export const CONFIRM_MAIL_HTML = `
|
||||
<p>{{title}}</p>
|
||||
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"address": "0xfeFc3aab779863c1624eE008aba485c53805dCeb",
|
||||
"event": "Confirmation",
|
||||
"abi": "BEMultiSigWallet",
|
||||
"fromBlock": 34804697,
|
||||
"eventProcesser": "ScheduleConfirmEvent"
|
||||
}
|
||||
]
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"0xfa44C759f0D51e749ba591a79b3f7F16a4d41CEC": 104
|
||||
}
|
@ -8,6 +8,7 @@ import { BlockChain } from 'chain/BlockChain'
|
||||
import { ChainTask } from 'models/ChainTask'
|
||||
import { isObjectId } from 'utils/string.util'
|
||||
import { WechatWorkService } from 'service/wechatwork.service'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
|
||||
class WorkFlowController extends BaseController {
|
||||
@role(ROLE_ANON)
|
||||
@ -75,7 +76,8 @@ class WorkFlowController extends BaseController {
|
||||
let tasks = requestTasks.map(o => {
|
||||
return { scheduleId: o.scheduleId, reqDatas: o.reqDatas }
|
||||
})
|
||||
let address = process.env.CHAIN_WALLET_ADDRESS
|
||||
let cfg = new ChainCache().chainArray.find(o => o.id === chainTask.chain)
|
||||
let address = cfg.wallet
|
||||
let types = Object.fromEntries(TaskTypeMap)
|
||||
return {
|
||||
chainTask: taskObj,
|
||||
@ -88,13 +90,16 @@ class WorkFlowController extends BaseController {
|
||||
@role(ROLE_ANON)
|
||||
@router('get /workflow/update_required')
|
||||
async updateRequired(req, res) {
|
||||
let result = await new BlockChain().walletReactor.updateRequired(1)
|
||||
let { chain } = req.params
|
||||
chain = parseInt(chain)
|
||||
let result = await new ChainCache().getWallet(chain).updateRequired(chain, 1)
|
||||
return result
|
||||
}
|
||||
|
||||
@role(ROLE_ANON)
|
||||
@router('post /workflow/update_required')
|
||||
async execUpdateRequired(req, res) {
|
||||
let { chain } = req.params
|
||||
let data = {
|
||||
scheduleId: '0xa5c35368cd44dbe805a4595d6813ed3afefa1bf667209dc8d63f99cdec117f58',
|
||||
targets: ['0xc195196351566d2c4e13563C4492fB0BdB7894Fb'],
|
||||
@ -103,7 +108,7 @@ class WorkFlowController extends BaseController {
|
||||
predecessor: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||
salt: '0x39383830353131363736333036',
|
||||
}
|
||||
let result = await new BlockChain().walletReactor.executeSchedule(data)
|
||||
let result = await new ChainCache().getWallet(chain).executeSchedule(data)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,8 @@ export class ChainTaskClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public taskId!: string
|
||||
@prop()
|
||||
public chain: number
|
||||
@prop()
|
||||
public name: string
|
||||
@prop()
|
||||
public desc: string
|
||||
@ -142,10 +144,9 @@ export class ChainTaskClass extends BaseModule {
|
||||
/**
|
||||
* 解析企业微信审批信息
|
||||
*/
|
||||
// TODO:: mint nft 处理原始数据, 如果传入的是amount, 那么根据规则生成tokenid
|
||||
public static async parseWxApprovalInfo({ taskId, name, desc, data, starter, starterName }) {
|
||||
public static async parseWxApprovalInfo({ taskId, name, desc, data, starter, starterName, chain }) {
|
||||
let maxTryCount = parseInt(process.env.CHAIN_MAX_TRY)
|
||||
let chainTask = await ChainTask.insertOrUpdate({ taskId }, { name, desc, starter, starterName, data })
|
||||
let chainTask = await ChainTask.insertOrUpdate({ taskId }, { name, desc, starter, starterName, data, chain })
|
||||
let subTasks: any = []
|
||||
if (chainTask.newRecord) {
|
||||
let subTask
|
||||
@ -156,6 +157,7 @@ export class ChainTaskClass extends BaseModule {
|
||||
subTask = new RequestTask({
|
||||
taskId,
|
||||
index,
|
||||
chain,
|
||||
chainTaskId: chainTask.id,
|
||||
reqDatas: [],
|
||||
maxTryCount,
|
||||
|
@ -3,6 +3,7 @@ import { BlockChain } from 'chain/BlockChain'
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import logger from 'logger/logger'
|
||||
import { BaseModule } from './Base'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
|
||||
export enum TaskType {
|
||||
UNKNOW = 0,
|
||||
@ -11,6 +12,8 @@ export enum TaskType {
|
||||
TRANSFER_FT = 3,
|
||||
TRANSFER_NFT = 4,
|
||||
PUBLISH_AIRDROP_LIST = 5,
|
||||
AIRDROP_NFT_ACTIVITY = 6,
|
||||
AIRDROP_NFT_INGAME = 7,
|
||||
}
|
||||
|
||||
export const TaskTypeMap = new Map([
|
||||
@ -20,6 +23,8 @@ export const TaskTypeMap = new Map([
|
||||
[TaskType.TRANSFER_FT, 'Ft转账'],
|
||||
[TaskType.TRANSFER_NFT, 'NFT转账'],
|
||||
[TaskType.PUBLISH_AIRDROP_LIST, '公布空投名单'],
|
||||
[TaskType.AIRDROP_NFT_ACTIVITY, '空投活动NFT'],
|
||||
[TaskType.AIRDROP_NFT_INGAME, '空投游戏中的NFT'],
|
||||
])
|
||||
|
||||
export enum ReqTaskStatus {
|
||||
@ -47,6 +52,9 @@ export class RequestTaskClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public chainTaskId!: string
|
||||
|
||||
@prop()
|
||||
public chain: number
|
||||
|
||||
@prop({ default: 0 })
|
||||
public index: number
|
||||
|
||||
@ -120,7 +128,7 @@ export class RequestTaskClass extends BaseModule {
|
||||
let self = this
|
||||
self.blockReq = new BlockChain().currentBlockNum
|
||||
await self.save()
|
||||
let result = await new BlockChain().walletReactor.beginSchedule(self, 60)
|
||||
let result = await new ChainCache().getWallet(self.chain).beginSchedule(self, 60)
|
||||
logger.info(result)
|
||||
let { transactionHash } = result
|
||||
self.txHash = transactionHash
|
||||
@ -130,7 +138,7 @@ export class RequestTaskClass extends BaseModule {
|
||||
|
||||
public async execSchdule(this: DocumentType<RequestTaskClass>) {
|
||||
let self = this
|
||||
let result = await new BlockChain().walletReactor.executeSchedule(self)
|
||||
let result = await new ChainCache().getWallet(self.chain).executeSchedule(self)
|
||||
logger.info('schedule exec result:')
|
||||
logger.info(result)
|
||||
let { transactionHash } = result
|
||||
|
@ -5,7 +5,7 @@ import { TaskSvr } from 'service/task.service'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
@dbconn()
|
||||
@index({ transactionHash: 1 }, { unique: true })
|
||||
@index({ chain: 1, transactionHash: 1 }, { unique: true })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'schedule_confirm_event', timestamps: true },
|
||||
})
|
||||
@ -13,6 +13,8 @@ export class ScheduleConfirmEventClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public address!: string
|
||||
@prop()
|
||||
public chain: number
|
||||
@prop()
|
||||
public event: string
|
||||
@prop({ required: true })
|
||||
public transactionHash: string
|
||||
@ -48,7 +50,10 @@ export class ScheduleConfirmEventClass extends BaseModule {
|
||||
$inc: { version: 1 },
|
||||
}
|
||||
|
||||
let record = await ScheduleConfirmEvent.insertOrUpdate({ transactionHash: event.transactionHash }, data)
|
||||
let record = await ScheduleConfirmEvent.insertOrUpdate(
|
||||
{ transactionHash: event.transactionHash, chain: event.chain },
|
||||
data,
|
||||
)
|
||||
if (record.version === 1) {
|
||||
logger.log('receive events: ' + JSON.stringify(record.scheduleIds))
|
||||
for (let id of record.scheduleIds) {
|
||||
|
@ -3,7 +3,7 @@ import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
@dbconn()
|
||||
@index({ transactionHash: 1, scheduleId: 1 }, { unique: true })
|
||||
@index({ chain: 1, transactionHash: 1, scheduleId: 1 }, { unique: true })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'schedule_executed_event', timestamps: true },
|
||||
})
|
||||
@ -11,6 +11,8 @@ export class ScheduleExecutedEventClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public address!: string
|
||||
@prop()
|
||||
public chain: number
|
||||
@prop()
|
||||
public event: string
|
||||
@prop({ required: true })
|
||||
public transactionHash: string
|
||||
@ -45,7 +47,7 @@ export class ScheduleExecutedEventClass extends BaseModule {
|
||||
}
|
||||
|
||||
let record = await ScheduleExecutedEvent.insertOrUpdate(
|
||||
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id },
|
||||
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id, chain: event.chain },
|
||||
data,
|
||||
)
|
||||
if (record.version === 1) {
|
||||
|
@ -3,7 +3,7 @@ import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
@dbconn()
|
||||
@index({ transactionHash: 1, scheduleId: 1 }, { unique: true })
|
||||
@index({ chain: 1, transactionHash: 1, scheduleId: 1 }, { unique: true })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'schedule_added_event', timestamps: true },
|
||||
})
|
||||
@ -11,6 +11,8 @@ export class ScheduledAddedEventClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public address!: string
|
||||
@prop()
|
||||
public chain: number
|
||||
@prop()
|
||||
public event: string
|
||||
@prop({ required: true })
|
||||
public transactionHash: string
|
||||
@ -45,7 +47,7 @@ export class ScheduledAddedEventClass extends BaseModule {
|
||||
}
|
||||
|
||||
return ScheduledAddedEvent.insertOrUpdate(
|
||||
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id },
|
||||
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id, chain: event.chain },
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
@ -21,12 +21,13 @@ let eventProcessers = {
|
||||
ScheduleExecutedEvent: ScheduleExecutedEvent,
|
||||
}
|
||||
|
||||
const events = require('config/events.json')
|
||||
const events = require('../config/events.json')
|
||||
|
||||
async function initEventSvrs() {
|
||||
// let nfts = [{ address: '0x37c30a2945799a53c5358636a721b442458fa691' }]
|
||||
for (let event of events) {
|
||||
let eventSvr = new EventSyncSvr({
|
||||
chain: event.chain,
|
||||
address: event.address,
|
||||
event: event.event,
|
||||
abi: require('abis/' + event.abi + '.json').abi,
|
||||
|
@ -5,12 +5,13 @@ import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
||||
import { BlockChain } from 'chain/BlockChain'
|
||||
import { ChainTask } from 'models/ChainTask'
|
||||
import logger from 'logger/logger'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
|
||||
const EXCLUDE_STATUS = [
|
||||
ReqTaskStatus.SUCCESS,
|
||||
ReqTaskStatus.WAIT_EXEC,
|
||||
ReqTaskStatus.WAIT_EXEC_CONFIRM,
|
||||
ReqTaskStatus.EXEC_REVERT,
|
||||
5, //ReqTaskStatus.SUCCESS,
|
||||
3, //ReqTaskStatus.WAIT_EXEC,
|
||||
4, //ReqTaskStatus.WAIT_EXEC_CONFIRM,
|
||||
9, //ReqTaskStatus.EXEC_REVERT,
|
||||
]
|
||||
@singleton
|
||||
export class ChainQueue {
|
||||
@ -37,7 +38,7 @@ export class ChainQueue {
|
||||
await subTask.save()
|
||||
}
|
||||
if (subTask.status === ReqTaskStatus.WAIT_CONFIRM) {
|
||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
||||
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||
return
|
||||
}
|
||||
|
||||
@ -61,7 +62,7 @@ export class ChainQueue {
|
||||
this.addTaskToQueue(subTask)
|
||||
return
|
||||
}
|
||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
||||
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||
} catch (err) {
|
||||
subTask.errMsg.push(err)
|
||||
await subTask.save()
|
||||
|
@ -5,13 +5,14 @@ import logger from 'logger/logger'
|
||||
import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
||||
import { DocumentType } from '@typegoose/typegoose'
|
||||
import { ChainTask } from 'models/ChainTask'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
|
||||
const EXCLUDE_STATUS = [
|
||||
ReqTaskStatus.NOTSTART,
|
||||
ReqTaskStatus.SUCCESS,
|
||||
ReqTaskStatus.PEDING,
|
||||
ReqTaskStatus.WAIT_CONFIRM,
|
||||
ReqTaskStatus.SCHEDULE_REVERT,
|
||||
0, //ReqTaskStatus.NOTSTART,
|
||||
5, //ReqTaskStatus.SUCCESS,
|
||||
1, //ReqTaskStatus.PEDING,
|
||||
2, //ReqTaskStatus.WAIT_CONFIRM,
|
||||
8, //ReqTaskStatus.SCHEDULE_REVERT,
|
||||
]
|
||||
@singleton
|
||||
export class ExecQueue {
|
||||
@ -34,7 +35,7 @@ export class ExecQueue {
|
||||
return
|
||||
}
|
||||
if (subTask.status === ReqTaskStatus.WAIT_EXEC_CONFIRM) {
|
||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
||||
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||
return
|
||||
}
|
||||
this.queue.push(async () => {
|
||||
@ -50,7 +51,7 @@ export class ExecQueue {
|
||||
this.addTaskToQueue(subTask)
|
||||
return
|
||||
}
|
||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
||||
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||
} catch (err) {
|
||||
logger.error('error add task: ' + err)
|
||||
subTask.errMsg.push(err)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
import { BlockChain } from 'chain/BlockChain'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import * as schedule from 'node-schedule'
|
||||
@ -5,7 +6,9 @@ import * as schedule from 'node-schedule'
|
||||
@singleton
|
||||
export default class BlocknumSchedule {
|
||||
parseAllRecord() {
|
||||
new BlockChain().updateCurrenBlockNum()
|
||||
for (let cfg of new ChainCache().chainArray) {
|
||||
new BlockChain().updateCurrenBlockNum(cfg.id)
|
||||
}
|
||||
}
|
||||
scheduleAll() {
|
||||
const job = schedule.scheduleJob('*/5 * * * * *', async () => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
import { HttpRetryProvider } from 'chain/HttpRetryProvider'
|
||||
import logger from 'logger/logger'
|
||||
import { NftTransferEvent } from 'models/NftTransferEvent'
|
||||
@ -8,6 +9,7 @@ import Web3 from 'web3'
|
||||
|
||||
export class EventSyncSvr {
|
||||
web3: Web3
|
||||
chain: number
|
||||
provider: HttpRetryProvider
|
||||
fromBlock: number = 27599018
|
||||
toBlock: number = 10000
|
||||
@ -18,26 +20,27 @@ export class EventSyncSvr {
|
||||
eventProcesser: any
|
||||
|
||||
constructor({
|
||||
chain,
|
||||
address,
|
||||
event,
|
||||
abi,
|
||||
fromBlock,
|
||||
eventProcesser,
|
||||
}: {
|
||||
chain: number
|
||||
address: string
|
||||
event: string
|
||||
abi: any
|
||||
fromBlock: number
|
||||
eventProcesser: any
|
||||
}) {
|
||||
this.provider = new HttpRetryProvider(process.env.CHAIN_RPC_URL.split('|'))
|
||||
// @ts-ignore
|
||||
this.web3 = new Web3(this.provider)
|
||||
this.chain = chain
|
||||
this.web3 = new ChainCache().getWeb3(chain)
|
||||
this.contract = new this.web3.eth.Contract(abi, address)
|
||||
this.address = this.contract.options.address
|
||||
this.event = event
|
||||
this.fromBlock = fromBlock
|
||||
this.blockKey = `${address.toLowerCase()}_${event}`
|
||||
this.blockKey = `${chain}_${address.toLowerCase()}_${event}`
|
||||
this.eventProcesser = eventProcesser
|
||||
}
|
||||
|
||||
@ -53,13 +56,14 @@ export class EventSyncSvr {
|
||||
}
|
||||
logger.log(`query events:: ${this.event} address: ${this.address} from: ${this.fromBlock} to: ${this.toBlock}`)
|
||||
let events = getPastEventsIter({
|
||||
chain: this.chain,
|
||||
contract: this.contract,
|
||||
event: this.event,
|
||||
fromBlock: this.fromBlock,
|
||||
toBlock: this.toBlock,
|
||||
})
|
||||
// this.fromBlock = this.toBlock
|
||||
await processEvents(this.web3, events, this.eventProcesser.saveEvent)
|
||||
await processEvents(this.web3, events, this.chain, this.eventProcesser.saveEvent)
|
||||
// 处理完一种nft后, 清楚block的timestamp缓存
|
||||
clearTimeCache()
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { ReqTaskStatus, RequestTask } from 'models/RequestTask'
|
||||
import { ChainQueue } from 'queue/chain.queue'
|
||||
import { ExecQueue } from 'queue/exec.queue'
|
||||
import { WechatWorkService } from './wechatwork.service'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
|
||||
@singleton
|
||||
export class TaskSvr {
|
||||
@ -14,7 +15,7 @@ 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)
|
||||
let { scheduleId } = new ChainCache().getWallet(data.chain).genOperation(subTask)
|
||||
subTask.scheduleId = scheduleId
|
||||
await subTask.save()
|
||||
new ChainQueue().addTaskToQueue(subTask)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { ChainCache } from 'cache/ChainCache'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
@ -240,8 +241,23 @@ export class WechatWorkService {
|
||||
let userInfo = await this.fetchUserInfo(starter)
|
||||
let starterName = userInfo.name
|
||||
let { filename } = await this.fetchFile(fileId)
|
||||
let { data, chain } = this.parseOneExcel(filename)
|
||||
return { taskId: spNo, name, desc, data, starter, starterName, chain }
|
||||
}
|
||||
|
||||
public parseOneExcel(filename: string) {
|
||||
let data = excelToJson(filename)
|
||||
return { taskId: spNo, name, desc, data, starter, starterName }
|
||||
let chain: number
|
||||
if (data && data.length) {
|
||||
chain = data[0].chain ? parseInt(data[0].chain) : 0
|
||||
}
|
||||
if (!chain) {
|
||||
throw new Error('no chain')
|
||||
}
|
||||
if (new ChainCache().chainArray.findIndex(o => o.id === chain) < 0) {
|
||||
throw new Error('chain not support')
|
||||
}
|
||||
return { data, chain }
|
||||
}
|
||||
// 检查审核记录中的文件
|
||||
private async queryMedidaIdFromSprecord(datas: any) {
|
||||
|
@ -170,12 +170,14 @@ export async function getPastEvents({
|
||||
}
|
||||
|
||||
export function* getPastEventsIter({
|
||||
chain,
|
||||
contract,
|
||||
event,
|
||||
fromBlock,
|
||||
toBlock,
|
||||
options,
|
||||
}: {
|
||||
chain: number
|
||||
contract: any
|
||||
event: string
|
||||
fromBlock: number
|
||||
@ -183,7 +185,7 @@ export function* getPastEventsIter({
|
||||
options?: any
|
||||
}) {
|
||||
const address = contract.options.address
|
||||
const redisKey = `${address.toLowerCase()}_${event}`
|
||||
const redisKey = `${chain}_${address.toLowerCase()}_${event}`
|
||||
logger.debug(`*getPastEventsIter: ${event} from: ${fromBlock} to: ${toBlock}`)
|
||||
let from = toBN(fromBlock)
|
||||
let to = toBN(fromBlock).add(queryRange)
|
||||
@ -198,7 +200,7 @@ export function* getPastEventsIter({
|
||||
yield new RedisClient().set(redisKey, toBlockBN.add(ONE) + '')
|
||||
}
|
||||
|
||||
export async function processEvents(web3, iterator, processedEvent) {
|
||||
export async function processEvents(web3, iterator, chain: number, processedEvent) {
|
||||
for (const getPastEventPromise of iterator) {
|
||||
const events = await getPastEventPromise
|
||||
for (const event of events) {
|
||||
@ -211,6 +213,7 @@ export async function processEvents(web3, iterator, processedEvent) {
|
||||
blockTimeMap.set(event.blockNumber, blockData.timestamp)
|
||||
}
|
||||
}
|
||||
event.chain = chain
|
||||
await processedEvent(event)
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,6 @@ import { IDCounter } from 'models/IDCounter'
|
||||
export const ONE_DAY = 24 * 60 * 60 * 1000
|
||||
export const NFT_BEGIN_DAY = new Date(2023, 4, 8)
|
||||
|
||||
export const NFT_TYPE = require('config/nfttypes.json')
|
||||
|
||||
export const MINT_CHANNEL = {
|
||||
claim: '01', // 2022购买用户claim
|
||||
}
|
||||
|
||||
// calc days between two Date
|
||||
export function daysBetween(date1: Date, date2: Date) {
|
||||
// hours*minutes*seconds*milliseconds
|
||||
|
Loading…
x
Reference in New Issue
Block a user