UNPKG

@hashgraphonline/standards-sdk

Version:

The Hashgraph Online Standards SDK provides a complete implementation of the Hashgraph Consensus Standards (HCS), giving developers all the tools needed to build applications on Hedera.

354 lines (353 loc) • 11.9 kB
import { Logger } from "./standards-sdk.es22.js"; import { HederaMirrorNode } from "./standards-sdk.es29.js"; import { HCS20_CONSTANTS } from "./standards-sdk.es16.js"; class HCS20PointsIndexer { constructor(network, logger, mirrorNodeUrl) { this.isProcessing = false; this.lastIndexedSequence = /* @__PURE__ */ new Map(); this.logger = logger || new Logger({ level: "info", module: "HCS20PointsIndexer" }); this.mirrorNode = new HederaMirrorNode(network, this.logger, { customUrl: mirrorNodeUrl }); this.state = this.initializeState(); } /** * Initialize empty state */ initializeState() { return { deployedPoints: /* @__PURE__ */ new Map(), balances: /* @__PURE__ */ new Map(), transactions: [], lastProcessedSequence: 0, lastProcessedTimestamp: (/* @__PURE__ */ new Date()).toISOString() }; } /** * Get current state snapshot */ getState() { return { ...this.state, deployedPoints: new Map(this.state.deployedPoints), balances: new Map(this.state.balances), transactions: [...this.state.transactions] }; } /** * Get points info for a specific tick */ getPointsInfo(tick) { return this.state.deployedPoints.get(this.normalizeTick(tick)); } /** * Get balance for an account and tick */ getBalance(tick, accountId) { const normalizedTick = this.normalizeTick(tick); const tickBalances = this.state.balances.get(normalizedTick); if (!tickBalances) return "0"; const balance = tickBalances.get(accountId); return balance?.balance || "0"; } /** * Start indexing process */ async startIndexing(options) { if (this.isProcessing) { this.logger.warn("Indexing already in progress"); return; } this.isProcessing = true; const publicTopicId = options?.publicTopicId || HCS20_CONSTANTS.PUBLIC_TOPIC_ID; const registryTopicId = options?.registryTopicId || HCS20_CONSTANTS.REGISTRY_TOPIC_ID; const pollInterval = options?.pollInterval || 3e4; await this.indexTopics( publicTopicId, registryTopicId, options?.privateTopics ); const pollTopics = async () => { if (!this.isProcessing) return; try { await this.indexTopics( publicTopicId, registryTopicId, options?.privateTopics ); } catch (error) { this.logger.error("Polling error:", error); } if (this.isProcessing) { setTimeout(pollTopics, pollInterval); } }; setTimeout(pollTopics, pollInterval); } /** * Index topics once and wait for completion */ async indexOnce(options) { const publicTopicId = options?.publicTopicId || HCS20_CONSTANTS.PUBLIC_TOPIC_ID; const registryTopicId = options?.registryTopicId || HCS20_CONSTANTS.REGISTRY_TOPIC_ID; await this.indexTopics( publicTopicId, registryTopicId, options?.privateTopics ); } /** * Stop indexing process */ stopIndexing() { this.isProcessing = false; this.logger.info("Indexing stopped"); } /** * Index topics and update state */ async indexTopics(publicTopicId, registryTopicId, privateTopics) { this.logger.debug("Starting indexing cycle"); await this.indexTopic(publicTopicId, false); const registeredTopics = await this.getRegisteredTopics(registryTopicId); const topicsToIndex = [...registeredTopics, ...privateTopics || []]; for (const topicId of topicsToIndex) { await this.indexTopic(topicId, true); } this.logger.debug("Indexing cycle complete"); } /** * Get registered topics from registry */ async getRegisteredTopics(registryTopicId) { const topics = []; try { const messages = await this.mirrorNode.getTopicMessages(registryTopicId, { limit: 100, order: "asc" }); for (const msg of messages) { try { const msgData = msg.data || msg; if (msgData && typeof msgData === "object" && msgData.p === "hcs-20" && msgData.op === "register" && msgData.t_id) { topics.push(msgData.t_id); } } catch (error) { continue; } } } catch (error) { this.logger.error("Failed to fetch registry messages:", error); } return topics; } /** * Index a single topic */ async indexTopic(topicId, isPrivate) { try { const lastSequence = this.lastIndexedSequence.get(topicId); this.logger.debug(`Indexing topic ${topicId}, starting from sequence ${lastSequence || 0}`); const messages = await this.mirrorNode.getTopicMessages(topicId, { sequenceNumber: lastSequence ? lastSequence + 1 : void 0, limit: 1e3, order: "asc" }); this.logger.debug(`Fetched ${messages.length} messages from topic ${topicId}`); let maxSequence = lastSequence || 0; for (const msg of messages) { try { const messageData = msg; if (!messageData.p || messageData.p !== "hcs-20") continue; const parsedMsg = messageData; const sequenceNumber = messageData.sequence_number || 0; this.logger.debug(`Found HCS-20 message: op=${parsedMsg.op}, sequence=${sequenceNumber}`); if (sequenceNumber > maxSequence) { maxSequence = sequenceNumber; } const topicMessage = { consensus_timestamp: messageData.consensus_timestamp || "", sequence_number: sequenceNumber, payer_account_id: messageData.payer_account_id || "", transaction_id: messageData.transaction_id || "" }; this.processMessage(parsedMsg, topicMessage, topicId, isPrivate); this.state.lastProcessedSequence++; this.state.lastProcessedTimestamp = messageData.consensus_timestamp || ""; } catch (error) { this.logger.debug(`Failed to process message: ${error}`); continue; } } if (maxSequence > (lastSequence || 0)) { this.lastIndexedSequence.set(topicId, maxSequence); } } catch (error) { this.logger.error(`Failed to index topic ${topicId}:`, error); } } /** * Process a single message */ processMessage(msg, hcsMsg, topicId, isPrivate) { switch (msg.op) { case "deploy": this.processDeployMessage(msg, hcsMsg, topicId, isPrivate); break; case "mint": this.processMintMessage(msg, hcsMsg, topicId, isPrivate); break; case "transfer": this.processTransferMessage(msg, hcsMsg, topicId, isPrivate); break; case "burn": this.processBurnMessage(msg, hcsMsg, topicId, isPrivate); break; } } /** * Process deploy message */ processDeployMessage(msg, hcsMsg, topicId, isPrivate) { const normalizedTick = this.normalizeTick(msg.tick); if (this.state.deployedPoints.has(normalizedTick)) { return; } const pointsInfo = { name: msg.name, tick: normalizedTick, maxSupply: msg.max, limitPerMint: msg.lim, metadata: msg.metadata, topicId, deployerAccountId: hcsMsg.payer_account_id, currentSupply: "0", deploymentTimestamp: hcsMsg.consensus_timestamp, isPrivate }; this.state.deployedPoints.set(normalizedTick, pointsInfo); this.logger.info(`Deployed points: ${normalizedTick}`); } /** * Process mint message */ processMintMessage(msg, hcsMsg, topicId, isPrivate) { const normalizedTick = this.normalizeTick(msg.tick); const pointsInfo = this.state.deployedPoints.get(normalizedTick); if (!pointsInfo) return; const mintAmount = BigInt(msg.amt); const currentSupply = BigInt(pointsInfo.currentSupply); const maxSupply = BigInt(pointsInfo.maxSupply); if (currentSupply + mintAmount > maxSupply) return; if (pointsInfo.limitPerMint && mintAmount > BigInt(pointsInfo.limitPerMint)) return; pointsInfo.currentSupply = (currentSupply + mintAmount).toString(); let tickBalances = this.state.balances.get(normalizedTick); if (!tickBalances) { tickBalances = /* @__PURE__ */ new Map(); this.state.balances.set(normalizedTick, tickBalances); } const currentBalance = tickBalances.get(msg.to); const newBalance = currentBalance ? (BigInt(currentBalance.balance) + mintAmount).toString() : msg.amt; tickBalances.set(msg.to, { tick: normalizedTick, accountId: msg.to, balance: newBalance, lastUpdated: hcsMsg.consensus_timestamp }); this.state.transactions.push({ id: hcsMsg.transaction_id || `${topicId}-${hcsMsg.sequence_number}`, operation: "mint", tick: normalizedTick, amount: msg.amt, to: msg.to, timestamp: hcsMsg.consensus_timestamp, sequenceNumber: hcsMsg.sequence_number, topicId, transactionId: hcsMsg.transaction_id || "", memo: msg.m }); } /** * Process transfer message */ processTransferMessage(msg, hcsMsg, topicId, isPrivate) { const normalizedTick = this.normalizeTick(msg.tick); const tickBalances = this.state.balances.get(normalizedTick); if (!tickBalances) return; if (!isPrivate && hcsMsg.payer_account_id !== msg.from) return; const senderBalance = tickBalances.get(msg.from); if (!senderBalance || BigInt(senderBalance.balance) < BigInt(msg.amt)) return; const transferAmount = BigInt(msg.amt); senderBalance.balance = (BigInt(senderBalance.balance) - transferAmount).toString(); senderBalance.lastUpdated = hcsMsg.consensus_timestamp; const receiverBalance = tickBalances.get(msg.to); if (receiverBalance) { receiverBalance.balance = (BigInt(receiverBalance.balance) + transferAmount).toString(); receiverBalance.lastUpdated = hcsMsg.consensus_timestamp; } else { tickBalances.set(msg.to, { tick: normalizedTick, accountId: msg.to, balance: msg.amt, lastUpdated: hcsMsg.consensus_timestamp }); } this.state.transactions.push({ id: hcsMsg.transaction_id || `${topicId}-${hcsMsg.sequence_number}`, operation: "transfer", tick: normalizedTick, amount: msg.amt, from: msg.from, to: msg.to, timestamp: hcsMsg.consensus_timestamp, sequenceNumber: hcsMsg.sequence_number, topicId, transactionId: hcsMsg.transaction_id || "", memo: msg.m }); } /** * Process burn message */ processBurnMessage(msg, hcsMsg, topicId, isPrivate) { const normalizedTick = this.normalizeTick(msg.tick); const pointsInfo = this.state.deployedPoints.get(normalizedTick); const tickBalances = this.state.balances.get(normalizedTick); if (!pointsInfo || !tickBalances) return; if (!isPrivate && hcsMsg.payer_account_id !== msg.from) return; const accountBalance = tickBalances.get(msg.from); if (!accountBalance || BigInt(accountBalance.balance) < BigInt(msg.amt)) return; const burnAmount = BigInt(msg.amt); accountBalance.balance = (BigInt(accountBalance.balance) - burnAmount).toString(); accountBalance.lastUpdated = hcsMsg.consensus_timestamp; pointsInfo.currentSupply = (BigInt(pointsInfo.currentSupply) - burnAmount).toString(); this.state.transactions.push({ id: hcsMsg.transaction_id || `${topicId}-${hcsMsg.sequence_number}`, operation: "burn", tick: normalizedTick, amount: msg.amt, from: msg.from, timestamp: hcsMsg.consensus_timestamp, sequenceNumber: hcsMsg.sequence_number, topicId, transactionId: hcsMsg.transaction_id || "", memo: msg.m }); } /** * Normalize tick to lowercase and trim */ normalizeTick(tick) { return tick.toLowerCase().trim(); } } export { HCS20PointsIndexer }; //# sourceMappingURL=standards-sdk.es21.js.map