增加多链支持

This commit is contained in:
CounterFire2023 2023-07-12 11:31:24 +08:00
parent 19c2828192
commit 3d36b846be
30 changed files with 39032 additions and 15973 deletions

7
config/chains.json Normal file
View File

@ -0,0 +1,7 @@
[
{
"id": 421613,
"key": "0xd9ed33809372932059c1ba7b336a33f406b4c55e7430daef8297134c67429d60",
"wallet": "0xE68F149daF2F314d9960c08496D8701BC7671850"
}
]

10
config/events.json Normal file
View File

@ -0,0 +1,10 @@
[
{
"address": "0xE68F149daF2F314d9960c08496D8701BC7671850",
"event": "Confirmation",
"abi": "BEMultiSigWallet",
"fromBlock": 30117942,
"eventProcesser": "ScheduleConfirmEvent",
"chain": 421613
}
]

File diff suppressed because one or more lines are too long

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
View 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 }
}
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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 })
}
}

View File

@ -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 })
}
}

View File

@ -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
View 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',
},
]

View File

@ -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>

View File

@ -1,9 +0,0 @@
[
{
"address": "0xfeFc3aab779863c1624eE008aba485c53805dCeb",
"event": "Confirmation",
"abi": "BEMultiSigWallet",
"fromBlock": 34804697,
"eventProcesser": "ScheduleConfirmEvent"
}
]

View File

@ -1,3 +0,0 @@
{
"0xfa44C759f0D51e749ba591a79b3f7F16a4d41CEC": 104
}

View File

@ -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
}

View File

@ -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,

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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,
)
}

View File

@ -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,

View File

@ -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()

View File

@ -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)

View File

@ -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 () => {

View File

@ -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()
}

View File

@ -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)

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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