增加多链支持
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 logger from 'logger/logger'
|
||||||
import { TaskType } from 'models/RequestTask'
|
import { TaskType } from 'models/RequestTask'
|
||||||
import { ConfirmQueue } from 'queue/confirm.queue'
|
import { ConfirmQueue } from 'queue/confirm.queue'
|
||||||
import Web3 from 'web3'
|
import { TransactionReceipt } from 'web3-core'
|
||||||
import { TransactionReceipt, AddedAccount } from 'web3-core'
|
|
||||||
import { ERC20Reactor } from './ERC20Reactor'
|
import { ERC20Reactor } from './ERC20Reactor'
|
||||||
import { ERC721Reactor } from './ERC721Reactor'
|
import { ERC721Reactor } from './ERC721Reactor'
|
||||||
import { HttpRetryProvider } from './HttpRetryProvider'
|
|
||||||
import { WalletReactor } from './WalletReactor'
|
|
||||||
import { DistributorReactor } from './DistributorReactor'
|
import { DistributorReactor } from './DistributorReactor'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
|
|
||||||
@singleton
|
@singleton
|
||||||
export class BlockChain {
|
export class BlockChain {
|
||||||
private web3: Web3
|
|
||||||
instanceCacheMap: Map<string, any>
|
|
||||||
private accountMaster: AddedAccount
|
|
||||||
public erc20Reactor: ERC20Reactor
|
public erc20Reactor: ERC20Reactor
|
||||||
public erc721Reactor: ERC721Reactor
|
public erc721Reactor: ERC721Reactor
|
||||||
public walletReactor: WalletReactor
|
|
||||||
public distributorReactor: DistributorReactor
|
public distributorReactor: DistributorReactor
|
||||||
public confirmQueue: ConfirmQueue
|
public confirmQueue: ConfirmQueue
|
||||||
public currentBlockNum: number = 0
|
public currentBlockNum: number = 0
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const provider = new HttpRetryProvider(process.env.CHAIN_RPC_URL.split('|'))
|
new ChainCache()
|
||||||
this.web3 = new Web3(provider)
|
this.erc20Reactor = new ERC20Reactor()
|
||||||
this.confirmQueue = new ConfirmQueue(this.web3)
|
this.erc721Reactor = new ERC721Reactor()
|
||||||
let key = process.env.CHAIN_MASTER_KEY
|
this.distributorReactor = new DistributorReactor()
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getContractInstance(address: string, abi: any) {
|
public async getTxConfirms(chain: number, txhash: string) {
|
||||||
if (!this.instanceCacheMap.has(address)) {
|
let web3 = new ChainCache().getWeb3(chain)
|
||||||
const instance = new this.web3.eth.Contract(abi, address, { from: this.accountMaster.address })
|
const receipt: TransactionReceipt = await web3.eth.getTransactionReceipt(txhash)
|
||||||
this.instanceCacheMap.set(address, instance)
|
const latest = await web3.eth.getBlockNumber()
|
||||||
}
|
|
||||||
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()
|
|
||||||
return latest - receipt.blockNumber + 1
|
return latest - receipt.blockNumber + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateCurrenBlockNum() {
|
public async updateCurrenBlockNum(chain: number) {
|
||||||
this.currentBlockNum = await this.web3.eth.getBlockNumber()
|
let web3 = new ChainCache().getWeb3(chain)
|
||||||
|
this.currentBlockNum = await web3.eth.getBlockNumber()
|
||||||
// logger.debug(`update block num: ${this.currentBlockNum}`)
|
// logger.debug(`update block num: ${this.currentBlockNum}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +47,9 @@ export class BlockChain {
|
|||||||
case TaskType.MINT_NFT:
|
case TaskType.MINT_NFT:
|
||||||
reqData.tokenId = reqData.tokenId || reqData.tokenid
|
reqData.tokenId = reqData.tokenId || reqData.tokenid
|
||||||
if (reqData.tokenId && !reqData.amount) {
|
if (reqData.tokenId && !reqData.amount) {
|
||||||
abi = await this.erc721Reactor.mint(reqData)
|
abi = await this.erc721Reactor.mintNFT(reqData)
|
||||||
} else {
|
} else {
|
||||||
abi = await this.erc721Reactor.batchMint(reqData)
|
abi = await this.erc721Reactor.mintBadge(reqData)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case TaskType.TRANSFER_FT:
|
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 { ChainCache } from 'cache/ChainCache'
|
||||||
import Web3 from 'web3'
|
import { GAS_BOOST } from 'common/Constants'
|
||||||
import { Account } from 'web3-core'
|
|
||||||
const abi = require('abis/NftDistributor.json').abi
|
const abi = require('abis/NftDistributor.json').abi
|
||||||
|
|
||||||
export class DistributorReactor {
|
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列表
|
* 发布NFT列表
|
||||||
*/
|
*/
|
||||||
async publishAirdropList({
|
async publishAirdropList({
|
||||||
|
chain,
|
||||||
address,
|
address,
|
||||||
to,
|
to,
|
||||||
nftList,
|
nftList,
|
||||||
encodeABI = false,
|
encodeABI = false,
|
||||||
}: {
|
}: {
|
||||||
address?: string
|
chain: number
|
||||||
|
address: string
|
||||||
to: string
|
to: string
|
||||||
nftList: string[]
|
nftList: string[]
|
||||||
encodeABI?: boolean
|
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) {
|
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 gas = await instance.methods.addNFTData(to, nftList).estimateGas({ from: account.address })
|
||||||
let res = await contract.methods.addNFTData(to, nftList).send({ gas: gas | 0 })
|
let res = await instance.methods.addNFTData(to, nftList).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* mint nft to user
|
* mint nft to user
|
||||||
*/
|
*/
|
||||||
async mintNft({
|
async mintNft({
|
||||||
|
chain,
|
||||||
address,
|
address,
|
||||||
to,
|
to,
|
||||||
count,
|
count,
|
||||||
encodeABI = false,
|
encodeABI = false,
|
||||||
}: {
|
}: {
|
||||||
|
chain: number
|
||||||
address?: string
|
address?: string
|
||||||
to: string
|
to: string
|
||||||
count: number
|
count: number
|
||||||
encodeABI?: boolean
|
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 + ''
|
const countNft = count + ''
|
||||||
if (encodeABI) {
|
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 gas = await instance.methods.mintToUser(to, countNft).estimateGas({ from: account.address })
|
||||||
let res = await contract.methods.mintToUser(to, countNft).send({ gas: gas | 0 })
|
let res = await instance.methods.mintToUser(to, countNft).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 查询用户可mint的数量
|
* 查询用户可mint的数量
|
||||||
*/
|
*/
|
||||||
async getMintableCount({ address, user }: { address?: string; user: string }) {
|
async getMintableCount({ chain, contract, user }: { chain: number; contract?: string; user: string }) {
|
||||||
const contract = address ? new this.web3.eth.Contract(abi, address, { from: this.account.address }) : this.contract
|
const { instance } = new ChainCache().getInstances(chain, contract, abi)
|
||||||
return await contract.methods.getMintableCount(user).call()
|
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 Web3 from 'web3'
|
||||||
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
const abi = require('abis/ERC20.json').abi
|
||||||
import { Account } from 'web3-core'
|
|
||||||
|
|
||||||
const abiFt = require('abis/ERC20.json').abi
|
|
||||||
export class ERC20Reactor {
|
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({
|
async transfer({
|
||||||
|
chain,
|
||||||
address,
|
address,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
amount,
|
amount,
|
||||||
account,
|
|
||||||
gas,
|
|
||||||
encodeABI = false,
|
encodeABI = false,
|
||||||
}: {
|
}: {
|
||||||
|
chain: number
|
||||||
address: string
|
address: string
|
||||||
from: string
|
from: string
|
||||||
to: string
|
to: string
|
||||||
account?: string
|
|
||||||
amount: number | string
|
amount: number | string
|
||||||
gas?: number
|
|
||||||
encodeABI: boolean
|
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 + ''))
|
const amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
|
||||||
if (encodeABI) {
|
if (encodeABI) {
|
||||||
return contract.methods.transfer(to, amountBN).encodeABI()
|
return instance.methods.transfer(to, amountBN).encodeABI()
|
||||||
}
|
}
|
||||||
return contract.methods.transferFrom(from, to, amountBN).send({
|
let gas = await instance.methods.transferFrom(from, to, amountBN).estimateGas({ from: account.address })
|
||||||
gas: gas || 1000000,
|
return instance.methods.transferFrom(from, to, amountBN).send({
|
||||||
|
gas: (gas * GAS_BOOST) | 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async mint({
|
async mint({
|
||||||
|
chain,
|
||||||
address,
|
address,
|
||||||
to,
|
to,
|
||||||
amount,
|
amount,
|
||||||
account,
|
|
||||||
encodeABI = false,
|
encodeABI = false,
|
||||||
}: {
|
}: {
|
||||||
account?: string
|
chain: number
|
||||||
address?: string
|
address?: string
|
||||||
to: string
|
to: string
|
||||||
amount: string
|
amount: string
|
||||||
encodeABI: boolean
|
encodeABI: boolean
|
||||||
}) {
|
}) {
|
||||||
const contract = address
|
const { instance, account } = new ChainCache().getInstances(chain, address, abi)
|
||||||
? new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address })
|
|
||||||
: this.contract
|
|
||||||
let amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
|
let amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
|
||||||
if (encodeABI) {
|
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 })
|
let gas = await instance.methods.mint(to, amountBN).estimateGas({ from: account.address })
|
||||||
return contract.methods.mint(to, amountBN).send({ gas: (gas * 1.5) | 0 })
|
return instance.methods.mint(to, amountBN).send({ gas: (gas * GAS_BOOST) | 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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,379 +1,76 @@
|
|||||||
import { timeoutFetch } from 'utils/net.util'
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
import { getFormattedIpfsUrl } from 'utils/wallet.util'
|
import { GAS_BOOST } from 'common/Constants'
|
||||||
import Web3 from 'web3'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { Account } from 'web3-core'
|
|
||||||
|
|
||||||
export const ERC721 = 'ERC721'
|
const abiBadge = require('abis/BEBadge.json').abi
|
||||||
export const ERC721_INTERFACE_ID = '0x80ac58cd'
|
const abiNFT = require('abis/NFT.json').abi
|
||||||
export const ERC721_METADATA_INTERFACE_ID = '0x5b5e139f'
|
|
||||||
export const ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63'
|
|
||||||
|
|
||||||
const abiNft = require('abis/BEBadge.json').abi
|
|
||||||
|
|
||||||
export class ERC721Reactor {
|
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({
|
async transfer({
|
||||||
|
chain,
|
||||||
address,
|
address,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
tokenId,
|
tokenId,
|
||||||
account,
|
|
||||||
gas,
|
|
||||||
encodeABI = false,
|
encodeABI = false,
|
||||||
}: {
|
}: {
|
||||||
|
chain: number
|
||||||
address?: string
|
address?: string
|
||||||
from: string
|
from: string
|
||||||
to: string
|
to: string
|
||||||
tokenId: string
|
tokenId: string
|
||||||
account?: string
|
|
||||||
gas?: number
|
|
||||||
encodeABI?: boolean
|
encodeABI?: boolean
|
||||||
}) {
|
}) {
|
||||||
const contract = address
|
const { instance, account } = new ChainCache().getInstances(chain, address, abiNFT)
|
||||||
? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address })
|
|
||||||
: this.contract
|
|
||||||
if (encodeABI) {
|
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({
|
let gas = await instance.methods.safeTransferFrom(from, to, tokenId).estimateGas({ from: account.address })
|
||||||
from,
|
return instance.methods.safeTransferFrom(from, to, tokenId).send({
|
||||||
gas: gas || 1000000,
|
gas: (gas * GAS_BOOST) | 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async mint({
|
async mintNFT({
|
||||||
|
chain,
|
||||||
address,
|
address,
|
||||||
to,
|
to,
|
||||||
tokenId,
|
tokenIds,
|
||||||
encodeABI = false,
|
encodeABI = false,
|
||||||
}: {
|
}: {
|
||||||
|
chain: number
|
||||||
address?: string
|
address?: string
|
||||||
to: string
|
to: string
|
||||||
tokenId: string
|
tokenIds: string[]
|
||||||
encodeABI?: boolean
|
encodeABI?: boolean
|
||||||
}) {
|
}) {
|
||||||
const contract = address
|
const { instance, account } = new ChainCache().getInstances(chain, address, abiNFT)
|
||||||
? new this.web3.eth.Contract(abiNft, address, { from: this.account.address })
|
|
||||||
: this.contract
|
|
||||||
if (encodeABI) {
|
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 })
|
let gas = await instance.methods.batchMint(to, tokenIds).estimateGas({ from: account.address })
|
||||||
return contract.methods.mint(to, tokenId).send({ gas: (gas * 1.5) | 0 })
|
return instance.methods.batchMint(to, tokenIds).send({ gas: (gas * GAS_BOOST) | 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
async batchMint({
|
async mintBadge({
|
||||||
account,
|
chain,
|
||||||
address,
|
address,
|
||||||
to,
|
to,
|
||||||
count,
|
count,
|
||||||
encodeABI = false,
|
encodeABI = false,
|
||||||
}: {
|
}: {
|
||||||
account?: string
|
chain: number
|
||||||
address?: string
|
address?: string
|
||||||
to: string
|
to: string
|
||||||
count: number
|
count: number
|
||||||
encodeABI?: boolean
|
encodeABI?: boolean
|
||||||
}) {
|
}) {
|
||||||
const contract = address
|
const { instance, account } = new ChainCache().getInstances(chain, address, abiBadge)
|
||||||
? new this.web3.eth.Contract(abiNft, address, { from: account || this.account.address })
|
|
||||||
: this.contract
|
|
||||||
const countStr = count + ''
|
const countStr = count + ''
|
||||||
if (encodeABI) {
|
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 })
|
let gas = await instance.methods.batchMint(to, countStr).estimateGas({ from: account.address })
|
||||||
return contract.methods.batchMint(to, countStr).send({ gas: (gas * 1.5) | 0 })
|
return instance.methods.batchMint(to, countStr).send({ gas: (gas * GAS_BOOST) | 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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import Web3 from 'web3'
|
|||||||
import { Account } from 'web3-core'
|
import { Account } from 'web3-core'
|
||||||
import { ZERO_BYTES32 } from 'common/Constants'
|
import { ZERO_BYTES32 } from 'common/Constants'
|
||||||
import { generateRandomBytes32 } from 'utils/wallet.util'
|
import { generateRandomBytes32 } from 'utils/wallet.util'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
const abi = require('abis/BEMultiSigWallet.json').abi
|
const abi = require('abis/BEMultiSigWallet.json').abi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,13 +132,17 @@ export class WalletReactor {
|
|||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateRequired(num: number) {
|
async updateRequired(chain: number, num: number) {
|
||||||
let contractAddress = [process.env.CHAIN_WALLET_ADDRESS]
|
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 values = ['0']
|
||||||
let abi = await this.contract.methods.changeRequirement(num + '').encodeABI()
|
let abi = await this.contract.methods.changeRequirement(num + '').encodeABI()
|
||||||
let salt = generateRandomBytes32()
|
let salt = generateRandomBytes32()
|
||||||
let operation: any = this.genOperation({
|
let operation: any = this.genOperation({
|
||||||
targets: contractAddress,
|
targets: [contractAddress],
|
||||||
values,
|
values,
|
||||||
datas: [abi],
|
datas: [abi],
|
||||||
predecessor: ZERO_BYTES32,
|
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 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 = `
|
export const CONFIRM_MAIL_HTML = `
|
||||||
<p>{{title}}</p>
|
<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 { ChainTask } from 'models/ChainTask'
|
||||||
import { isObjectId } from 'utils/string.util'
|
import { isObjectId } from 'utils/string.util'
|
||||||
import { WechatWorkService } from 'service/wechatwork.service'
|
import { WechatWorkService } from 'service/wechatwork.service'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
|
|
||||||
class WorkFlowController extends BaseController {
|
class WorkFlowController extends BaseController {
|
||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@ -75,7 +76,8 @@ class WorkFlowController extends BaseController {
|
|||||||
let tasks = requestTasks.map(o => {
|
let tasks = requestTasks.map(o => {
|
||||||
return { scheduleId: o.scheduleId, reqDatas: o.reqDatas }
|
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)
|
let types = Object.fromEntries(TaskTypeMap)
|
||||||
return {
|
return {
|
||||||
chainTask: taskObj,
|
chainTask: taskObj,
|
||||||
@ -88,13 +90,16 @@ class WorkFlowController extends BaseController {
|
|||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@router('get /workflow/update_required')
|
@router('get /workflow/update_required')
|
||||||
async updateRequired(req, res) {
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@router('post /workflow/update_required')
|
@router('post /workflow/update_required')
|
||||||
async execUpdateRequired(req, res) {
|
async execUpdateRequired(req, res) {
|
||||||
|
let { chain } = req.params
|
||||||
let data = {
|
let data = {
|
||||||
scheduleId: '0xa5c35368cd44dbe805a4595d6813ed3afefa1bf667209dc8d63f99cdec117f58',
|
scheduleId: '0xa5c35368cd44dbe805a4595d6813ed3afefa1bf667209dc8d63f99cdec117f58',
|
||||||
targets: ['0xc195196351566d2c4e13563C4492fB0BdB7894Fb'],
|
targets: ['0xc195196351566d2c4e13563C4492fB0BdB7894Fb'],
|
||||||
@ -103,7 +108,7 @@ class WorkFlowController extends BaseController {
|
|||||||
predecessor: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
predecessor: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
salt: '0x39383830353131363736333036',
|
salt: '0x39383830353131363736333036',
|
||||||
}
|
}
|
||||||
let result = await new BlockChain().walletReactor.executeSchedule(data)
|
let result = await new ChainCache().getWallet(chain).executeSchedule(data)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ export class ChainTaskClass extends BaseModule {
|
|||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public taskId!: string
|
public taskId!: string
|
||||||
@prop()
|
@prop()
|
||||||
|
public chain: number
|
||||||
|
@prop()
|
||||||
public name: string
|
public name: string
|
||||||
@prop()
|
@prop()
|
||||||
public desc: string
|
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, chain }) {
|
||||||
public static async parseWxApprovalInfo({ taskId, name, desc, data, starter, starterName }) {
|
|
||||||
let maxTryCount = parseInt(process.env.CHAIN_MAX_TRY)
|
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 = []
|
let subTasks: any = []
|
||||||
if (chainTask.newRecord) {
|
if (chainTask.newRecord) {
|
||||||
let subTask
|
let subTask
|
||||||
@ -156,6 +157,7 @@ export class ChainTaskClass extends BaseModule {
|
|||||||
subTask = new RequestTask({
|
subTask = new RequestTask({
|
||||||
taskId,
|
taskId,
|
||||||
index,
|
index,
|
||||||
|
chain,
|
||||||
chainTaskId: chainTask.id,
|
chainTaskId: chainTask.id,
|
||||||
reqDatas: [],
|
reqDatas: [],
|
||||||
maxTryCount,
|
maxTryCount,
|
||||||
|
@ -3,6 +3,7 @@ import { BlockChain } from 'chain/BlockChain'
|
|||||||
import { dbconn } from 'decorators/dbconn'
|
import { dbconn } from 'decorators/dbconn'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
|
|
||||||
export enum TaskType {
|
export enum TaskType {
|
||||||
UNKNOW = 0,
|
UNKNOW = 0,
|
||||||
@ -11,6 +12,8 @@ export enum TaskType {
|
|||||||
TRANSFER_FT = 3,
|
TRANSFER_FT = 3,
|
||||||
TRANSFER_NFT = 4,
|
TRANSFER_NFT = 4,
|
||||||
PUBLISH_AIRDROP_LIST = 5,
|
PUBLISH_AIRDROP_LIST = 5,
|
||||||
|
AIRDROP_NFT_ACTIVITY = 6,
|
||||||
|
AIRDROP_NFT_INGAME = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TaskTypeMap = new Map([
|
export const TaskTypeMap = new Map([
|
||||||
@ -20,6 +23,8 @@ export const TaskTypeMap = new Map([
|
|||||||
[TaskType.TRANSFER_FT, 'Ft转账'],
|
[TaskType.TRANSFER_FT, 'Ft转账'],
|
||||||
[TaskType.TRANSFER_NFT, 'NFT转账'],
|
[TaskType.TRANSFER_NFT, 'NFT转账'],
|
||||||
[TaskType.PUBLISH_AIRDROP_LIST, '公布空投名单'],
|
[TaskType.PUBLISH_AIRDROP_LIST, '公布空投名单'],
|
||||||
|
[TaskType.AIRDROP_NFT_ACTIVITY, '空投活动NFT'],
|
||||||
|
[TaskType.AIRDROP_NFT_INGAME, '空投游戏中的NFT'],
|
||||||
])
|
])
|
||||||
|
|
||||||
export enum ReqTaskStatus {
|
export enum ReqTaskStatus {
|
||||||
@ -47,6 +52,9 @@ export class RequestTaskClass extends BaseModule {
|
|||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public chainTaskId!: string
|
public chainTaskId!: string
|
||||||
|
|
||||||
|
@prop()
|
||||||
|
public chain: number
|
||||||
|
|
||||||
@prop({ default: 0 })
|
@prop({ default: 0 })
|
||||||
public index: number
|
public index: number
|
||||||
|
|
||||||
@ -120,7 +128,7 @@ export class RequestTaskClass extends BaseModule {
|
|||||||
let self = this
|
let self = this
|
||||||
self.blockReq = new BlockChain().currentBlockNum
|
self.blockReq = new BlockChain().currentBlockNum
|
||||||
await self.save()
|
await self.save()
|
||||||
let result = await new BlockChain().walletReactor.beginSchedule(self, 60)
|
let result = await new ChainCache().getWallet(self.chain).beginSchedule(self, 60)
|
||||||
logger.info(result)
|
logger.info(result)
|
||||||
let { transactionHash } = result
|
let { transactionHash } = result
|
||||||
self.txHash = transactionHash
|
self.txHash = transactionHash
|
||||||
@ -130,7 +138,7 @@ export class RequestTaskClass extends BaseModule {
|
|||||||
|
|
||||||
public async execSchdule(this: DocumentType<RequestTaskClass>) {
|
public async execSchdule(this: DocumentType<RequestTaskClass>) {
|
||||||
let self = this
|
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('schedule exec result:')
|
||||||
logger.info(result)
|
logger.info(result)
|
||||||
let { transactionHash } = result
|
let { transactionHash } = result
|
||||||
|
@ -5,7 +5,7 @@ import { TaskSvr } from 'service/task.service'
|
|||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
|
|
||||||
@dbconn()
|
@dbconn()
|
||||||
@index({ transactionHash: 1 }, { unique: true })
|
@index({ chain: 1, transactionHash: 1 }, { unique: true })
|
||||||
@modelOptions({
|
@modelOptions({
|
||||||
schemaOptions: { collection: 'schedule_confirm_event', timestamps: true },
|
schemaOptions: { collection: 'schedule_confirm_event', timestamps: true },
|
||||||
})
|
})
|
||||||
@ -13,6 +13,8 @@ export class ScheduleConfirmEventClass extends BaseModule {
|
|||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public address!: string
|
public address!: string
|
||||||
@prop()
|
@prop()
|
||||||
|
public chain: number
|
||||||
|
@prop()
|
||||||
public event: string
|
public event: string
|
||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public transactionHash: string
|
public transactionHash: string
|
||||||
@ -48,7 +50,10 @@ export class ScheduleConfirmEventClass extends BaseModule {
|
|||||||
$inc: { version: 1 },
|
$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) {
|
if (record.version === 1) {
|
||||||
logger.log('receive events: ' + JSON.stringify(record.scheduleIds))
|
logger.log('receive events: ' + JSON.stringify(record.scheduleIds))
|
||||||
for (let id of record.scheduleIds) {
|
for (let id of record.scheduleIds) {
|
||||||
|
@ -3,7 +3,7 @@ import { dbconn } from 'decorators/dbconn'
|
|||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
|
|
||||||
@dbconn()
|
@dbconn()
|
||||||
@index({ transactionHash: 1, scheduleId: 1 }, { unique: true })
|
@index({ chain: 1, transactionHash: 1, scheduleId: 1 }, { unique: true })
|
||||||
@modelOptions({
|
@modelOptions({
|
||||||
schemaOptions: { collection: 'schedule_executed_event', timestamps: true },
|
schemaOptions: { collection: 'schedule_executed_event', timestamps: true },
|
||||||
})
|
})
|
||||||
@ -11,6 +11,8 @@ export class ScheduleExecutedEventClass extends BaseModule {
|
|||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public address!: string
|
public address!: string
|
||||||
@prop()
|
@prop()
|
||||||
|
public chain: number
|
||||||
|
@prop()
|
||||||
public event: string
|
public event: string
|
||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public transactionHash: string
|
public transactionHash: string
|
||||||
@ -45,7 +47,7 @@ export class ScheduleExecutedEventClass extends BaseModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let record = await ScheduleExecutedEvent.insertOrUpdate(
|
let record = await ScheduleExecutedEvent.insertOrUpdate(
|
||||||
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id },
|
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id, chain: event.chain },
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
if (record.version === 1) {
|
if (record.version === 1) {
|
||||||
|
@ -3,7 +3,7 @@ import { dbconn } from 'decorators/dbconn'
|
|||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
|
|
||||||
@dbconn()
|
@dbconn()
|
||||||
@index({ transactionHash: 1, scheduleId: 1 }, { unique: true })
|
@index({ chain: 1, transactionHash: 1, scheduleId: 1 }, { unique: true })
|
||||||
@modelOptions({
|
@modelOptions({
|
||||||
schemaOptions: { collection: 'schedule_added_event', timestamps: true },
|
schemaOptions: { collection: 'schedule_added_event', timestamps: true },
|
||||||
})
|
})
|
||||||
@ -11,6 +11,8 @@ export class ScheduledAddedEventClass extends BaseModule {
|
|||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public address!: string
|
public address!: string
|
||||||
@prop()
|
@prop()
|
||||||
|
public chain: number
|
||||||
|
@prop()
|
||||||
public event: string
|
public event: string
|
||||||
@prop({ required: true })
|
@prop({ required: true })
|
||||||
public transactionHash: string
|
public transactionHash: string
|
||||||
@ -45,7 +47,7 @@ export class ScheduledAddedEventClass extends BaseModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ScheduledAddedEvent.insertOrUpdate(
|
return ScheduledAddedEvent.insertOrUpdate(
|
||||||
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id },
|
{ transactionHash: event.transactionHash, scheduleId: event.returnValues.id, chain: event.chain },
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,13 @@ let eventProcessers = {
|
|||||||
ScheduleExecutedEvent: ScheduleExecutedEvent,
|
ScheduleExecutedEvent: ScheduleExecutedEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = require('config/events.json')
|
const events = require('../config/events.json')
|
||||||
|
|
||||||
async function initEventSvrs() {
|
async function initEventSvrs() {
|
||||||
// let nfts = [{ address: '0x37c30a2945799a53c5358636a721b442458fa691' }]
|
// let nfts = [{ address: '0x37c30a2945799a53c5358636a721b442458fa691' }]
|
||||||
for (let event of events) {
|
for (let event of events) {
|
||||||
let eventSvr = new EventSyncSvr({
|
let eventSvr = new EventSyncSvr({
|
||||||
|
chain: event.chain,
|
||||||
address: event.address,
|
address: event.address,
|
||||||
event: event.event,
|
event: event.event,
|
||||||
abi: require('abis/' + event.abi + '.json').abi,
|
abi: require('abis/' + event.abi + '.json').abi,
|
||||||
|
@ -5,12 +5,13 @@ import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
|||||||
import { BlockChain } from 'chain/BlockChain'
|
import { BlockChain } from 'chain/BlockChain'
|
||||||
import { ChainTask } from 'models/ChainTask'
|
import { ChainTask } from 'models/ChainTask'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
|
|
||||||
const EXCLUDE_STATUS = [
|
const EXCLUDE_STATUS = [
|
||||||
ReqTaskStatus.SUCCESS,
|
5, //ReqTaskStatus.SUCCESS,
|
||||||
ReqTaskStatus.WAIT_EXEC,
|
3, //ReqTaskStatus.WAIT_EXEC,
|
||||||
ReqTaskStatus.WAIT_EXEC_CONFIRM,
|
4, //ReqTaskStatus.WAIT_EXEC_CONFIRM,
|
||||||
ReqTaskStatus.EXEC_REVERT,
|
9, //ReqTaskStatus.EXEC_REVERT,
|
||||||
]
|
]
|
||||||
@singleton
|
@singleton
|
||||||
export class ChainQueue {
|
export class ChainQueue {
|
||||||
@ -37,7 +38,7 @@ export class ChainQueue {
|
|||||||
await subTask.save()
|
await subTask.save()
|
||||||
}
|
}
|
||||||
if (subTask.status === ReqTaskStatus.WAIT_CONFIRM) {
|
if (subTask.status === ReqTaskStatus.WAIT_CONFIRM) {
|
||||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ export class ChainQueue {
|
|||||||
this.addTaskToQueue(subTask)
|
this.addTaskToQueue(subTask)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
subTask.errMsg.push(err)
|
subTask.errMsg.push(err)
|
||||||
await subTask.save()
|
await subTask.save()
|
||||||
|
@ -5,13 +5,14 @@ import logger from 'logger/logger'
|
|||||||
import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask'
|
||||||
import { DocumentType } from '@typegoose/typegoose'
|
import { DocumentType } from '@typegoose/typegoose'
|
||||||
import { ChainTask } from 'models/ChainTask'
|
import { ChainTask } from 'models/ChainTask'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
|
|
||||||
const EXCLUDE_STATUS = [
|
const EXCLUDE_STATUS = [
|
||||||
ReqTaskStatus.NOTSTART,
|
0, //ReqTaskStatus.NOTSTART,
|
||||||
ReqTaskStatus.SUCCESS,
|
5, //ReqTaskStatus.SUCCESS,
|
||||||
ReqTaskStatus.PEDING,
|
1, //ReqTaskStatus.PEDING,
|
||||||
ReqTaskStatus.WAIT_CONFIRM,
|
2, //ReqTaskStatus.WAIT_CONFIRM,
|
||||||
ReqTaskStatus.SCHEDULE_REVERT,
|
8, //ReqTaskStatus.SCHEDULE_REVERT,
|
||||||
]
|
]
|
||||||
@singleton
|
@singleton
|
||||||
export class ExecQueue {
|
export class ExecQueue {
|
||||||
@ -34,7 +35,7 @@ export class ExecQueue {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (subTask.status === ReqTaskStatus.WAIT_EXEC_CONFIRM) {
|
if (subTask.status === ReqTaskStatus.WAIT_EXEC_CONFIRM) {
|
||||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.queue.push(async () => {
|
this.queue.push(async () => {
|
||||||
@ -50,7 +51,7 @@ export class ExecQueue {
|
|||||||
this.addTaskToQueue(subTask)
|
this.addTaskToQueue(subTask)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.blockChain.confirmQueue.addTaskToQueue(subTask)
|
new ChainCache().getConfirmQueue(subTask.chain).addTaskToQueue(subTask)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('error add task: ' + err)
|
logger.error('error add task: ' + err)
|
||||||
subTask.errMsg.push(err)
|
subTask.errMsg.push(err)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
import { BlockChain } from 'chain/BlockChain'
|
import { BlockChain } from 'chain/BlockChain'
|
||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'decorators/singleton'
|
||||||
import * as schedule from 'node-schedule'
|
import * as schedule from 'node-schedule'
|
||||||
@ -5,7 +6,9 @@ import * as schedule from 'node-schedule'
|
|||||||
@singleton
|
@singleton
|
||||||
export default class BlocknumSchedule {
|
export default class BlocknumSchedule {
|
||||||
parseAllRecord() {
|
parseAllRecord() {
|
||||||
new BlockChain().updateCurrenBlockNum()
|
for (let cfg of new ChainCache().chainArray) {
|
||||||
|
new BlockChain().updateCurrenBlockNum(cfg.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
scheduleAll() {
|
scheduleAll() {
|
||||||
const job = schedule.scheduleJob('*/5 * * * * *', async () => {
|
const job = schedule.scheduleJob('*/5 * * * * *', async () => {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
import { HttpRetryProvider } from 'chain/HttpRetryProvider'
|
import { HttpRetryProvider } from 'chain/HttpRetryProvider'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { NftTransferEvent } from 'models/NftTransferEvent'
|
import { NftTransferEvent } from 'models/NftTransferEvent'
|
||||||
@ -8,6 +9,7 @@ import Web3 from 'web3'
|
|||||||
|
|
||||||
export class EventSyncSvr {
|
export class EventSyncSvr {
|
||||||
web3: Web3
|
web3: Web3
|
||||||
|
chain: number
|
||||||
provider: HttpRetryProvider
|
provider: HttpRetryProvider
|
||||||
fromBlock: number = 27599018
|
fromBlock: number = 27599018
|
||||||
toBlock: number = 10000
|
toBlock: number = 10000
|
||||||
@ -18,26 +20,27 @@ export class EventSyncSvr {
|
|||||||
eventProcesser: any
|
eventProcesser: any
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
chain,
|
||||||
address,
|
address,
|
||||||
event,
|
event,
|
||||||
abi,
|
abi,
|
||||||
fromBlock,
|
fromBlock,
|
||||||
eventProcesser,
|
eventProcesser,
|
||||||
}: {
|
}: {
|
||||||
|
chain: number
|
||||||
address: string
|
address: string
|
||||||
event: string
|
event: string
|
||||||
abi: any
|
abi: any
|
||||||
fromBlock: number
|
fromBlock: number
|
||||||
eventProcesser: any
|
eventProcesser: any
|
||||||
}) {
|
}) {
|
||||||
this.provider = new HttpRetryProvider(process.env.CHAIN_RPC_URL.split('|'))
|
this.chain = chain
|
||||||
// @ts-ignore
|
this.web3 = new ChainCache().getWeb3(chain)
|
||||||
this.web3 = new Web3(this.provider)
|
|
||||||
this.contract = new this.web3.eth.Contract(abi, address)
|
this.contract = new this.web3.eth.Contract(abi, address)
|
||||||
this.address = this.contract.options.address
|
this.address = this.contract.options.address
|
||||||
this.event = event
|
this.event = event
|
||||||
this.fromBlock = fromBlock
|
this.fromBlock = fromBlock
|
||||||
this.blockKey = `${address.toLowerCase()}_${event}`
|
this.blockKey = `${chain}_${address.toLowerCase()}_${event}`
|
||||||
this.eventProcesser = eventProcesser
|
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}`)
|
logger.log(`query events:: ${this.event} address: ${this.address} from: ${this.fromBlock} to: ${this.toBlock}`)
|
||||||
let events = getPastEventsIter({
|
let events = getPastEventsIter({
|
||||||
|
chain: this.chain,
|
||||||
contract: this.contract,
|
contract: this.contract,
|
||||||
event: this.event,
|
event: this.event,
|
||||||
fromBlock: this.fromBlock,
|
fromBlock: this.fromBlock,
|
||||||
toBlock: this.toBlock,
|
toBlock: this.toBlock,
|
||||||
})
|
})
|
||||||
// this.fromBlock = 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缓存
|
// 处理完一种nft后, 清楚block的timestamp缓存
|
||||||
clearTimeCache()
|
clearTimeCache()
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { ReqTaskStatus, RequestTask } from 'models/RequestTask'
|
|||||||
import { ChainQueue } from 'queue/chain.queue'
|
import { ChainQueue } from 'queue/chain.queue'
|
||||||
import { ExecQueue } from 'queue/exec.queue'
|
import { ExecQueue } from 'queue/exec.queue'
|
||||||
import { WechatWorkService } from './wechatwork.service'
|
import { WechatWorkService } from './wechatwork.service'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
|
|
||||||
@singleton
|
@singleton
|
||||||
export class TaskSvr {
|
export class TaskSvr {
|
||||||
@ -14,7 +15,7 @@ export class TaskSvr {
|
|||||||
let data = await new WechatWorkService().parseOneTask(spNo)
|
let data = await new WechatWorkService().parseOneTask(spNo)
|
||||||
let subTasks = await ChainTask.parseWxApprovalInfo(data)
|
let subTasks = await ChainTask.parseWxApprovalInfo(data)
|
||||||
for (let subTask of subTasks) {
|
for (let subTask of subTasks) {
|
||||||
let { scheduleId } = new BlockChain().walletReactor.genOperation(subTask)
|
let { scheduleId } = new ChainCache().getWallet(data.chain).genOperation(subTask)
|
||||||
subTask.scheduleId = scheduleId
|
subTask.scheduleId = scheduleId
|
||||||
await subTask.save()
|
await subTask.save()
|
||||||
new ChainQueue().addTaskToQueue(subTask)
|
new ChainQueue().addTaskToQueue(subTask)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import axios, { AxiosRequestConfig } from 'axios'
|
import axios, { AxiosRequestConfig } from 'axios'
|
||||||
|
import { ChainCache } from 'cache/ChainCache'
|
||||||
import { singleton } from 'decorators/singleton'
|
import { singleton } from 'decorators/singleton'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
@ -240,8 +241,23 @@ export class WechatWorkService {
|
|||||||
let userInfo = await this.fetchUserInfo(starter)
|
let userInfo = await this.fetchUserInfo(starter)
|
||||||
let starterName = userInfo.name
|
let starterName = userInfo.name
|
||||||
let { filename } = await this.fetchFile(fileId)
|
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)
|
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) {
|
private async queryMedidaIdFromSprecord(datas: any) {
|
||||||
|
@ -170,12 +170,14 @@ export async function getPastEvents({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function* getPastEventsIter({
|
export function* getPastEventsIter({
|
||||||
|
chain,
|
||||||
contract,
|
contract,
|
||||||
event,
|
event,
|
||||||
fromBlock,
|
fromBlock,
|
||||||
toBlock,
|
toBlock,
|
||||||
options,
|
options,
|
||||||
}: {
|
}: {
|
||||||
|
chain: number
|
||||||
contract: any
|
contract: any
|
||||||
event: string
|
event: string
|
||||||
fromBlock: number
|
fromBlock: number
|
||||||
@ -183,7 +185,7 @@ export function* getPastEventsIter({
|
|||||||
options?: any
|
options?: any
|
||||||
}) {
|
}) {
|
||||||
const address = contract.options.address
|
const address = contract.options.address
|
||||||
const redisKey = `${address.toLowerCase()}_${event}`
|
const redisKey = `${chain}_${address.toLowerCase()}_${event}`
|
||||||
logger.debug(`*getPastEventsIter: ${event} from: ${fromBlock} to: ${toBlock}`)
|
logger.debug(`*getPastEventsIter: ${event} from: ${fromBlock} to: ${toBlock}`)
|
||||||
let from = toBN(fromBlock)
|
let from = toBN(fromBlock)
|
||||||
let to = toBN(fromBlock).add(queryRange)
|
let to = toBN(fromBlock).add(queryRange)
|
||||||
@ -198,7 +200,7 @@ export function* getPastEventsIter({
|
|||||||
yield new RedisClient().set(redisKey, toBlockBN.add(ONE) + '')
|
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) {
|
for (const getPastEventPromise of iterator) {
|
||||||
const events = await getPastEventPromise
|
const events = await getPastEventPromise
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
@ -211,6 +213,7 @@ export async function processEvents(web3, iterator, processedEvent) {
|
|||||||
blockTimeMap.set(event.blockNumber, blockData.timestamp)
|
blockTimeMap.set(event.blockNumber, blockData.timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
event.chain = chain
|
||||||
await processedEvent(event)
|
await processedEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,6 @@ import { IDCounter } from 'models/IDCounter'
|
|||||||
export const ONE_DAY = 24 * 60 * 60 * 1000
|
export const ONE_DAY = 24 * 60 * 60 * 1000
|
||||||
export const NFT_BEGIN_DAY = new Date(2023, 4, 8)
|
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
|
// calc days between two Date
|
||||||
export function daysBetween(date1: Date, date2: Date) {
|
export function daysBetween(date1: Date, date2: Date) {
|
||||||
// hours*minutes*seconds*milliseconds
|
// hours*minutes*seconds*milliseconds
|
||||||
|
Loading…
x
Reference in New Issue
Block a user