From d7dbae9d5239712ad117ef53401b795e97c4581b Mon Sep 17 00:00:00 2001 From: Ivaylo Nikolov Date: Tue, 17 Dec 2024 01:37:10 +0200 Subject: [PATCH] feat: add mirror node contract call and estimate queries Signed-off-by: Ivaylo Nikolov --- src/query/MirrorNodeContractCallQuery.js | 37 ++++ src/query/MirrorNodeContractEstimateQuery.js | 36 ++++ src/query/MirrorNodeContractQuery.js | 187 +++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 src/query/MirrorNodeContractCallQuery.js create mode 100644 src/query/MirrorNodeContractEstimateQuery.js create mode 100644 src/query/MirrorNodeContractQuery.js diff --git a/src/query/MirrorNodeContractCallQuery.js b/src/query/MirrorNodeContractCallQuery.js new file mode 100644 index 000000000..2511de3c9 --- /dev/null +++ b/src/query/MirrorNodeContractCallQuery.js @@ -0,0 +1,37 @@ +import MirrorNodeContractQuery from "./MirrorNodeContractQuery.js"; + +/** + * @typedef {import("../channel/Channel.js").default} Channel + * @typedef {import("../client/Client.js").default<*, *>} Client + */ +export default class MirrorNodeContractCallQuery extends MirrorNodeContractQuery { + /** + * @returns {Promise} + */ + async execute() { + if (this.callData == null) { + throw new Error("Call data is required."); + } + + const API_ENDPOINT = "contracts/call"; + const JSON_PAYLOAD = { + data: Buffer.from(this.callData).toString("hex"), + to: this.contractEvmAddress, + estimate: false, + }; + + console.log(JSON_PAYLOAD); + /** + * @type { { data: { result: string } } } + */ + const mirrorNodeRequest = await this.performMirrorNodeRequest( + API_ENDPOINT, + JSON.stringify(JSON_PAYLOAD), + ); + //console.log(mirrorNodeRequest); + /** + * @type {object} + */ + return mirrorNodeRequest.data.result; + } +} diff --git a/src/query/MirrorNodeContractEstimateQuery.js b/src/query/MirrorNodeContractEstimateQuery.js new file mode 100644 index 000000000..da61a4228 --- /dev/null +++ b/src/query/MirrorNodeContractEstimateQuery.js @@ -0,0 +1,36 @@ +import Long from "long"; +import MirrorNodeContractQuery from "./MirrorNodeContractQuery.js"; + +/** + * @typedef {import("../channel/Channel.js").default} Channel + * @typedef {import("../client/Client.js").default<*, *>} Client + */ +export default class MirrorNodeContractCallQuery extends MirrorNodeContractQuery { + /** + * @returns {Promise} + */ + async execute() { + if (this.callData == null) { + throw new Error("Call data is required."); + } + + const JSON_PAYLOAD = { + data: Buffer.from(this.callData).toString("hex"), + from: this.senderEvmAddress, + to: this.contractEvmAddress, + estimate: true, + value: this.value, + }; + + /** + * @type {{data: {result: string}}} + */ + const mirrorNodeRequest = await this.performMirrorNodeRequest( + "contracts/call", + JSON.stringify(JSON_PAYLOAD), + ); + + console.log(Number(mirrorNodeRequest.data.result)); + return Long.fromNumber(Number(mirrorNodeRequest.data.result)); + } +} diff --git a/src/query/MirrorNodeContractQuery.js b/src/query/MirrorNodeContractQuery.js new file mode 100644 index 000000000..3e093ee2f --- /dev/null +++ b/src/query/MirrorNodeContractQuery.js @@ -0,0 +1,187 @@ +import axios from "axios"; +import { ContractFunctionParameters } from "../exports.js"; + +/** + * @typedef {import("../contract/ContractId").default} ContractId + * @typedef {import("../account/AccountId").default} AccountId + * @typedef {import("../client/Client.js").default<*, *>} Client + * @typedef {import("axios").AxiosResponse} AxiosResponse + * + */ +export default class MirrorNodeContractQuery { + constructor() { + this._contractId = null; + this._contractEvmAddress = null; + this._sender = null; + this._senderEvmAddress = null; + this._functionName = null; + this._functionParameters = null; + this._value = null; + this._gasLimit = null; + this._gasPrice = null; + this._blockNumber = null; + } + + /** + * + * @param {ContractId} contractId + * @returns + */ + setContractId(contractId) { + this._contractId = contractId; + return this; + } + + /** + * @param {AccountId} sender + * @returns + */ + setSender(sender) { + this._sender = sender; + return this; + } + + /** + * + * @param {string} name + * @param {ContractFunctionParameters} functionParameters + * @returns + */ + setFunction(name, functionParameters) { + this._functionParameters = + functionParameters != null + ? functionParameters._build(name) + : new ContractFunctionParameters()._build(name); + + return this; + } + + /** + * @param {Long} value + * @returns + */ + setValue(value) { + this._value = value; + return this; + } + + /** + * @param {Long} gasLimit + * @returns + */ + setGasLimit(gasLimit) { + this._gasLimit = gasLimit; + return this; + } + + /** + * @param {Long} gasPrice + * @returns + */ + setGasPrice(gasPrice) { + this._gasPrice = gasPrice; + return this; + } + + /** + * @param {Long} blockNumber + * @returns + */ + setBlockNumber(blockNumber) { + this._blockNumber = blockNumber; + return this; + } + + /** + * @returns {ContractId?} + */ + get contractId() { + return this._contractId; + } + + /** + * @returns {string} + */ + get contractEvmAddress() { + const solidityAddress = this._contractId?.toSolidityAddress(); + if (solidityAddress == null) { + throw new Error("Contract ID is not set"); + } + return solidityAddress; + } + + /** + * @returns {AccountId?} + */ + get sender() { + return this._sender; + } + + /** + * @returns {string?} + */ + get senderEvmAddress() { + const solidityAddress = this._sender?.toSolidityAddress(); + if (solidityAddress == null) { + throw new Error("Sender is not set"); + } + return solidityAddress; + } + + /** + * @returns {Uint8Array | null | undefined} + */ + get callData() { + return this._functionParameters; + } + + /** + * @returns {Long?} + */ + get value() { + return this._value; + } + + /** + * @returns {Long?} + */ + get gasLimit() { + return this._gasLimit; + } + + /** + * @returns {Long?} + */ + get gasPrice() { + return this._gasPrice; + } + + /** + * @returns {Long?} + */ + get blockNumber() { + return this._blockNumber; + } + + /** + * + * @param {string} apiEndpoint + * @param {string} jsonPayload + * @returns {Promise} + */ + async performMirrorNodeRequest(apiEndpoint, jsonPayload) { + if (this.contractId == null) { + throw new Error("Contract ID is not set"); + } + + const MIRROR_NETWORK_ADDRESS = + "https://testnet.mirrornode.hedera.com/api/v1/" + apiEndpoint; + + let result = await axios.post(MIRROR_NETWORK_ADDRESS, jsonPayload, { + headers: { + "Content-Type": "application/json", + }, + }); + return result; + } +}