tornado-initiation-ui/services/provider.js
2020-11-05 20:17:59 +10:00

377 lines
8.2 KiB
JavaScript

import Web3 from 'web3'
import { hexToNumberString, toChecksumAddress } from 'web3-utils'
class Provider {
/**
* Constructor params.
*
* @param options {object}
* @param {object} options.config
* @param {number} options.config.id
* @param {string} options.config.rpcUrl
* @param {number} options.config.rpcCallRetryAttempt
* @param {object} options.provider
* @param {string} options.rpcUrl - node api endpoint.
* @returns {*}
*/
constructor(options) {
this.address = ''
this.version = 'new'
this.config = options.config
this.web3 = new Web3(new Web3.providers.HttpProvider(options.rpcUrl))
}
async initProvider(provider) {
try {
this.provider = provider
this.web3 = new Web3(provider)
await this._checkVersion()
return await this._initProvider()
} catch (err) {
throw new Error(`Provider method initProvider has error: ${err.message}`)
}
}
/**
* Send a request to the provider.
*
* @param params {object}
* @returns {*}
*/
async sendRequest(params) {
try {
const request = (args) =>
this.version === 'old'
? this._sendAsync(args)
: this.provider.request(args)
return await request(params)
} catch (err) {
throw new Error(`Provider method sendRequest has error: ${err.message}`)
}
}
/**
* Calling a custom method on the provider.
*
* @param params {object}
* @param {string} params.methodName
* @param {string} params.data
* @param {string} params.to
* @param {string} params.from
* @param {string} params.gas
* @param {number} params.value
* @returns {*}
*/
async contractRequest({ methodName, data, to, from, gas, value = 0 }) {
const { rpcCallRetryAttempt, blockGasLimit } = this.config
try {
const params = {
to,
data,
value,
from: from || this.address,
gas: gas || blockGasLimit + 100000,
}
return await this._repeatUntilResult(
() => this.web3.eth[methodName](params),
rpcCallRetryAttempt
)
} catch (err) {
throw new Error(
`Provider method contractRequest has error: ${err.message}`
)
}
}
/**
* Get your address balance.
*
* @param params {object}
* @param {string} params.address.
* @returns {*}
*/
async getBalance({ address }) {
const { rpcCallRetryAttempt } = this.config
try {
const params = {
method: 'eth_getBalance',
params: [address, 'latest'],
}
const balance = await this._repeatUntilResult(
() => this.sendRequest(params),
rpcCallRetryAttempt
)
return hexToNumberString(balance)
} catch (err) {
throw new Error(`Provider method getBalance has error: ${err.message}`)
}
}
/**
* Waiting for receipt of the transaction.
*
* @param params {object}
* @param {string} params.txHash
* @returns {*}
*/
async waitForTxReceipt({ txHash }) {
const { rpcCallRetryAttempt } = this.config
try {
const params = {
method: 'eth_getTransactionReceipt',
params: [txHash],
}
return await this._repeatUntilResult(
() => this.sendRequest(params),
rpcCallRetryAttempt * 10
)
} catch (err) {
throw new Error(
`Provider method waitForTxReceipt has error: ${err.message}`
)
}
}
/**
* Get network version, the current provider.
*
* @param params {object}
* @param {array} params.txs
* @param {function} params.callback
* @returns {*}
*/
async batchRequest({ txs, callback }) {
try {
const web3 = new Web3(this.provider)
const batch = new web3.BatchRequest()
const txsPromisesBucket = []
for (const params of txs) {
const txPromise = new Promise((resolve, reject) => {
const tx = web3.eth.sendTransaction.request(
params,
(error, txHash) => {
if (error) {
reject(error)
} else {
resolve(txHash)
}
}
)
batch.add(tx)
})
txsPromisesBucket.push(txPromise)
}
batch.execute()
callback(txsPromisesBucket)
return await Promise.all(txsPromisesBucket)
} catch (err) {
throw new Error(err.message)
}
}
/**
* Get network version, the current provider.
*/
async checkNetworkVersion() {
try {
return await this.sendRequest({ method: 'net_version' })
} catch (err) {
throw new Error(
`Provider method _checkNetworkVersion has error: ${err.message}`
)
}
}
/**
* Initialize provider.
*
*/
async _initProvider() {
try {
const request = () =>
this.version
? this.provider.enable()
: this.sendRequest({ method: 'eth_requestAccounts' })
const [account] = await request('')
if (!account) {
throw new Error('Locked metamask')
}
this.address = account
this.provider.on('accountsChanged', (accounts) =>
this._onAccountsChanged(accounts)
)
this.provider.on('chainChanged', (id) => this._onNetworkChanged({ id }))
this.config.id = await this.checkNetworkVersion()
return toChecksumAddress(account)
} catch (err) {
throw new Error(`Provider method _initProvider has error: ${err.message}`)
}
}
/**
* Subscribe to event in provider.
*
* @param method {string}
* @param {function} callback
* @returns {*}
*/
on(method, callback) {
if (method) {
this.provider.on(method, callback)
}
}
/**
* Send a request to the provider (old api).
*
* @param options {object}
* @param {string} params.method.
* @param {string} params.params.
* @param {string} params.from.
* @returns {*}
*/
_sendAsync({ method, params, from }) {
const { id } = this.config
switch (id) {
case 77:
case 99:
case 100:
from = undefined
break
}
return new Promise((resolve, reject) => {
const callback = (err, response) => {
if (err || response.error) {
reject(err)
}
resolve(response.result)
}
this.provider.sendAsync(
{
method,
params,
jsonrpc: '2.0',
from,
},
callback
)
})
}
/**
* Send a request to the provider (old api).
*
* @param options {object}
* @param {string} params.method.
* @param {string} params.params.
* @returns {*}
*/
async _send({ method, params }) {
try {
return await this.provider.send(method, params)
} catch (err) {
throw new Error(`Provider method _send has error: ${err.message}`)
}
}
/**
* Set network id.
*
* @param params {object}
* @param {string} params.id.
* @returns {*}
*/
_onNetworkChanged({ id, callback }) {
if (id) {
this.network = id
if (typeof callback === 'function') {
callback()
}
window.location.reload()
}
}
/**
* Set default address to the provider.
*
* @param accounts {String[]}.
* @returns {*}
*/
_onAccountsChanged(accounts) {
const [account] = accounts
if (account) {
this.address = toChecksumAddress(account)
window.location.reload()
}
}
/**
* Check version currently provider.
*
*/
_checkVersion() {
if (this.provider && this.provider.request) {
this.version = 'new'
} else {
this.version = 'old'
}
}
/**
* Repeat the function call as many times as needed.
*
* @function action.
* @function totalAttempts {number}
* @function retryAttempt {number}
* @returns {*}
*/
_repeatUntilResult(action, totalAttempts, retryAttempt = 1) {
return new Promise((resolve, reject) => {
const iteration = async () => {
const result = await action()
if (!result) {
if (retryAttempt <= totalAttempts) {
retryAttempt++
setTimeout(iteration, 1000 * retryAttempt)
} else {
return reject(new Error('Tx not minted'))
}
} else {
resolve(result)
}
}
iteration()
})
}
}
export default Provider