UNPKG

olympus-module

Version:

Module made by Olympus Labs for easier access to the Olympus protocol through Javascript/Typescript

195 lines (167 loc) 6.02 kB
import * as Web3 from 'web3'; import Promisify from '../utils/Promisify'; import Config from '../config'; import BigNumber from 'bignumber.js'; import { Tx } from '../interfaces/Tx'; import * as EthereumTx from 'ethereumjs-tx'; import { logger } from '../utils/logger'; import { TxStatus } from '../interfaces/Enums'; let rpcConnection; export class RpcService { private _rpc; public constructor(web3Url: string) { if (!rpcConnection) { rpcConnection = new Web3(); rpcConnection.setProvider(new Web3.providers.HttpProvider(web3Url, 9000)); } this._rpc = rpcConnection; } // Access to native rpc public get rpc(): Web3 { return this._rpc; } // Wrapper public toHex(number): number { return this.rpc.toHex(number); } public contract(abi: any, address: string): any { return this.rpc.eth.contract(abi).at(address); } public fromWei(amount: BigNumber, unit: string = 'ether') { return this.rpc.fromWei(amount, unit); } public toWei(amount: number, unit: string) { return this.rpc.toWei(amount, unit); } public version() { return this.rpc.version.api; } public isValidAddress(address) { return this.rpc.isAddress(address); } public async getNonce(address): Promise<number> { const nonce = await Promisify((callback) => this.rpc.eth.getTransactionCount(address, this.rpc.eth.defaultBlock, callback)) as number; return nonce; } public async getTransactionReceipt(hash: string): Promise<{ status: string, logs: any }> { const result = await Promisify((callback) => this.rpc.eth.getTransactionReceipt(hash, callback)) as { status: string, logs: any }; return result; } public async getTransactionsReceipts(hashes: string[]): Promise<any[]> { // Return receipts from eth const receipts = await Promise.all( hashes.map(async (hash) => await Promisify((callback) => this.rpc.eth.getTransactionReceipt(hash, callback)), ), ); return receipts; } public async getBalance(address: string): Promise<BigNumber> { return await Promisify((callback) => this.rpc.eth.getBalance(address, callback)) as BigNumber; } public async getGasPrice(): Promise<BigNumber> { const gasPrice = await Promisify((callback) => this.rpc.eth.getGasPrice(callback)) as BigNumber; return gasPrice; } public async getGasLimit(): Promise<number> { const block: any = await Promisify((callback) => this.rpc.eth.getBlock('latest', callback)); const gasLimit = block.gasLimit; return gasLimit; } public toAscii(hex) { return this.rpc.toAscii(hex); } public fromAscii(string) { return this.rpc.fromAscii(string); } public async buildTx({ from, to, data, amount = 0, gasPrice = 0, gasLimit = 0 }: { from?: string, to: string, data: any, amount?: number, gasPrice?: number, gasLimit?: number }, ): Promise<Tx> { const amountWei = amount ? this.rpc.toWei(amount, 'ether') : 0; const recommendedGasPrice = gasPrice || (await this.getGasPrice()).toNumber() + Config.extraGasPrice; const walletAddress = from || Config.walletAddress; let finalGasLimit; try { finalGasLimit = gasLimit || Math.round(await this.estimateGas({ to, data, }) * Config.gasIncrement); } catch (e) { // If it is not an estimation error, still throw it, it will be handled normally and not executed if (e && e.message && !e.message.includes('gas required exceeds allowance or always failing transaction')) { throw e; } logger.warn(`Estimation error: ${e}`); // We can use this to deduce that the estimation failed // didEstimateFail = finalGasLimit == Config.maximumBlockGas ? true : false finalGasLimit = Config.maximumBlockGas; } const tx: Partial<Tx> = { from: walletAddress, gasPrice: this.rpc.toHex(recommendedGasPrice), gas: this.rpc.toHex(Math.min(finalGasLimit, Config.maximumBlockGas)), to, value: this.rpc.toHex(amountWei), data, nonce: await this.getNonce(walletAddress), }; return tx as Tx; } public async sendTX(productAddress: string, data) { try { const tx = await this.buildTx({ to: productAddress, data }); const newTx = await this.sendRawTransaction(tx); return newTx; } catch (e) { console.error(`Error sending tx for address: ${productAddress}. Error: ${e}`); return null; } } public async sendRawTransaction(tx: Tx): Promise<Tx> { const ethTx = new EthereumTx(tx); ethTx.sign(new Buffer(Config.privateKey, 'hex')); const serializedTx = ethTx.serialize(); // TODO, OL-1732 // if (AppOptions.dry) { // logger.info(`Running dry for ${tx}`); // return tx; // } tx.hash = await Promisify((callback) => this.rpc.eth.sendRawTransaction(`0x${serializedTx.toString('hex')}`, callback)) as string; return tx; } public async isTxSuccessful(hash: string): Promise<TxStatus> { const result = await this.getTransactionReceipt(hash); if (result) { switch (result.status) { case '0x0': return TxStatus.FAIL; case '0x1': return TxStatus.SUCCESS; default: return TxStatus.FAIL; } } return TxStatus.PENDING; } public async estimateGas({ from, to, data, amount = 0, gasPrice = 0 }: { from?: string, to: string, data: any, amount?: number, gasPrice?: number }, ): Promise<number> { let gas; const amountWei = amount ? this.toWei(amount, 'ether') : undefined; const recommendedGasPrice = gasPrice || await this.getGasPrice(); const walletAddress = from || Config.walletAddress; gas = await Promisify<number>((cb: () => any) => { this.rpc.eth.estimateGas({ from: walletAddress, gasPrice: this.toHex(+recommendedGasPrice), to, value: this.toHex(amountWei), data, }, cb); }); return Math.ceil(gas * 1.1); } }