增加一个获取转账价格的接口
This commit is contained in:
parent
06313311c3
commit
e522aaa693
@ -10,6 +10,7 @@ import logger from 'logger/logger'
|
||||
import BlocknumSchedule from 'schedule/blocknum.schedule'
|
||||
import { RedisClient } from 'redis/RedisClient'
|
||||
import { restartAllUnFinishedTask } from 'service/chain.service'
|
||||
import { PriceSvr } from 'service/price.service'
|
||||
|
||||
const zReqParserPlugin = require('plugins/zReqParser')
|
||||
|
||||
@ -105,6 +106,7 @@ export class ApiServer {
|
||||
}
|
||||
private initSchedules() {
|
||||
new BlocknumSchedule().scheduleAll()
|
||||
new PriceSvr().scheduleAll()
|
||||
}
|
||||
private restoreChainQueue() {}
|
||||
private setErrHandler() {
|
||||
|
@ -8,13 +8,19 @@ import { ERC721Reactor } from './ERC721Reactor'
|
||||
import { HttpRetryProvider } from './HttpRetryProvider'
|
||||
import { WalletReactor } from './WalletReactor'
|
||||
import { DistributorReactor } from './DistributorReactor'
|
||||
import { fromTokenMinimalUnit } from 'utils/number.util'
|
||||
import { fromTokenMinimalUnit, safeNumberToBN, toBN } from 'utils/number.util'
|
||||
import { AllChains } from './allchain'
|
||||
import assert from 'assert'
|
||||
import { IPriceData } from 'structs/PriceData'
|
||||
import { IChainData } from 'structs/ChainData'
|
||||
import { queryEthPrice } from 'service/chain.service'
|
||||
|
||||
@singleton
|
||||
export class BlockChain {
|
||||
private web3: Web3
|
||||
instanceCacheMap: Map<string, any>
|
||||
private accountMaster: AddedAccount
|
||||
private currentChain: IChainData
|
||||
public erc20Reactor: ERC20Reactor
|
||||
public erc721Reactor: ERC721Reactor
|
||||
public walletReactor: WalletReactor
|
||||
@ -23,8 +29,12 @@ export class BlockChain {
|
||||
public currentBlockNum: number = 0
|
||||
|
||||
constructor() {
|
||||
const provider = new HttpRetryProvider(process.env.CHAIN_RPC_URL.split('|'))
|
||||
const defaultChain = parseInt(process.env.CHAIN_DEFAULT)
|
||||
this.currentChain = AllChains.find(o => o.id === defaultChain)
|
||||
assert(this.currentChain, `chain data with ${defaultChain} not found`)
|
||||
const provider = new HttpRetryProvider(this.currentChain.rpc.split('|'))
|
||||
this.web3 = new Web3(provider)
|
||||
this.web3.eth.handleRevert = true
|
||||
this.confirmQueue = new ConfirmQueue(this.web3)
|
||||
let key = process.env.CHAIN_MASTER_KEY
|
||||
this.accountMaster = this.web3.eth.accounts.wallet.add(key)
|
||||
@ -47,6 +57,10 @@ export class BlockChain {
|
||||
})
|
||||
}
|
||||
|
||||
public get currentAccount() {
|
||||
return this.accountMaster.address
|
||||
}
|
||||
|
||||
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 })
|
||||
@ -97,10 +111,15 @@ export class BlockChain {
|
||||
return abi
|
||||
}
|
||||
|
||||
public async generateGasShow(gas: any) {
|
||||
public async generateGasShow(gas: any): Promise<IPriceData> {
|
||||
let price = await this.web3.eth.getGasPrice()
|
||||
let ehtBN = this.web3.utils.toBN(price).mul(this.web3.utils.toBN(gas))
|
||||
let ehtBN = safeNumberToBN(price).mul(safeNumberToBN(gas))
|
||||
let leagelSymbol = 'USD'
|
||||
let ethSymbol = this.currentChain.type !== 'Testnet' ? this.currentChain.symbol : 'ETH'
|
||||
let leagelPriceData = await queryEthPrice(ethSymbol, leagelSymbol)
|
||||
let leagelPriceBN = safeNumberToBN(leagelPriceData[leagelSymbol] * 100)
|
||||
let leagel = fromTokenMinimalUnit(ehtBN.mul(leagelPriceBN), 20)
|
||||
let eth = fromTokenMinimalUnit(ehtBN, 18)
|
||||
return { gas, price, eth }
|
||||
return { gas, price, eth, leagel }
|
||||
}
|
||||
}
|
||||
|
@ -116,31 +116,31 @@ export class ERC20Reactor {
|
||||
account,
|
||||
gas,
|
||||
encodeABI = false,
|
||||
estimate = false,
|
||||
}: {
|
||||
address: string
|
||||
from: string
|
||||
from?: string
|
||||
to: string
|
||||
account?: string
|
||||
amount: number | string
|
||||
gas?: number
|
||||
encodeABI: boolean
|
||||
encodeABI?: boolean
|
||||
estimate?: boolean
|
||||
}) {
|
||||
from = from || account || this.account.address
|
||||
const contract = new this.web3.eth.Contract(abiFt, address, { from: account || this.account.address })
|
||||
const amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ''))
|
||||
if (encodeABI) {
|
||||
return contract.methods.transfer(to, amountBN).encodeABI()
|
||||
return contract.methods.transferFrom(from, to, amountBN).encodeABI()
|
||||
}
|
||||
if (estimate) {
|
||||
return contract.methods.transferFrom(from, to, 0).estimateGas()
|
||||
}
|
||||
return contract.methods.transferFrom(from, to, amountBN).send({
|
||||
gas: gas || 1000000,
|
||||
})
|
||||
}
|
||||
|
||||
async calcTransferGas({ address }: { address: string }) {
|
||||
const contract = new this.web3.eth.Contract(abiFt, address, { from: this.account.address })
|
||||
let gas = await contract.methods.transferFrom(this.account.address, this.account.address, 0).estimateGas()
|
||||
return gas
|
||||
}
|
||||
|
||||
async mint({
|
||||
address,
|
||||
to,
|
||||
|
291
src/chain/allchain.ts
Normal file
291
src/chain/allchain.ts
Normal file
@ -0,0 +1,291 @@
|
||||
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://rpc.ankr.com/arbitrum',
|
||||
id: 42161,
|
||||
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,
|
||||
symbol: 'AGOR',
|
||||
symbol2: 'ETH',
|
||||
explorerurl: 'https://goerli-rollup-explorer.arbitrum.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',
|
||||
},
|
||||
]
|
@ -1,15 +1,13 @@
|
||||
import BaseController from 'common/base.controller'
|
||||
import { role, router } from 'decorators/router'
|
||||
import { BlockChain } from 'chain/BlockChain'
|
||||
import { ZError } from 'common/ZError'
|
||||
import { PriceSvr } from 'service/price.service'
|
||||
|
||||
class TokenController extends BaseController {
|
||||
@role('anon')
|
||||
@router('post /chain/estimate_transfer_gas')
|
||||
async calcGasPrice(req, res) {
|
||||
const { address } = req.params
|
||||
let gas = await new BlockChain().erc20Reactor.calcTransferGas({ address })
|
||||
let data = await new BlockChain().generateGasShow(gas)
|
||||
let data = await new PriceSvr().getTokenTransferPrice(address)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,20 @@
|
||||
import { BlockChain } from 'chain/BlockChain'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import logger from 'logger/logger'
|
||||
import * as schedule from 'node-schedule'
|
||||
|
||||
@singleton
|
||||
export default class BlocknumSchedule {
|
||||
parseAllRecord() {
|
||||
new BlockChain().updateCurrenBlockNum()
|
||||
async parseAllRecord() {
|
||||
try {
|
||||
await new BlockChain().updateCurrenBlockNum()
|
||||
} catch (err) {
|
||||
logger.info('updateCurrenBlockNum error', err.message || err)
|
||||
}
|
||||
}
|
||||
scheduleAll() {
|
||||
const job = schedule.scheduleJob('*/5 * * * * *', async () => {
|
||||
await this.parseAllRecord()
|
||||
this.parseAllRecord()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import axios from 'axios'
|
||||
import logger from 'logger/logger'
|
||||
import { ChainTask } from 'models/ChainTask'
|
||||
import { RequestTask } from 'models/RequestTask'
|
||||
@ -15,3 +16,8 @@ export async function restartAllUnFinishedTask() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function queryEthPrice(eth: string, usd: string = 'USD') {
|
||||
const url = `https://min-api.cryptocompare.com/data/price?fsym=${eth}&tsyms=${usd}`
|
||||
return axios.get(url).then(res => res.data)
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import assert from 'assert'
|
||||
import { AllChains } from 'chain/allchain'
|
||||
import { HttpRetryProvider } from 'chain/HttpRetryProvider'
|
||||
import logger from 'logger/logger'
|
||||
import { NftTransferEvent } from 'models/NftTransferEvent'
|
||||
@ -30,7 +32,10 @@ export class EventSyncSvr {
|
||||
fromBlock: number
|
||||
eventProcesser: any
|
||||
}) {
|
||||
this.provider = new HttpRetryProvider(process.env.CHAIN_RPC_URL.split('|'))
|
||||
const defaultChain = parseInt(process.env.CHAIN_DEFAULT)
|
||||
const chainData = AllChains.find(o => o.id === defaultChain)
|
||||
assert(chainData, `chain data with ${defaultChain} not found`)
|
||||
this.provider = new HttpRetryProvider(chainData.rpc.split('|'))
|
||||
// @ts-ignore
|
||||
this.web3 = new Web3(this.provider)
|
||||
this.contract = new this.web3.eth.Contract(abi, address)
|
||||
|
51
src/service/price.service.ts
Normal file
51
src/service/price.service.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { BlockChain } from 'chain/BlockChain'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import { IPriceData } from 'structs/PriceData'
|
||||
import * as schedule from 'node-schedule'
|
||||
import logger from 'logger/logger'
|
||||
|
||||
@singleton
|
||||
export class PriceSvr {
|
||||
private priceMap: Map<string, IPriceData> = new Map()
|
||||
|
||||
public async getTokenTransferPrice(token: string, refresh: boolean = false) {
|
||||
logger.debug(`get price for ${token}, refresh: ${refresh}`)
|
||||
const key = `token_transfer_price|${token}`
|
||||
let data = this.priceMap.get(key)
|
||||
if (!data || data.expired < Date.now() || refresh) {
|
||||
logger.debug(`need update price for ${token}`)
|
||||
let account = new BlockChain().currentAccount
|
||||
try {
|
||||
let gas = await new BlockChain().erc20Reactor.transfer({
|
||||
address: token,
|
||||
to: account,
|
||||
amount: 0,
|
||||
estimate: true,
|
||||
})
|
||||
let data = await new BlockChain().generateGasShow(gas)
|
||||
data = { ...data, ...{ expired: Date.now() + 1000 * 60 } }
|
||||
this.priceMap.set(key, data)
|
||||
} catch (e) {
|
||||
logger.log(e)
|
||||
}
|
||||
}
|
||||
return this.priceMap.get(key)
|
||||
}
|
||||
private async refreshAll() {
|
||||
for (let key of this.priceMap.keys()) {
|
||||
let [type, token] = key.split('|')
|
||||
if (type == 'token_transfer_price') {
|
||||
try {
|
||||
this.getTokenTransferPrice(token, true)
|
||||
} catch (e) {
|
||||
logger.info(`error refresh price of token: ${token}`, e.message || e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleAll() {
|
||||
const job = schedule.scheduleJob('*/30 * * * * *', async () => {
|
||||
this.refreshAll()
|
||||
})
|
||||
}
|
||||
}
|
8
src/structs/ChainData.ts
Normal file
8
src/structs/ChainData.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface IChainData {
|
||||
name: string
|
||||
type: string
|
||||
rpc: string
|
||||
id: number
|
||||
symbol: string
|
||||
explorerurl: string
|
||||
}
|
7
src/structs/PriceData.ts
Normal file
7
src/structs/PriceData.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface IPriceData {
|
||||
gas: number
|
||||
price: string
|
||||
eth: string
|
||||
leagel?: string
|
||||
expired?: number
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user