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
text/typescript
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);
}
}