provider/lib/plugin.js

349 lines
8.9 KiB
JavaScript

const Web3 = require('web3')
const { hexToNumberString, toChecksumAddress, numberToHex } = require('web3-utils')
const VERSIONS = ['old', 'new']
// TODO: create constants, utils
// TODO: move _repeatUntilResult and _generateId to utils
export default (ctx, inject) => {
const moduleOptions = <%= JSON.stringify(options) %>
const instance = class Provider {
constructor(options) {
this.address = ''
this.version = 'new'
this.config = options.config
this.web3 = new Web3(new Web3.providers.HttpProvider(options.config.rpcUrl))
}
async initProvider(provider, { version }) {
try {
this.provider = provider
await this._setVersion(version)
return await this._initProvider()
} catch (err) {
throw new Error(`Provider method initProvider has error: ${err.message}`)
}
}
initWeb3(rpcUrl) {
try {
if (!rpcUrl) {
throw new Error(`Please set rpcUrl to params, current rpcUrl ${rpcUrl}`)
}
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
this.web3 = web3
return web3
} catch (err) {
throw new Error(`Provider method initWeb3 has error: ${err.message}`)
}
}
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}`)
}
}
getWeb3(rpcUrl) {
try {
if (!rpcUrl) {
throw new Error(`Please set url to params, current url ${rpcUrl}`)
}
return new Web3(rpcUrl)
} catch (err) {
throw new Error(`Provider method getWeb3 has error: ${err.message}`)
}
}
getContract({ abi, address }) {
return new this.web3.eth.Contract(abi, address)
}
async deployContract({ domain, deploymentActions, abi, address }) {
try {
const [{ bytecode, expectedAddress }] = deploymentActions.actions.filter((action) => action.domain === domain)
const code = await this.web3.eth.getCode(expectedAddress)
if (code !== '0x') {
throw new Error('Already deployed')
}
const data = this.getContract({ abi, address }).methods.deploy(bytecode, deploymentActions.salt).encodeABI()
const callParams = {
method: 'eth_sendTransaction',
params: [
{
from: this.address,
to: this.getContract()._address,
gas: numberToHex(6e6),
gasPrice: '0x100000000',
value: 0,
data,
},
],
from: this.address,
}
return await this.sendRequest(callParams)
} catch (err) {
throw new Error(`Provider method deployContract has error: ${err.message}`)
}
}
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}`)
}
}
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}`)
}
}
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}`)
}
}
async batchRequest({ txs, callback }) {
try {
const txsPromisesBucket = []
for (const [index, params] of txs.entries()) {
const txPromise = this.sendRequest({
method: 'eth_sendTransaction',
params: [params],
})
await this._sleep(1000)
if (!(index % 2) && index !== 0) {
await txPromise
}
txsPromisesBucket.push(txPromise)
}
callback(txsPromisesBucket)
return await Promise.all(txsPromisesBucket)
} catch (err) {
throw new Error(err.message)
}
}
async checkNetworkVersion() {
try {
return await this.sendRequest({ method: 'net_version' })
} catch (err) {
throw new Error(`Provider method _checkNetworkVersion has error: ${err.message}`)
}
}
on({ method, callback }) {
try {
this.provider.on(method, callback)
} catch (err) {
throw new Error(`Provider method _checkNetworkVersion has error: ${err.message}`)
}
}
async _initProvider() {
try {
const request = () =>
this.version === 'old' ? 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}`)
}
}
_sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
}
_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(
{
from,
method,
params,
jsonrpc: '2.0',
id: this._generateId(),
},
callback,
)
})
}
async _send({ method, params }) {
try {
return await this.provider.send(method, params)
} catch (err) {
throw new Error(`Provider method _send has error: ${err.message}`)
}
}
_onNetworkChanged({ id }) {
if (id) {
this.network = id
window.location.reload()
}
}
_onAccountsChanged(accounts) {
const [account] = accounts
if (account) {
this.address = toChecksumAddress(account)
window.location.reload()
}
}
async _setVersion(version) {
try {
if(version) {
if (!VERSIONS.includes(version)) {
throw new Error('Invalid version parameter.')
}
this.version = version
return
}
return await this._checkVersion()
} catch (err) {
throw new Error(`Provider method _setVersion has error: ${err.message}`)
}
}
_checkVersion() {
if (this.provider && this.provider.request) {
this.version = 'new'
} else {
this.version = 'old'
}
}
_repeatUntilResult(action, totalAttempts, retryAttempt = 1) {
return new Promise((resolve, reject) => {
const iteration = async () => {
try {
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)
}
} catch (err) {
reject(err)
}
}
iteration()
})
}
_generateId() {
const date = Date.now() * Math.pow(10, 3);
const extra = Math.floor(Math.random() * Math.pow(10, 3));
return date + extra;
}
}
const provider = new instance({ config: moduleOptions })
inject('provider', provider)
ctx.$provider = provider
}