UNPKG

@ledgerhq/coin-hedera

Version:
322 lines 12.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.apiClient = void 0; const errors_1 = require("@ledgerhq/errors"); const live_env_1 = require("@ledgerhq/live-env"); const live_network_1 = __importDefault(require("@ledgerhq/live-network")); const bignumber_js_1 = __importDefault(require("bignumber.js")); const viem_1 = require("viem"); const constants_1 = require("../constants"); const errors_2 = require("../errors"); const API_URL = (0, live_env_1.getEnv)("API_HEDERA_MIRROR"); async function getAccountsForPublicKey(publicKey) { let res; try { res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/accounts?account.publicKey=${publicKey}&balance=true&limit=100`, }); } catch (e) { if (e instanceof errors_1.LedgerAPI4xx) return []; throw e; } const accounts = res.data.accounts; return accounts; } /** * Fetches account information from the Hedera Mirror Node API, excluding transactions. * * @param address - The Hedera account ID (e.g., "0.0.12345") * @param timestamp - Optional timestamp filter to get historical account state. * Supports comparison operators: * - "lt:1234567890.123456789" - state before the timestamp * - "eq:1234567890.123456789" - state at the timestamp * Used primarily for analyzing state changes in staking operations. * @returns Promise resolving to account data * @throws HederaAddAccountError if account not found (404) */ async function getAccount(address, timestamp) { try { const params = new URLSearchParams({ transactions: "false", ...(timestamp && { timestamp }), }); const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/accounts/${address}?${params.toString()}`, }); const account = res.data; return account; } catch (error) { if (error instanceof errors_1.LedgerAPI4xx && "status" in error && error.status === 404) { throw new errors_2.HederaAddAccountError(); } throw error; } } // keeps old behavior when all pages are fetched const getPaginationDirection = (fetchAllPages, order) => { if (fetchAllPages) return "gt"; return order === "asc" ? "gt" : "lt"; }; async function getAccountTransactions({ address, pagingToken, limit = 100, order = "desc", fetchAllPages, }) { const transactions = []; const params = new URLSearchParams({ "account.id": address, limit: limit.toString(), order, }); if (pagingToken) { params.append("timestamp", `${getPaginationDirection(fetchAllPages, order)}:${pagingToken}`); } let nextCursor = null; let nextPath = `/api/v1/transactions?${params.toString()}`; // WARNING: don't break the loop when `transactions` array is empty but `links.next` is present // the mirror node API enforces a 60-day max time range per query, even if `timestamp` param is set // see: https://hedera.com/blog/changes-to-the-hedera-operated-mirror-node while (nextPath) { const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}${nextPath}`, }); const newTransactions = res.data.transactions; transactions.push(...newTransactions); nextPath = res.data.links.next; // stop fetching if pagination mode is used and we reached the limit if (!fetchAllPages && transactions.length >= limit) { break; } } // ensure we don't exceed the limit in pagination mode if (!fetchAllPages && transactions.length > limit) { transactions.splice(limit); } // set the next cursor only if we have more transactions to fetch if (!fetchAllPages && nextPath) { const lastTx = transactions.at(-1); nextCursor = lastTx?.consensus_timestamp ?? null; } return { transactions, nextCursor }; } async function getAccountTokens(address) { const tokens = []; const params = new URLSearchParams({ limit: "100", }); let nextPath = `/api/v1/accounts/${address}/tokens?${params.toString()}`; while (nextPath) { const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}${nextPath}`, }); const newTokens = res.data.tokens; tokens.push(...newTokens); nextPath = res.data.links.next; } return tokens; } async function getLatestTransaction(before) { const params = new URLSearchParams({ limit: "1", order: "desc", timestamp: `lt:${before.getTime() / 1000}`, }); const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/transactions?${params.toString()}`, }); const transaction = res.data.transactions[0]; if (!transaction) { throw new Error("No transactions found on the Hedera network"); } return transaction; } async function getLatestBlock() { const params = new URLSearchParams({ limit: "1", order: "desc", }); const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/blocks?${params.toString()}`, }); const block = res.data.blocks[0]; if (!block) { throw new Error("No blocks found on the Hedera network"); } return block; } async function getNetworkFees() { const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/network/fees`, }); return res.data; } async function getContractCallResult(transactionHash) { const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/contracts/results/${transactionHash}`, }); return res.data; } // TODO: remove once migration to new API is complete async function findTransactionByContractCall(timestamp, contractId) { const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/transactions?timestamp=${timestamp}`, }); const transactions = res.data.transactions; const relatedTx = transactions.find(el => el.name === constants_1.HEDERA_TRANSACTION_NAMES.ContractCall && el.entity_id === contractId); return relatedTx ?? null; } async function findTransactionByContractCallV2({ timestamp, payerAddress, }) { // Hgraph API returns timestamp as number and nanoseconds precision is lost during parsing // instead of using `timestamp=eq:${timestamp}`, we need to fetch transactions in a small range // +-10 microseconds is used to bypass hgraph precision issue const timestampAsNumber = new bignumber_js_1.default(timestamp).multipliedBy(10 ** 9); const timestampDiffNs = new bignumber_js_1.default(10_000); const from = new bignumber_js_1.default(timestampAsNumber).minus(timestampDiffNs).dividedBy(10 ** 9); const to = new bignumber_js_1.default(timestampAsNumber).plus(timestampDiffNs).dividedBy(10 ** 9); const params = new URLSearchParams({ limit: "100", order: "desc" }); params.append("timestamp", `gte:${from.toFixed(9)}`); params.append("timestamp", `lte:${to.toFixed(9)}`); const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/transactions?${params.toString()}`, }); // try to find main CONTRACT_CALL transaction related to the given address const relatedTx = res.data.transactions.find(tx => { return (tx.name === constants_1.HEDERA_TRANSACTION_NAMES.ContractCall && tx.transaction_id.startsWith(payerAddress) && tx.parent_consensus_timestamp === null); }); return relatedTx ?? null; } // TODO: remove once migration to new API is complete async function getERC20Balance(accountEvmAddress, contractEvmAddress) { const res = await (0, live_network_1.default)({ method: "POST", url: `${API_URL}/api/v1/contracts/call`, data: { block: "latest", to: contractEvmAddress, data: (0, viem_1.encodeFunctionData)({ abi: viem_1.erc20Abi, functionName: "balanceOf", args: [accountEvmAddress], }), }, }); return new bignumber_js_1.default(res.data.result); } async function estimateContractCallGas(senderEvmAddress, recipientEvmAddress, contractEvmAddress, amount) { const res = await (0, live_network_1.default)({ method: "POST", url: `${API_URL}/api/v1/contracts/call`, data: { block: "latest", estimate: true, from: senderEvmAddress, to: contractEvmAddress, data: (0, viem_1.encodeFunctionData)({ abi: viem_1.erc20Abi, functionName: "transfer", args: [recipientEvmAddress, amount], }), }, }); return new bignumber_js_1.default(res.data.result); } async function getTransactionsByTimestampRange({ address, startTimestamp, endTimestamp, limit = 100, order = "desc", }) { const transactions = []; const params = new URLSearchParams({ limit: limit.toString(), order, ...(address && { "account.id": address }), }); params.append("timestamp", startTimestamp); params.append("timestamp", endTimestamp); let nextPath = `/api/v1/transactions?${params.toString()}`; while (nextPath) { const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}${nextPath}`, }); const newTransactions = res.data.transactions; transactions.push(...newTransactions); nextPath = res.data.links.next; } return transactions; } async function getNode(nodeId) { const params = new URLSearchParams({ "node.id": `eq:${nodeId}`, limit: "1", }); const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}/api/v1/network/nodes?${params.toString()}`, }); return res.data.nodes[0] ?? null; } async function getNodes({ cursor, limit = 100, order = "desc", fetchAllPages, }) { const nodes = []; const params = new URLSearchParams({ order, limit: limit.toString(), }); if (cursor) { params.append("node.id", `${getPaginationDirection(fetchAllPages, order)}:${cursor}`); } let nextCursor = null; let nextPath = `/api/v1/network/nodes?${params.toString()}`; while (nextPath) { const res = await (0, live_network_1.default)({ method: "GET", url: `${API_URL}${nextPath}`, }); const newNodes = res.data.nodes; nodes.push(...newNodes); nextPath = res.data.links.next; // stop fetching if pagination mode is used and we reached the limit if (!fetchAllPages && nodes.length >= limit) { break; } } // ensure we don't exceed the limit in pagination mode if (!fetchAllPages && nodes.length > limit) { nodes.splice(limit); } // set the next cursor only if we have more nodes to fetch if (!fetchAllPages && nextPath) { const lastNode = nodes.at(-1); nextCursor = lastNode?.node_id?.toString() ?? null; } return { nodes, nextCursor }; } exports.apiClient = { getAccountsForPublicKey, getAccount, getAccountTokens, getAccountTransactions, getLatestBlock, getLatestTransaction, getNetworkFees, getContractCallResult, findTransactionByContractCall, findTransactionByContractCallV2, getERC20Balance, estimateContractCallGas, getTransactionsByTimestampRange, getNode, getNodes, }; //# sourceMappingURL=api.js.map