rubic-sdk
Version:
Simplify dApp creation
287 lines • 12.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EvmWeb3Public = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const errors_1 = require("../../../../../common/errors");
const p_timeout_1 = __importDefault(require("../../../../../common/utils/p-timeout"));
const healthcheck_1 = require("../../../constants/healthcheck");
const erc_20_token_abi_1 = require("./constants/erc-20-token-abi");
const evm_multicall_abi_1 = require("./constants/evm-multicall-abi");
const tx_status_1 = require("../models/tx-status");
const web3_public_1 = require("../web3-public");
const evm_web3_pure_1 = require("../../../web3-pure/typed-web3-pure/evm-web3-pure/evm-web3-pure");
const default_http_client_1 = require("../../../../http-client/default-http-client");
/**
* Class containing methods for calling contracts in order to obtain information from the blockchain.
* To send transaction or execute contract method use {@link Web3Private}.
*/
class EvmWeb3Public extends web3_public_1.Web3Public {
constructor(web3, blockchainName, httpClient) {
super(blockchainName);
this.web3 = web3;
this.httpClient = httpClient;
this.tokenContractAbi = erc_20_token_abi_1.ERC20_TOKEN_ABI;
}
setProvider(provider) {
this.web3.setProvider(provider);
}
async healthCheck(timeoutMs = 4000) {
if (!(0, healthcheck_1.isBlockchainHealthcheckAvailable)(this.blockchainName)) {
return true;
}
const healthcheckData = healthcheck_1.HEALTHCHECK[this.blockchainName];
const contract = new this.web3.eth.Contract(healthcheckData.contractAbi, healthcheckData.contractAddress);
try {
const result = await (0, p_timeout_1.default)(contract.methods[healthcheckData.method]().call(), timeoutMs);
return result === healthcheckData.expected;
}
catch (e) {
if (e instanceof errors_1.TimeoutError) {
console.debug(`${this.blockchainName} node healthcheck timeout (${timeoutMs}ms) has occurred.`);
}
else {
console.debug(`${this.blockchainName} node healthcheck fail: ${e}`);
}
return false;
}
}
async getBalance(userAddress, tokenAddress) {
let balance;
if (tokenAddress && !evm_web3_pure_1.EvmWeb3Pure.isNativeAddress(tokenAddress)) {
balance = await this.getTokenBalance(userAddress, tokenAddress);
}
else {
balance = await this.web3.eth.getBalance(userAddress);
}
return new bignumber_js_1.default(balance);
}
async getTokenBalance(userAddress, tokenAddress) {
const contract = new this.web3.eth.Contract(this.tokenContractAbi, tokenAddress);
const balance = await contract.methods.balanceOf(userAddress).call();
return new bignumber_js_1.default(balance);
}
async getAllowance(tokenAddress, ownerAddress, spenderAddress) {
const contract = new this.web3.eth.Contract(this.tokenContractAbi, tokenAddress);
const allowance = await contract.methods.allowance(ownerAddress, spenderAddress).call();
return new bignumber_js_1.default(allowance);
}
async multicallContractsMethods(contractAbi, contractsData) {
if (this.multicallAddress) {
const calls = contractsData.map(({ contractAddress, methodsData }) => {
const contract = new this.web3.eth.Contract(contractAbi, contractAddress);
return methodsData.map(({ methodName, methodArguments }) => ({
callData: contract.methods[methodName](...methodArguments).encodeABI(),
target: contractAddress
}));
});
const outputs = await this.multicall(calls.flat());
let outputIndex = 0;
return contractsData.map(contractData => contractData.methodsData.map(methodData => {
const methodOutputAbi = contractAbi.find(funcSignature => funcSignature.name === methodData.methodName).outputs;
const output = outputs[outputIndex];
if (!output) {
throw new errors_1.RubicSdkError('Output has to be defined');
}
outputIndex++;
return {
success: output.success,
output: output.success && output.returnData.length > 2
? this.web3.eth.abi.decodeParameters(methodOutputAbi, output.returnData)[0]
: null
};
}));
}
return this.multicallContractsMethodsByOne(contractAbi, contractsData);
}
/**
* Executes multiple calls in the single contract call.
* @param calls Multicall calls data list.
* @returns Result of calls execution.
*/
async multicall(calls) {
const contract = new this.web3.eth.Contract(evm_multicall_abi_1.EVM_MULTICALL_ABI, this.multicallAddress);
return contract.methods.tryAggregate(false, calls).call();
}
multicallContractsMethodsByOne(contractAbi, contractsData) {
return Promise.all(contractsData.map(contractData => {
const contract = new this.web3.eth.Contract(contractAbi, contractData.contractAddress);
return Promise.all(contractData.methodsData.map(async (methodData) => {
try {
const output = (await contract.methods[methodData.methodName](...methodData.methodArguments).call());
return {
success: true,
output
};
}
catch {
return {
success: false,
output: null
};
}
}));
}));
}
async callContractMethod(contractAddress, contractAbi, methodName, methodArguments = [], options = {}) {
const contract = new this.web3.eth.Contract(contractAbi, contractAddress);
return contract.methods[methodName](...methodArguments).call({
...(options.from && { from: options.from }),
...(options.value && { value: options.value })
});
}
/**
* Predicts the volume of gas required to execute the contract method.
* @param contractAbi Abi of smart-contract.
* @param contractAddress Address of smart-contract.
* @param methodName Method which execution gas limit is to be calculated.
* @param methodArguments Arguments of the contract method.
* @param fromAddress The address for which the gas calculation will be called.
* @param value The value transferred for the call “transaction” in wei.
* @returns Estimated gas limit.
*/
async getEstimatedGas(contractAbi, contractAddress, methodName, methodArguments, fromAddress, value) {
const contract = new this.web3.eth.Contract(contractAbi, contractAddress);
try {
const gasLimit = await contract.methods[methodName](...methodArguments).estimateGas({
from: fromAddress,
gas: 10000000,
...(value && { value })
});
return new bignumber_js_1.default(gasLimit);
}
catch (err) {
console.debug(err);
return null;
}
}
/**
* Get estimated gas of several contract method executions via rpc batch request.
* @param fromAddress Sender address.
* @param callsData Transactions parameters.
* @returns List of contract execution estimated gases.
* If the execution of the method in the real blockchain would not be reverted,
* then the list item would be equal to the predicted gas limit.
* Else (if you have not enough balance, allowance ...) then the list item would be equal to null.
*/
async batchEstimatedGas(fromAddress, callsData) {
try {
const rpcCallsData = callsData.map(callData => ({
rpcMethod: 'eth_estimateGas',
params: {
from: fromAddress,
to: callData.to,
data: callData.data,
...(callData.value && {
value: `0x${parseInt(callData.value).toString(16)}`
})
}
}));
const result = await this.rpcBatchRequest(rpcCallsData);
return result.map(value => (value ? new bignumber_js_1.default(value) : null));
}
catch (e) {
console.error(e);
return callsData.map(() => null);
}
}
/**
* Sends batch request to rpc provider directly.
* @see {@link https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=false&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false|EthereumJSON-RPC}
* @param rpcCallsData Rpc methods and parameters list.
* @returns Rpc batch request call result sorted in order of input parameters.
*/
async rpcBatchRequest(rpcCallsData) {
const seed = Date.now();
const batch = rpcCallsData.map((callData, index) => ({
id: seed + index,
jsonrpc: '2.0',
method: callData.rpcMethod,
params: [{ ...callData.params }]
}));
const httpClient = await this.getHttpClient();
const response = await httpClient.post(this.web3.currentProvider.host, batch);
return response.sort((a, b) => a.id - b.id).map(item => (item.error ? null : item.result));
}
/**
* Returns httpClient if it exists or imports the axios client.
*/
async getHttpClient() {
if (!this.httpClient) {
this.httpClient = await default_http_client_1.DefaultHttpClient.getInstance();
}
return this.httpClient;
}
/**
* Gets mined transaction receipt.
* @param hash Transaction hash
*/
async getTransactionReceipt(hash) {
return this.web3.eth.getTransactionReceipt(hash);
}
async getTransactionStatus(hash) {
const txReceipt = await this.getTransactionReceipt(hash);
if (txReceipt === null) {
return tx_status_1.TxStatus.PENDING;
}
if (txReceipt.status) {
return tx_status_1.TxStatus.SUCCESS;
}
return tx_status_1.TxStatus.FAIL;
}
/**
* Calculates the average price per unit of gas according to web3.
* @returns Average gas price in wei.
*/
async getGasPrice() {
return this.web3.eth.getGasPrice();
}
/**
* Gets block by block id.
* @param [blockId] Block id: hash, number ... Default is 'latest'.
* @returns Block by blockId parameter.
*/
getBlock(blockId = 'latest') {
return this.web3.eth.getBlock(blockId);
}
async getBlockNumber() {
return this.web3.eth.getBlockNumber();
}
async getPastEvents(contractAddress, contractAbi, eventName, options) {
const contract = new this.web3.eth.Contract(contractAbi, contractAddress);
const blockNumber = options.toBlock === 'latest' ? await this.getBlockNumber() : options.toBlock;
return contract.getPastEvents(eventName, {
fromBlock: blockNumber - options.blocksAmount,
toBlock: blockNumber
});
}
/**
* Will call smart contract method in the EVM without sending any transaction.
* @param contractAddress Contract address.
* @param contractAbi Contract ABI.
* @param methodName Method name.
* @param methodArguments Method arguments.
* @param options Sender address and value.
* @returns Transaction receipt.
*/
async staticCallContractMethod(contractAddress, contractAbi, methodName, methodArguments, options) {
const contract = new this.web3.eth.Contract(contractAbi, contractAddress);
return new Promise((resolve, reject) => {
contract.methods[methodName](...methodArguments).call({
from: options?.from,
...(options?.value && { value: options.value })
}, (error, result) => {
if (result) {
resolve(result);
}
if (error) {
reject(error);
}
});
});
}
}
exports.EvmWeb3Public = EvmWeb3Public;
//# sourceMappingURL=evm-web3-public.js.map