@hashgraphonline/standards-sdk 0.0.101 → 0.0.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/services/mirror-node.d.ts +102 -1
- package/dist/es/services/mirror-node.d.ts.map +1 -1
- package/dist/es/services/types.d.ts +53 -0
- package/dist/es/services/types.d.ts.map +1 -1
- package/dist/es/standards-sdk.es100.js +8 -62
- package/dist/es/standards-sdk.es100.js.map +1 -1
- package/dist/es/standards-sdk.es101.js +18 -6
- package/dist/es/standards-sdk.es101.js.map +1 -1
- package/dist/es/standards-sdk.es102.js +5 -14
- package/dist/es/standards-sdk.es102.js.map +1 -1
- package/dist/es/standards-sdk.es103.js +9 -7
- package/dist/es/standards-sdk.es103.js.map +1 -1
- package/dist/es/standards-sdk.es104.js +12 -44
- package/dist/es/standards-sdk.es104.js.map +1 -1
- package/dist/es/standards-sdk.es105.js +13 -2
- package/dist/es/standards-sdk.es105.js.map +1 -1
- package/dist/es/standards-sdk.es106.js +30 -134
- package/dist/es/standards-sdk.es106.js.map +1 -1
- package/dist/es/standards-sdk.es107.js +14 -171
- package/dist/es/standards-sdk.es107.js.map +1 -1
- package/dist/es/standards-sdk.es108.js +4 -13
- package/dist/es/standards-sdk.es108.js.map +1 -1
- package/dist/es/standards-sdk.es109.js +4 -13
- package/dist/es/standards-sdk.es109.js.map +1 -1
- package/dist/es/standards-sdk.es110.js +15 -31
- package/dist/es/standards-sdk.es110.js.map +1 -1
- package/dist/es/standards-sdk.es111.js +4 -14
- package/dist/es/standards-sdk.es111.js.map +1 -1
- package/dist/es/standards-sdk.es112.js +39 -4
- package/dist/es/standards-sdk.es112.js.map +1 -1
- package/dist/es/standards-sdk.es113.js +40 -4
- package/dist/es/standards-sdk.es113.js.map +1 -1
- package/dist/es/standards-sdk.es114.js +36 -15
- package/dist/es/standards-sdk.es114.js.map +1 -1
- package/dist/es/standards-sdk.es115.js +78 -5
- package/dist/es/standards-sdk.es115.js.map +1 -1
- package/dist/es/standards-sdk.es116.js +418 -38
- package/dist/es/standards-sdk.es116.js.map +1 -1
- package/dist/es/standards-sdk.es117.js +2 -40
- package/dist/es/standards-sdk.es117.js.map +1 -1
- package/dist/es/standards-sdk.es118.js +117 -34
- package/dist/es/standards-sdk.es118.js.map +1 -1
- package/dist/es/standards-sdk.es119.js +3 -78
- package/dist/es/standards-sdk.es119.js.map +1 -1
- package/dist/es/standards-sdk.es120.js +2 -3
- package/dist/es/standards-sdk.es120.js.map +1 -1
- package/dist/es/standards-sdk.es121.js +2 -2
- package/dist/es/standards-sdk.es121.js.map +1 -1
- package/dist/es/standards-sdk.es122.js +34 -2
- package/dist/es/standards-sdk.es122.js.map +1 -1
- package/dist/es/standards-sdk.es123.js +29 -30
- package/dist/es/standards-sdk.es123.js.map +1 -1
- package/dist/es/standards-sdk.es124.js +9 -33
- package/dist/es/standards-sdk.es124.js.map +1 -1
- package/dist/es/standards-sdk.es125.js +34 -9
- package/dist/es/standards-sdk.es125.js.map +1 -1
- package/dist/es/standards-sdk.es126.js +4 -34
- package/dist/es/standards-sdk.es126.js.map +1 -1
- package/dist/es/standards-sdk.es13.js +2 -2
- package/dist/es/standards-sdk.es18.js +2 -2
- package/dist/es/standards-sdk.es19.js.map +1 -1
- package/dist/es/standards-sdk.es21.js +505 -48
- package/dist/es/standards-sdk.es21.js.map +1 -1
- package/dist/es/standards-sdk.es24.js +10 -10
- package/dist/es/standards-sdk.es26.js +36 -3
- package/dist/es/standards-sdk.es26.js.map +1 -1
- package/dist/es/standards-sdk.es27.js +48 -4168
- package/dist/es/standards-sdk.es27.js.map +1 -1
- package/dist/es/standards-sdk.es28.js +1 -1
- package/dist/es/standards-sdk.es29.js +3 -9
- package/dist/es/standards-sdk.es29.js.map +1 -1
- package/dist/es/standards-sdk.es30.js +9 -2
- package/dist/es/standards-sdk.es30.js.map +1 -1
- package/dist/es/standards-sdk.es31.js +2 -36
- package/dist/es/standards-sdk.es31.js.map +1 -1
- package/dist/es/standards-sdk.es32.js +4168 -48
- package/dist/es/standards-sdk.es32.js.map +1 -1
- package/dist/es/standards-sdk.es33.js +2 -2
- package/dist/es/standards-sdk.es34.js +1 -1
- package/dist/es/standards-sdk.es35.js +2 -101
- package/dist/es/standards-sdk.es35.js.map +1 -1
- package/dist/es/standards-sdk.es36.js +84 -2
- package/dist/es/standards-sdk.es36.js.map +1 -1
- package/dist/es/standards-sdk.es37.js +22 -15
- package/dist/es/standards-sdk.es37.js.map +1 -1
- package/dist/es/standards-sdk.es38.js +138 -399
- package/dist/es/standards-sdk.es38.js.map +1 -1
- package/dist/es/standards-sdk.es39.js +91 -2
- package/dist/es/standards-sdk.es39.js.map +1 -1
- package/dist/es/standards-sdk.es40.js +174 -2282
- package/dist/es/standards-sdk.es40.js.map +1 -1
- package/dist/es/standards-sdk.es41.js +5 -163
- package/dist/es/standards-sdk.es41.js.map +1 -1
- package/dist/es/standards-sdk.es42.js +130 -71
- package/dist/es/standards-sdk.es42.js.map +1 -1
- package/dist/es/standards-sdk.es43.js +190 -18
- package/dist/es/standards-sdk.es43.js.map +1 -1
- package/dist/es/standards-sdk.es44.js +417 -127
- package/dist/es/standards-sdk.es44.js.map +1 -1
- package/dist/es/standards-sdk.es45.js +21 -87
- package/dist/es/standards-sdk.es45.js.map +1 -1
- package/dist/es/standards-sdk.es46.js +125 -181
- package/dist/es/standards-sdk.es46.js.map +1 -1
- package/dist/es/standards-sdk.es47.js +13 -433
- package/dist/es/standards-sdk.es47.js.map +1 -1
- package/dist/es/standards-sdk.es48.js +22 -18
- package/dist/es/standards-sdk.es48.js.map +1 -1
- package/dist/es/standards-sdk.es49.js +17 -127
- package/dist/es/standards-sdk.es49.js.map +1 -1
- package/dist/es/standards-sdk.es50.js +13 -10
- package/dist/es/standards-sdk.es50.js.map +1 -1
- package/dist/es/standards-sdk.es51.js +37 -22
- package/dist/es/standards-sdk.es51.js.map +1 -1
- package/dist/es/standards-sdk.es52.js +10 -21
- package/dist/es/standards-sdk.es52.js.map +1 -1
- package/dist/es/standards-sdk.es53.js +54 -14
- package/dist/es/standards-sdk.es53.js.map +1 -1
- package/dist/es/standards-sdk.es54.js +1284 -39
- package/dist/es/standards-sdk.es54.js.map +1 -1
- package/dist/es/standards-sdk.es55.js +29 -15
- package/dist/es/standards-sdk.es55.js.map +1 -1
- package/dist/es/standards-sdk.es56.js +149 -51
- package/dist/es/standards-sdk.es56.js.map +1 -1
- package/dist/es/standards-sdk.es57.js +629 -1125
- package/dist/es/standards-sdk.es57.js.map +1 -1
- package/dist/es/standards-sdk.es58.js +360 -28
- package/dist/es/standards-sdk.es58.js.map +1 -1
- package/dist/es/standards-sdk.es59.js +5 -5
- package/dist/es/standards-sdk.es59.js.map +1 -1
- package/dist/es/standards-sdk.es60.js +167 -147
- package/dist/es/standards-sdk.es60.js.map +1 -1
- package/dist/es/standards-sdk.es61.js +71 -783
- package/dist/es/standards-sdk.es61.js.map +1 -1
- package/dist/es/standards-sdk.es62.js +105 -165
- package/dist/es/standards-sdk.es62.js.map +1 -1
- package/dist/es/standards-sdk.es63.js +41 -132
- package/dist/es/standards-sdk.es63.js.map +1 -1
- package/dist/es/standards-sdk.es64.js +9 -20
- package/dist/es/standards-sdk.es64.js.map +1 -1
- package/dist/es/standards-sdk.es65.js +95 -25
- package/dist/es/standards-sdk.es65.js.map +1 -1
- package/dist/es/standards-sdk.es66.js +3 -25
- package/dist/es/standards-sdk.es66.js.map +1 -1
- package/dist/es/standards-sdk.es67.js +2 -38
- package/dist/es/standards-sdk.es67.js.map +1 -1
- package/dist/es/standards-sdk.es68.js +113 -3
- package/dist/es/standards-sdk.es68.js.map +1 -1
- package/dist/es/standards-sdk.es69.js +73 -3
- package/dist/es/standards-sdk.es69.js.map +1 -1
- package/dist/es/standards-sdk.es7.js +2 -2
- package/dist/es/standards-sdk.es70.js +4 -7134
- package/dist/es/standards-sdk.es70.js.map +1 -1
- package/dist/es/standards-sdk.es71.js +4 -9
- package/dist/es/standards-sdk.es71.js.map +1 -1
- package/dist/es/standards-sdk.es72.js +218 -8
- package/dist/es/standards-sdk.es72.js.map +1 -1
- package/dist/es/standards-sdk.es73.js +56 -16
- package/dist/es/standards-sdk.es73.js.map +1 -1
- package/dist/es/standards-sdk.es74.js +69 -6
- package/dist/es/standards-sdk.es74.js.map +1 -1
- package/dist/es/standards-sdk.es75.js +100 -8
- package/dist/es/standards-sdk.es75.js.map +1 -1
- package/dist/es/standards-sdk.es76.js +2 -419
- package/dist/es/standards-sdk.es76.js.map +1 -1
- package/dist/es/standards-sdk.es77.js +16 -2
- package/dist/es/standards-sdk.es77.js.map +1 -1
- package/dist/es/standards-sdk.es78.js +395 -106
- package/dist/es/standards-sdk.es78.js.map +1 -1
- package/dist/es/standards-sdk.es79.js +22 -359
- package/dist/es/standards-sdk.es79.js.map +1 -1
- package/dist/es/standards-sdk.es8.js +1 -1
- package/dist/es/standards-sdk.es80.js +25 -5
- package/dist/es/standards-sdk.es80.js.map +1 -1
- package/dist/es/standards-sdk.es81.js +36 -177
- package/dist/es/standards-sdk.es81.js.map +1 -1
- package/dist/es/standards-sdk.es82.js +16 -76
- package/dist/es/standards-sdk.es82.js.map +1 -1
- package/dist/es/standards-sdk.es83.js +2281 -113
- package/dist/es/standards-sdk.es83.js.map +1 -1
- package/dist/es/standards-sdk.es84.js +158 -46
- package/dist/es/standards-sdk.es84.js.map +1 -1
- package/dist/es/standards-sdk.es85.js +3 -10
- package/dist/es/standards-sdk.es85.js.map +1 -1
- package/dist/es/standards-sdk.es86.js +26 -91
- package/dist/es/standards-sdk.es86.js.map +1 -1
- package/dist/es/standards-sdk.es87.js +62 -3
- package/dist/es/standards-sdk.es87.js.map +1 -1
- package/dist/es/standards-sdk.es88.js +50 -2
- package/dist/es/standards-sdk.es88.js.map +1 -1
- package/dist/es/standards-sdk.es89.js +8 -111
- package/dist/es/standards-sdk.es89.js.map +1 -1
- package/dist/es/standards-sdk.es9.js +1 -1
- package/dist/es/standards-sdk.es90.js +61 -70
- package/dist/es/standards-sdk.es90.js.map +1 -1
- package/dist/es/standards-sdk.es91.js +6 -6
- package/dist/es/standards-sdk.es91.js.map +1 -1
- package/dist/es/standards-sdk.es92.js +14 -4
- package/dist/es/standards-sdk.es92.js.map +1 -1
- package/dist/es/standards-sdk.es93.js +7 -221
- package/dist/es/standards-sdk.es93.js.map +1 -1
- package/dist/es/standards-sdk.es94.js +41 -54
- package/dist/es/standards-sdk.es94.js.map +1 -1
- package/dist/es/standards-sdk.es95.js +2 -69
- package/dist/es/standards-sdk.es95.js.map +1 -1
- package/dist/es/standards-sdk.es96.js +135 -31
- package/dist/es/standards-sdk.es96.js.map +1 -1
- package/dist/es/standards-sdk.es97.js +164 -55
- package/dist/es/standards-sdk.es97.js.map +1 -1
- package/dist/es/standards-sdk.es98.js +7131 -45
- package/dist/es/standards-sdk.es98.js.map +1 -1
- package/dist/es/standards-sdk.es99.js +9 -9
- package/dist/es/standards-sdk.es99.js.map +1 -1
- package/dist/umd/services/mirror-node.d.ts +102 -1
- package/dist/umd/services/mirror-node.d.ts.map +1 -1
- package/dist/umd/services/types.d.ts +53 -0
- package/dist/umd/services/types.d.ts.map +1 -1
- package/dist/umd/standards-sdk.umd.js +12 -12
- package/dist/umd/standards-sdk.umd.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import Buffer from "./standards-sdk.es25.js";
|
|
2
|
-
import { PublicKey, Timestamp } from "@hashgraph/sdk";
|
|
3
|
-
import "./standards-sdk.
|
|
2
|
+
import { PublicKey, Timestamp, AccountId } from "@hashgraph/sdk";
|
|
3
|
+
import "./standards-sdk.es26.js";
|
|
4
4
|
import { proto } from "@hashgraph/proto";
|
|
5
|
-
import axios from "./standards-sdk.
|
|
5
|
+
import axios from "./standards-sdk.es27.js";
|
|
6
6
|
class HederaMirrorNode {
|
|
7
7
|
constructor(network, logger) {
|
|
8
|
+
this.maxRetries = 3;
|
|
9
|
+
this.initialDelayMs = 1e3;
|
|
10
|
+
this.maxDelayMs = 3e4;
|
|
11
|
+
this.backoffFactor = 2;
|
|
8
12
|
this.network = network;
|
|
9
13
|
this.baseUrl = this.getMirrorNodeUrl();
|
|
10
14
|
this.logger = logger;
|
|
11
15
|
this.isServerEnvironment = typeof window === "undefined";
|
|
12
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Configures the retry mechanism for API requests.
|
|
19
|
+
* @param config The retry configuration.
|
|
20
|
+
*/
|
|
21
|
+
configureRetry(config) {
|
|
22
|
+
this.maxRetries = config.maxRetries ?? this.maxRetries;
|
|
23
|
+
this.initialDelayMs = config.initialDelayMs ?? this.initialDelayMs;
|
|
24
|
+
this.maxDelayMs = config.maxDelayMs ?? this.maxDelayMs;
|
|
25
|
+
this.backoffFactor = config.backoffFactor ?? this.backoffFactor;
|
|
26
|
+
this.logger.info(
|
|
27
|
+
`Retry configuration updated: maxRetries=${this.maxRetries}, initialDelayMs=${this.initialDelayMs}, maxDelayMs=${this.maxDelayMs}, backoffFactor=${this.backoffFactor}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
13
30
|
getMirrorNodeUrl() {
|
|
14
31
|
return this.network === "mainnet" ? "https://mainnet-public.mirrornode.hedera.com" : "https://testnet.mirrornode.hedera.com";
|
|
15
32
|
}
|
|
@@ -46,29 +63,24 @@ class HederaMirrorNode {
|
|
|
46
63
|
* @throws An error if the account ID is invalid or the memo cannot be retrieved.
|
|
47
64
|
*/
|
|
48
65
|
async getAccountMemo(accountId) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
this.logger.error(`No memo found for account ${accountId}`);
|
|
59
|
-
if (attempt < maxRetries - 1) {
|
|
60
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
61
|
-
}
|
|
62
|
-
} catch (e) {
|
|
63
|
-
const error = e;
|
|
64
|
-
const logMessage = `Error getting account memo (attempt ${attempt + 1}): ${error.message}`;
|
|
65
|
-
this.logger.error(logMessage);
|
|
66
|
-
if (attempt < maxRetries - 1) {
|
|
67
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
68
|
-
}
|
|
66
|
+
this.logger.info(`Getting account memo for account ID: ${accountId}`);
|
|
67
|
+
const accountInfoUrl = `${this.baseUrl}/api/v1/accounts/${accountId}`;
|
|
68
|
+
try {
|
|
69
|
+
const accountInfo = await this._requestWithRetry(
|
|
70
|
+
accountInfoUrl
|
|
71
|
+
);
|
|
72
|
+
if (accountInfo && accountInfo.memo) {
|
|
73
|
+
return accountInfo.memo;
|
|
69
74
|
}
|
|
75
|
+
this.logger.warn(`No memo found for account ${accountId}`);
|
|
76
|
+
return null;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
const error = e;
|
|
79
|
+
this.logger.error(
|
|
80
|
+
`Failed to get account memo for ${accountId} after retries: ${error.message}`
|
|
81
|
+
);
|
|
82
|
+
return null;
|
|
70
83
|
}
|
|
71
|
-
return null;
|
|
72
84
|
}
|
|
73
85
|
/**
|
|
74
86
|
* Retrieves topic information for a given topic ID from the mirror node.
|
|
@@ -79,11 +91,12 @@ class HederaMirrorNode {
|
|
|
79
91
|
async getTopicInfo(topicId) {
|
|
80
92
|
try {
|
|
81
93
|
const topicInfoUrl = `${this.baseUrl}/api/v1/topics/${topicId}`;
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
this.logger.debug(`Fetching topic info from ${topicInfoUrl}`);
|
|
95
|
+
const data = await this._requestWithRetry(topicInfoUrl);
|
|
96
|
+
return data;
|
|
84
97
|
} catch (e) {
|
|
85
98
|
const error = e;
|
|
86
|
-
const logMessage = `Error retrieving topic information: ${error.message}`;
|
|
99
|
+
const logMessage = `Error retrieving topic information for ${topicId} after retries: ${error.message}`;
|
|
87
100
|
this.logger.error(logMessage);
|
|
88
101
|
throw new Error(logMessage);
|
|
89
102
|
}
|
|
@@ -114,10 +127,9 @@ class HederaMirrorNode {
|
|
|
114
127
|
async getHBARPrice(date) {
|
|
115
128
|
try {
|
|
116
129
|
const timestamp = Timestamp.fromDate(date).toString();
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
);
|
|
120
|
-
const response = await request.json();
|
|
130
|
+
const url = `https://mainnet-public.mirrornode.hedera.com/api/v1/network/exchangerate?timestamp=${timestamp}`;
|
|
131
|
+
this.logger.debug(`Fetching HBAR price from ${url}`);
|
|
132
|
+
const response = await this._fetchWithRetry(url);
|
|
121
133
|
const usdPrice = Number(response?.current_rate?.cent_equivalent) / Number(response?.current_rate?.hbar_equivalent) / 100;
|
|
122
134
|
return usdPrice;
|
|
123
135
|
} catch (e) {
|
|
@@ -137,10 +149,12 @@ class HederaMirrorNode {
|
|
|
137
149
|
this.logger.debug(`Fetching token info for ${tokenId}`);
|
|
138
150
|
try {
|
|
139
151
|
const tokenInfoUrl = `${this.baseUrl}/api/v1/tokens/${tokenId}`;
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
const data = await this._requestWithRetry(
|
|
153
|
+
tokenInfoUrl
|
|
154
|
+
);
|
|
155
|
+
if (data) {
|
|
156
|
+
this.logger.trace(`Token info found for ${tokenId}:`, data);
|
|
157
|
+
return data;
|
|
144
158
|
}
|
|
145
159
|
this.logger.warn(`No token info found for ${tokenId}`);
|
|
146
160
|
return null;
|
|
@@ -163,8 +177,9 @@ class HederaMirrorNode {
|
|
|
163
177
|
const messages = [];
|
|
164
178
|
while (nextUrl) {
|
|
165
179
|
try {
|
|
166
|
-
const
|
|
167
|
-
|
|
180
|
+
const data = await this._requestWithRetry(
|
|
181
|
+
nextUrl
|
|
182
|
+
);
|
|
168
183
|
if (data.messages && data.messages.length > 0) {
|
|
169
184
|
for (const message of data.messages) {
|
|
170
185
|
try {
|
|
@@ -197,7 +212,7 @@ class HederaMirrorNode {
|
|
|
197
212
|
} catch (error) {
|
|
198
213
|
const logMessage = `Invalid JSON message content: ${messageContent}`;
|
|
199
214
|
this.logger.error(logMessage);
|
|
200
|
-
|
|
215
|
+
continue;
|
|
201
216
|
}
|
|
202
217
|
messageJson.sequence_number = message.sequence_number;
|
|
203
218
|
messages.push({
|
|
@@ -215,7 +230,7 @@ class HederaMirrorNode {
|
|
|
215
230
|
nextUrl = data.links?.next ? `${this.baseUrl}${data.links.next}` : "";
|
|
216
231
|
} catch (e) {
|
|
217
232
|
const error = e;
|
|
218
|
-
const logMessage = `Error querying topic messages: ${
|
|
233
|
+
const logMessage = `Error querying topic messages for topic ${topicId} (URL: ${nextUrl}) after retries: ${error.message}`;
|
|
219
234
|
this.logger.error(logMessage);
|
|
220
235
|
throw new Error(logMessage);
|
|
221
236
|
}
|
|
@@ -231,16 +246,19 @@ class HederaMirrorNode {
|
|
|
231
246
|
async requestAccount(accountId) {
|
|
232
247
|
try {
|
|
233
248
|
const accountInfoUrl = `${this.baseUrl}/api/v1/accounts/${accountId}`;
|
|
234
|
-
|
|
235
|
-
|
|
249
|
+
this.logger.debug(`Requesting account info from ${accountInfoUrl}`);
|
|
250
|
+
const data = await this._requestWithRetry(
|
|
251
|
+
accountInfoUrl
|
|
252
|
+
);
|
|
253
|
+
if (!data) {
|
|
236
254
|
throw new Error(
|
|
237
|
-
`
|
|
255
|
+
`No data received from mirror node for account: ${accountId}`
|
|
238
256
|
);
|
|
239
257
|
}
|
|
240
|
-
return
|
|
258
|
+
return data;
|
|
241
259
|
} catch (e) {
|
|
242
260
|
const error = e;
|
|
243
|
-
const logMessage = `Failed to fetch account: ${error.message}`;
|
|
261
|
+
const logMessage = `Failed to fetch account ${accountId} after retries: ${error.message}`;
|
|
244
262
|
this.logger.error(logMessage);
|
|
245
263
|
throw new Error(logMessage);
|
|
246
264
|
}
|
|
@@ -344,13 +362,18 @@ class HederaMirrorNode {
|
|
|
344
362
|
`Getting information for scheduled transaction ${scheduleId}`
|
|
345
363
|
);
|
|
346
364
|
const url = `${this.baseUrl}/api/v1/schedules/${scheduleId}`;
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
349
|
-
return
|
|
365
|
+
const data = await this._requestWithRetry(url);
|
|
366
|
+
if (data) {
|
|
367
|
+
return data;
|
|
350
368
|
}
|
|
369
|
+
this.logger.warn(
|
|
370
|
+
`No schedule info found for ${scheduleId} after retries.`
|
|
371
|
+
);
|
|
351
372
|
return null;
|
|
352
373
|
} catch (error) {
|
|
353
|
-
this.logger.error(
|
|
374
|
+
this.logger.error(
|
|
375
|
+
`Error fetching schedule info for ${scheduleId} after retries: ${error.message}`
|
|
376
|
+
);
|
|
354
377
|
return null;
|
|
355
378
|
}
|
|
356
379
|
}
|
|
@@ -380,6 +403,440 @@ class HederaMirrorNode {
|
|
|
380
403
|
throw error;
|
|
381
404
|
}
|
|
382
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Retrieves details for a given transaction ID or hash from the mirror node.
|
|
408
|
+
* @param transactionIdOrHash The ID or hash of the transaction.
|
|
409
|
+
* @returns A promise that resolves to the transaction details.
|
|
410
|
+
* @throws An error if the transaction ID/hash is invalid or details cannot be retrieved.
|
|
411
|
+
*/
|
|
412
|
+
async getTransaction(transactionIdOrHash) {
|
|
413
|
+
this.logger.info(
|
|
414
|
+
`Getting transaction details for ID/hash: ${transactionIdOrHash}`
|
|
415
|
+
);
|
|
416
|
+
const endpoint = transactionIdOrHash.includes("-") ? `transactions/${transactionIdOrHash}` : `transactions/${transactionIdOrHash}`;
|
|
417
|
+
const transactionDetailsUrl = `${this.baseUrl}/api/v1/${endpoint}`;
|
|
418
|
+
try {
|
|
419
|
+
const response = await this._requestWithRetry(transactionDetailsUrl);
|
|
420
|
+
if (response?.transactions?.length > 0) {
|
|
421
|
+
this.logger.trace(
|
|
422
|
+
`Transaction details found for ${transactionIdOrHash}:`,
|
|
423
|
+
response.transactions[0]
|
|
424
|
+
);
|
|
425
|
+
return response.transactions[0];
|
|
426
|
+
}
|
|
427
|
+
this.logger.warn(
|
|
428
|
+
`No transaction details found for ${transactionIdOrHash} or unexpected response structure.`
|
|
429
|
+
);
|
|
430
|
+
return null;
|
|
431
|
+
} catch (e) {
|
|
432
|
+
const error = e;
|
|
433
|
+
this.logger.error(
|
|
434
|
+
`Failed to get transaction details for ${transactionIdOrHash} after retries: ${error.message}`
|
|
435
|
+
);
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Private helper to make GET requests with retry logic using Axios.
|
|
441
|
+
*/
|
|
442
|
+
async _requestWithRetry(url, axiosConfig) {
|
|
443
|
+
let attempt = 0;
|
|
444
|
+
let delay = this.initialDelayMs;
|
|
445
|
+
while (attempt < this.maxRetries) {
|
|
446
|
+
try {
|
|
447
|
+
const response = await axios.get(url, axiosConfig);
|
|
448
|
+
return response.data;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
attempt++;
|
|
451
|
+
const isLastAttempt = attempt >= this.maxRetries;
|
|
452
|
+
const statusCode = error.response?.status;
|
|
453
|
+
if (statusCode && statusCode >= 400 && statusCode < 500 && statusCode !== 429) {
|
|
454
|
+
this.logger.error(
|
|
455
|
+
`Client error for ${url} (status ${statusCode}): ${error.message}. Not retrying.`
|
|
456
|
+
);
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
if (isLastAttempt) {
|
|
460
|
+
this.logger.error(
|
|
461
|
+
`Max retries (${this.maxRetries}) reached for ${url}. Last error: ${error.message}`
|
|
462
|
+
);
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
this.logger.warn(
|
|
466
|
+
`Attempt ${attempt}/${this.maxRetries} failed for ${url}: ${error.message}. Retrying in ${delay}ms...`
|
|
467
|
+
);
|
|
468
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
469
|
+
delay = Math.min(delay * this.backoffFactor, this.maxDelayMs);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
throw new Error(
|
|
473
|
+
`Failed to fetch data from ${url} after ${this.maxRetries} attempts.`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Private helper to make fetch requests with retry logic.
|
|
478
|
+
*/
|
|
479
|
+
async _fetchWithRetry(url, fetchOptions) {
|
|
480
|
+
let attempt = 0;
|
|
481
|
+
let delay = this.initialDelayMs;
|
|
482
|
+
while (attempt < this.maxRetries) {
|
|
483
|
+
try {
|
|
484
|
+
const request = await fetch(url, fetchOptions);
|
|
485
|
+
if (!request.ok) {
|
|
486
|
+
if (request.status >= 400 && request.status < 500 && request.status !== 429) {
|
|
487
|
+
this.logger.error(
|
|
488
|
+
`Client error for ${url} (status ${request.status}): ${request.statusText}. Not retrying.`
|
|
489
|
+
);
|
|
490
|
+
throw new Error(
|
|
491
|
+
`Fetch failed with status ${request.status}: ${request.statusText} for URL: ${url}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
throw new Error(
|
|
495
|
+
`Fetch failed with status ${request.status}: ${request.statusText} for URL: ${url}`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
const response = await request.json();
|
|
499
|
+
return response;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
attempt++;
|
|
502
|
+
if (attempt >= this.maxRetries) {
|
|
503
|
+
this.logger.error(
|
|
504
|
+
`Max retries (${this.maxRetries}) reached for ${url}. Last error: ${error.message}`
|
|
505
|
+
);
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
this.logger.warn(
|
|
509
|
+
`Attempt ${attempt}/${this.maxRetries} failed for ${url}: ${error.message}. Retrying in ${delay}ms...`
|
|
510
|
+
);
|
|
511
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
512
|
+
delay = Math.min(delay * this.backoffFactor, this.maxDelayMs);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
throw new Error(
|
|
516
|
+
`Failed to fetch data from ${url} after ${this.maxRetries} attempts.`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Retrieves the numerical balance (in HBAR) for a given account ID.
|
|
521
|
+
* @param accountId The ID of the account.
|
|
522
|
+
* @returns A promise that resolves to the HBAR balance or null if an error occurs.
|
|
523
|
+
*/
|
|
524
|
+
async getAccountBalanceNumerical(accountId) {
|
|
525
|
+
this.logger.info(`Getting numerical balance for account ${accountId}`);
|
|
526
|
+
try {
|
|
527
|
+
const accountInfo = await this.requestAccount(accountId);
|
|
528
|
+
if (accountInfo && accountInfo.balance) {
|
|
529
|
+
const hbarBalance = accountInfo.balance.balance / 1e8;
|
|
530
|
+
return hbarBalance;
|
|
531
|
+
}
|
|
532
|
+
this.logger.warn(
|
|
533
|
+
`Could not retrieve balance for account ${accountId} from account info.`
|
|
534
|
+
);
|
|
535
|
+
return null;
|
|
536
|
+
} catch (error) {
|
|
537
|
+
this.logger.error(
|
|
538
|
+
`Error fetching numerical balance for account ${accountId}: ${error.message}`
|
|
539
|
+
);
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Retrieves messages for a given topic ID with optional filters.
|
|
545
|
+
* @param topicId The ID of the topic.
|
|
546
|
+
* @param sequenceNumber Filter by sequence number (e.g., "gt:10", "lte:20").
|
|
547
|
+
* @param startTime Filter by consensus timestamp (e.g., "gt:1629400000.000000000").
|
|
548
|
+
* @param endTime Filter by consensus timestamp (e.g., "lt:1629500000.000000000").
|
|
549
|
+
* @param limit The maximum number of messages to return.
|
|
550
|
+
* @returns A promise that resolves to an array of HCSMessages or null.
|
|
551
|
+
*/
|
|
552
|
+
async getTopicMessagesByFilter(topicId, options) {
|
|
553
|
+
this.logger.trace(
|
|
554
|
+
`Querying messages for topic ${topicId} with filters: ${JSON.stringify(
|
|
555
|
+
options
|
|
556
|
+
)}`
|
|
557
|
+
);
|
|
558
|
+
let nextUrl = `${this.baseUrl}/api/v1/topics/${topicId}/messages`;
|
|
559
|
+
const params = new URLSearchParams();
|
|
560
|
+
if (options?.limit) {
|
|
561
|
+
params.append("limit", options.limit.toString());
|
|
562
|
+
}
|
|
563
|
+
if (options?.sequenceNumber) {
|
|
564
|
+
params.append("sequencenumber", options.sequenceNumber);
|
|
565
|
+
}
|
|
566
|
+
if (options?.startTime) {
|
|
567
|
+
params.append("timestamp", `gte:${options.startTime}`);
|
|
568
|
+
}
|
|
569
|
+
if (options?.endTime) {
|
|
570
|
+
params.append("timestamp", `lt:${options.endTime}`);
|
|
571
|
+
}
|
|
572
|
+
if (options?.order) {
|
|
573
|
+
params.append("order", options.order);
|
|
574
|
+
}
|
|
575
|
+
const queryString = params.toString();
|
|
576
|
+
if (queryString) {
|
|
577
|
+
nextUrl += `?${queryString}`;
|
|
578
|
+
}
|
|
579
|
+
const messages = [];
|
|
580
|
+
let pagesFetched = 0;
|
|
581
|
+
const maxPages = 10;
|
|
582
|
+
try {
|
|
583
|
+
while (nextUrl && pagesFetched < maxPages) {
|
|
584
|
+
pagesFetched++;
|
|
585
|
+
const data = await this._requestWithRetry(
|
|
586
|
+
nextUrl
|
|
587
|
+
);
|
|
588
|
+
if (data.messages && data.messages.length > 0) {
|
|
589
|
+
for (const message of data.messages) {
|
|
590
|
+
try {
|
|
591
|
+
if (!message.message) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
let messageContent;
|
|
595
|
+
if (this.isServerEnvironment) {
|
|
596
|
+
messageContent = Buffer.from(
|
|
597
|
+
message.message,
|
|
598
|
+
"base64"
|
|
599
|
+
).toString("utf-8");
|
|
600
|
+
} else {
|
|
601
|
+
messageContent = new TextDecoder().decode(
|
|
602
|
+
Uint8Array.from(atob(message.message), (c) => c.charCodeAt(0))
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
let messageJson = {};
|
|
606
|
+
try {
|
|
607
|
+
messageJson = JSON.parse(messageContent);
|
|
608
|
+
} catch (parseError) {
|
|
609
|
+
this.logger.debug(
|
|
610
|
+
`Message content is not valid JSON, using raw: ${messageContent}`
|
|
611
|
+
);
|
|
612
|
+
messageJson = { raw_content: messageContent };
|
|
613
|
+
}
|
|
614
|
+
const parsedContent = messageJson;
|
|
615
|
+
const hcsMsg = {
|
|
616
|
+
...parsedContent,
|
|
617
|
+
consensus_timestamp: message.consensus_timestamp,
|
|
618
|
+
sequence_number: message.sequence_number,
|
|
619
|
+
payer_account_id: message.payer_account_id,
|
|
620
|
+
topic_id: message.topic_id,
|
|
621
|
+
running_hash: message.running_hash,
|
|
622
|
+
running_hash_version: message.running_hash_version,
|
|
623
|
+
chunk_info: message.chunk_info,
|
|
624
|
+
created: new Date(
|
|
625
|
+
Number(message.consensus_timestamp.split(".")[0]) * 1e3 + Number(message.consensus_timestamp.split(".")[1] || 0) / 1e6
|
|
626
|
+
),
|
|
627
|
+
payer: message.payer_account_id
|
|
628
|
+
};
|
|
629
|
+
messages.push(hcsMsg);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
this.logger.error(
|
|
632
|
+
`Error processing individual message: ${error.message}`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (options?.limit && messages.length >= options.limit) break;
|
|
638
|
+
nextUrl = data.links?.next ? `${this.baseUrl}${data.links.next}` : "";
|
|
639
|
+
}
|
|
640
|
+
return messages;
|
|
641
|
+
} catch (e) {
|
|
642
|
+
const error = e;
|
|
643
|
+
this.logger.error(
|
|
644
|
+
`Error querying filtered topic messages for ${topicId}: ${error.message}`
|
|
645
|
+
);
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Retrieves token balances for a given account ID.
|
|
651
|
+
* @param accountId The ID of the account.
|
|
652
|
+
* @param limit The maximum number of tokens to return.
|
|
653
|
+
* @returns A promise that resolves to an array of AccountTokenBalance or null.
|
|
654
|
+
*/
|
|
655
|
+
async getAccountTokens(accountId, limit = 100) {
|
|
656
|
+
this.logger.info(`Getting tokens for account ${accountId}`);
|
|
657
|
+
let allTokens = [];
|
|
658
|
+
let url = `${this.baseUrl}/api/v1/accounts/${accountId}/tokens?limit=${limit}`;
|
|
659
|
+
try {
|
|
660
|
+
for (let i = 0; i < 10 && url; i++) {
|
|
661
|
+
const response = await this._requestWithRetry(
|
|
662
|
+
url
|
|
663
|
+
);
|
|
664
|
+
if (response && response.tokens) {
|
|
665
|
+
allTokens = allTokens.concat(response.tokens);
|
|
666
|
+
}
|
|
667
|
+
url = response.links?.next ? `${this.baseUrl}${response.links.next}` : "";
|
|
668
|
+
if (!url || limit && allTokens.length >= limit) {
|
|
669
|
+
if (limit && allTokens.length > limit) {
|
|
670
|
+
allTokens = allTokens.slice(0, limit);
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return allTokens;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
this.logger.error(
|
|
678
|
+
`Error fetching tokens for account ${accountId}: ${error.message}`
|
|
679
|
+
);
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Retrieves transaction details by consensus timestamp.
|
|
685
|
+
* @param timestamp The consensus timestamp of the transaction (e.g., "1629400000.000000000").
|
|
686
|
+
* @returns A promise that resolves to the transaction details or null.
|
|
687
|
+
*/
|
|
688
|
+
async getTransactionByTimestamp(timestamp) {
|
|
689
|
+
this.logger.info(`Getting transaction by timestamp: ${timestamp}`);
|
|
690
|
+
const url = `${this.baseUrl}/api/v1/transactions?timestamp=${timestamp}&limit=1`;
|
|
691
|
+
try {
|
|
692
|
+
const response = await this._requestWithRetry(url);
|
|
693
|
+
if (response && response.transactions && response.transactions.length > 0) {
|
|
694
|
+
const specificTransactionId = response.transactions[0].transaction_id;
|
|
695
|
+
this.logger.debug(
|
|
696
|
+
`Transaction found by timestamp, fetching full details for ID: ${specificTransactionId}`
|
|
697
|
+
);
|
|
698
|
+
return this.getTransaction(specificTransactionId);
|
|
699
|
+
}
|
|
700
|
+
this.logger.warn(`No transaction found for timestamp: ${timestamp}`);
|
|
701
|
+
return null;
|
|
702
|
+
} catch (error) {
|
|
703
|
+
this.logger.error(
|
|
704
|
+
`Error fetching transaction by timestamp ${timestamp}: ${error.message}`
|
|
705
|
+
);
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Retrieves NFTs for a given account ID, optionally filtered by token ID.
|
|
711
|
+
* @param accountId The ID of the account.
|
|
712
|
+
* @param tokenId Optional ID of the token to filter NFTs by.
|
|
713
|
+
* @param limit The maximum number of NFTs to return per page (API has its own max).
|
|
714
|
+
* @returns A promise that resolves to an array of NftDetail or null.
|
|
715
|
+
*/
|
|
716
|
+
async getAccountNfts(accountId, tokenId, limit = 100) {
|
|
717
|
+
this.logger.info(
|
|
718
|
+
`Getting NFTs for account ${accountId}${tokenId ? ` for token ${tokenId}` : ""}`
|
|
719
|
+
);
|
|
720
|
+
let allNfts = [];
|
|
721
|
+
let url = `${this.baseUrl}/api/v1/accounts/${accountId}/nfts?limit=${limit}`;
|
|
722
|
+
if (tokenId) {
|
|
723
|
+
url += `&token.id=${tokenId}`;
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
for (let i = 0; i < 10 && url; i++) {
|
|
727
|
+
const response = await this._requestWithRetry(url);
|
|
728
|
+
if (response && response.nfts) {
|
|
729
|
+
const nftsWithUri = response.nfts.map((nft) => {
|
|
730
|
+
let tokenUri = void 0;
|
|
731
|
+
if (nft.metadata) {
|
|
732
|
+
try {
|
|
733
|
+
if (this.isServerEnvironment) {
|
|
734
|
+
tokenUri = Buffer.from(nft.metadata, "base64").toString(
|
|
735
|
+
"utf-8"
|
|
736
|
+
);
|
|
737
|
+
} else {
|
|
738
|
+
tokenUri = new TextDecoder().decode(
|
|
739
|
+
Uint8Array.from(atob(nft.metadata), (c) => c.charCodeAt(0))
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
} catch (e) {
|
|
743
|
+
this.logger.warn(
|
|
744
|
+
`Failed to decode metadata for NFT ${nft.token_id} SN ${nft.serial_number}: ${e.message}`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return { ...nft, token_uri: tokenUri };
|
|
749
|
+
});
|
|
750
|
+
allNfts = allNfts.concat(nftsWithUri);
|
|
751
|
+
}
|
|
752
|
+
url = response.links?.next ? `${this.baseUrl}${response.links.next}` : "";
|
|
753
|
+
if (!url) break;
|
|
754
|
+
}
|
|
755
|
+
return allNfts;
|
|
756
|
+
} catch (error) {
|
|
757
|
+
this.logger.error(
|
|
758
|
+
`Error fetching NFTs for account ${accountId}: ${error.message}`
|
|
759
|
+
);
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Validates NFT ownership by checking if a specific serial number of a token ID exists for an account.
|
|
765
|
+
* @param accountId The ID of the account.
|
|
766
|
+
* @param tokenId The ID of the NFT's token.
|
|
767
|
+
* @param serialNumber The serial number of the NFT.
|
|
768
|
+
* @returns A promise that resolves to the NftDetail if owned, or null otherwise.
|
|
769
|
+
*/
|
|
770
|
+
async validateNFTOwnership(accountId, tokenId, serialNumber) {
|
|
771
|
+
this.logger.info(
|
|
772
|
+
`Validating ownership of NFT ${tokenId} SN ${serialNumber} for account ${accountId}`
|
|
773
|
+
);
|
|
774
|
+
try {
|
|
775
|
+
const nfts = await this.getAccountNfts(accountId, tokenId);
|
|
776
|
+
if (nfts) {
|
|
777
|
+
const foundNft = nfts.find(
|
|
778
|
+
(nft) => nft.token_id === tokenId && nft.serial_number === serialNumber
|
|
779
|
+
);
|
|
780
|
+
return foundNft || null;
|
|
781
|
+
}
|
|
782
|
+
return null;
|
|
783
|
+
} catch (error) {
|
|
784
|
+
this.logger.error(`Error validating NFT ownership: ${error.message}`);
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Performs a read-only query against a smart contract (eth_call like).
|
|
790
|
+
* @param contractIdOrAddress The contract ID (e.g., "0.0.123") or EVM address (e.g., "0x...").
|
|
791
|
+
* @param functionSelector The function selector and encoded parameters (e.g., "0xabcdef12...").
|
|
792
|
+
* @param payerAccountId The account ID of the payer (not strictly payer for read-only, but often required as 'from').
|
|
793
|
+
* @param estimate Whether this is an estimate call. Mirror node might not support this directly in /contracts/call for true estimation.
|
|
794
|
+
* @param block Block parameter, e.g., "latest", "pending", or block number.
|
|
795
|
+
* @param value The value in tinybars to send with the call (for payable view/pure functions, usually 0).
|
|
796
|
+
* @returns A promise that resolves to the contract call query response or null.
|
|
797
|
+
*/
|
|
798
|
+
async readSmartContractQuery(contractIdOrAddress, functionSelector, payerAccountId, options) {
|
|
799
|
+
this.logger.info(
|
|
800
|
+
`Reading smart contract ${contractIdOrAddress} with selector ${functionSelector}`
|
|
801
|
+
);
|
|
802
|
+
const url = `${this.baseUrl}/api/v1/contracts/call`;
|
|
803
|
+
const toAddress = contractIdOrAddress.startsWith("0x") ? contractIdOrAddress : `0x${AccountId.fromString(contractIdOrAddress).toSolidityAddress()}`;
|
|
804
|
+
const fromAddress = payerAccountId.startsWith("0x") ? payerAccountId : `0x${AccountId.fromString(payerAccountId).toSolidityAddress()}`;
|
|
805
|
+
const body = {
|
|
806
|
+
block: options?.block || "latest",
|
|
807
|
+
data: functionSelector,
|
|
808
|
+
estimate: options?.estimate || false,
|
|
809
|
+
from: fromAddress,
|
|
810
|
+
to: toAddress,
|
|
811
|
+
gas: options?.gas,
|
|
812
|
+
gasPrice: options?.gasPrice,
|
|
813
|
+
value: options?.value || 0
|
|
814
|
+
};
|
|
815
|
+
Object.keys(body).forEach((key) => {
|
|
816
|
+
const K = key;
|
|
817
|
+
if (body[K] === void 0) {
|
|
818
|
+
delete body[K];
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
try {
|
|
822
|
+
const response = await this._fetchWithRetry(
|
|
823
|
+
url,
|
|
824
|
+
{
|
|
825
|
+
method: "POST",
|
|
826
|
+
body: JSON.stringify(body),
|
|
827
|
+
headers: {
|
|
828
|
+
"Content-Type": "application/json"
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
);
|
|
832
|
+
return response;
|
|
833
|
+
} catch (error) {
|
|
834
|
+
this.logger.error(
|
|
835
|
+
`Error reading smart contract ${contractIdOrAddress}: ${error.message}`
|
|
836
|
+
);
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
383
840
|
}
|
|
384
841
|
export {
|
|
385
842
|
HederaMirrorNode
|