@hashgraphonline/standards-sdk 0.0.100 → 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.
Files changed (228) hide show
  1. package/dist/es/services/mirror-node.d.ts +102 -1
  2. package/dist/es/services/mirror-node.d.ts.map +1 -1
  3. package/dist/es/services/types.d.ts +53 -0
  4. package/dist/es/services/types.d.ts.map +1 -1
  5. package/dist/es/standards-sdk.es100.js +9 -14
  6. package/dist/es/standards-sdk.es100.js.map +1 -1
  7. package/dist/es/standards-sdk.es101.js +17 -4
  8. package/dist/es/standards-sdk.es101.js.map +1 -1
  9. package/dist/es/standards-sdk.es102.js +6 -39
  10. package/dist/es/standards-sdk.es102.js.map +1 -1
  11. package/dist/es/standards-sdk.es103.js +9 -40
  12. package/dist/es/standards-sdk.es103.js.map +1 -1
  13. package/dist/es/standards-sdk.es104.js +12 -36
  14. package/dist/es/standards-sdk.es104.js.map +1 -1
  15. package/dist/es/standards-sdk.es105.js +13 -78
  16. package/dist/es/standards-sdk.es105.js.map +1 -1
  17. package/dist/es/standards-sdk.es106.js +32 -3
  18. package/dist/es/standards-sdk.es106.js.map +1 -1
  19. package/dist/es/standards-sdk.es107.js +15 -2
  20. package/dist/es/standards-sdk.es107.js.map +1 -1
  21. package/dist/es/standards-sdk.es108.js +4 -2
  22. package/dist/es/standards-sdk.es108.js.map +1 -1
  23. package/dist/es/standards-sdk.es109.js +3 -20
  24. package/dist/es/standards-sdk.es109.js.map +1 -1
  25. package/dist/es/standards-sdk.es110.js +14 -25
  26. package/dist/es/standards-sdk.es110.js.map +1 -1
  27. package/dist/es/standards-sdk.es111.js +4 -25
  28. package/dist/es/standards-sdk.es111.js.map +1 -1
  29. package/dist/es/standards-sdk.es112.js +39 -38
  30. package/dist/es/standards-sdk.es112.js.map +1 -1
  31. package/dist/es/standards-sdk.es113.js +38 -32
  32. package/dist/es/standards-sdk.es113.js.map +1 -1
  33. package/dist/es/standards-sdk.es114.js +35 -31
  34. package/dist/es/standards-sdk.es114.js.map +1 -1
  35. package/dist/es/standards-sdk.es115.js +78 -9
  36. package/dist/es/standards-sdk.es115.js.map +1 -1
  37. package/dist/es/standards-sdk.es116.js +414 -29
  38. package/dist/es/standards-sdk.es116.js.map +1 -1
  39. package/dist/es/standards-sdk.es117.js +2 -101
  40. package/dist/es/standards-sdk.es117.js.map +1 -1
  41. package/dist/es/standards-sdk.es118.js +120 -2
  42. package/dist/es/standards-sdk.es118.js.map +1 -1
  43. package/dist/es/standards-sdk.es119.js +3 -16
  44. package/dist/es/standards-sdk.es119.js.map +1 -1
  45. package/dist/es/standards-sdk.es120.js +2 -409
  46. package/dist/es/standards-sdk.es120.js.map +1 -1
  47. package/dist/es/standards-sdk.es121.js +2 -2282
  48. package/dist/es/standards-sdk.es121.js.map +1 -1
  49. package/dist/es/standards-sdk.es122.js +33 -9
  50. package/dist/es/standards-sdk.es122.js.map +1 -1
  51. package/dist/es/standards-sdk.es123.js +32 -10
  52. package/dist/es/standards-sdk.es123.js.map +1 -1
  53. package/dist/es/standards-sdk.es124.js +9 -18
  54. package/dist/es/standards-sdk.es124.js.map +1 -1
  55. package/dist/es/standards-sdk.es125.js +33 -5
  56. package/dist/es/standards-sdk.es125.js.map +1 -1
  57. package/dist/es/standards-sdk.es126.js +3 -8
  58. package/dist/es/standards-sdk.es126.js.map +1 -1
  59. package/dist/es/standards-sdk.es13.js +3 -3
  60. package/dist/es/standards-sdk.es15.js +1 -1
  61. package/dist/es/standards-sdk.es19.js +83 -18
  62. package/dist/es/standards-sdk.es19.js.map +1 -1
  63. package/dist/es/standards-sdk.es20.js +2 -2
  64. package/dist/es/standards-sdk.es21.js +504 -47
  65. package/dist/es/standards-sdk.es21.js.map +1 -1
  66. package/dist/es/standards-sdk.es24.js +10 -10
  67. package/dist/es/standards-sdk.es25.js +1769 -5
  68. package/dist/es/standards-sdk.es25.js.map +1 -1
  69. package/dist/es/standards-sdk.es27.js +17 -17
  70. package/dist/es/standards-sdk.es28.js +5543 -122
  71. package/dist/es/standards-sdk.es28.js.map +1 -1
  72. package/dist/es/standards-sdk.es29.js +3 -10
  73. package/dist/es/standards-sdk.es29.js.map +1 -1
  74. package/dist/es/standards-sdk.es30.js +8 -7189
  75. package/dist/es/standards-sdk.es30.js.map +1 -1
  76. package/dist/es/standards-sdk.es31.js +2 -3
  77. package/dist/es/standards-sdk.es31.js.map +1 -1
  78. package/dist/es/standards-sdk.es32.js +4167 -8
  79. package/dist/es/standards-sdk.es32.js.map +1 -1
  80. package/dist/es/standards-sdk.es33.js +5 -2
  81. package/dist/es/standards-sdk.es33.js.map +1 -1
  82. package/dist/es/standards-sdk.es34.js +8 -4166
  83. package/dist/es/standards-sdk.es34.js.map +1 -1
  84. package/dist/es/standards-sdk.es35.js +2 -419
  85. package/dist/es/standards-sdk.es35.js.map +1 -1
  86. package/dist/es/standards-sdk.es36.js +84 -2
  87. package/dist/es/standards-sdk.es36.js.map +1 -1
  88. package/dist/es/standards-sdk.es37.js +22 -119
  89. package/dist/es/standards-sdk.es37.js.map +1 -1
  90. package/dist/es/standards-sdk.es38.js +127 -343
  91. package/dist/es/standards-sdk.es38.js.map +1 -1
  92. package/dist/es/standards-sdk.es39.js +90 -5
  93. package/dist/es/standards-sdk.es39.js.map +1 -1
  94. package/dist/es/standards-sdk.es40.js +164 -169
  95. package/dist/es/standards-sdk.es40.js.map +1 -1
  96. package/dist/es/standards-sdk.es41.js +5 -80
  97. package/dist/es/standards-sdk.es41.js.map +1 -1
  98. package/dist/es/standards-sdk.es42.js +129 -100
  99. package/dist/es/standards-sdk.es42.js.map +1 -1
  100. package/dist/es/standards-sdk.es43.js +190 -47
  101. package/dist/es/standards-sdk.es43.js.map +1 -1
  102. package/dist/es/standards-sdk.es44.js +438 -10
  103. package/dist/es/standards-sdk.es44.js.map +1 -1
  104. package/dist/es/standards-sdk.es45.js +19 -91
  105. package/dist/es/standards-sdk.es45.js.map +1 -1
  106. package/dist/es/standards-sdk.es46.js +138 -3
  107. package/dist/es/standards-sdk.es46.js.map +1 -1
  108. package/dist/es/standards-sdk.es47.js +18 -2
  109. package/dist/es/standards-sdk.es47.js.map +1 -1
  110. package/dist/es/standards-sdk.es48.js +22 -106
  111. package/dist/es/standards-sdk.es48.js.map +1 -1
  112. package/dist/es/standards-sdk.es49.js +26 -71
  113. package/dist/es/standards-sdk.es49.js.map +1 -1
  114. package/dist/es/standards-sdk.es5.js +1 -1
  115. package/dist/es/standards-sdk.es50.js +20 -5
  116. package/dist/es/standards-sdk.es50.js.map +1 -1
  117. package/dist/es/standards-sdk.es51.js +43 -4
  118. package/dist/es/standards-sdk.es51.js.map +1 -1
  119. package/dist/es/standards-sdk.es52.js +13 -216
  120. package/dist/es/standards-sdk.es52.js.map +1 -1
  121. package/dist/es/standards-sdk.es53.js +57 -54
  122. package/dist/es/standards-sdk.es53.js.map +1 -1
  123. package/dist/es/standards-sdk.es54.js +1287 -67
  124. package/dist/es/standards-sdk.es54.js.map +1 -1
  125. package/dist/es/standards-sdk.es55.js +32 -2
  126. package/dist/es/standards-sdk.es55.js.map +1 -1
  127. package/dist/es/standards-sdk.es56.js +159 -3
  128. package/dist/es/standards-sdk.es56.js.map +1 -1
  129. package/dist/es/standards-sdk.es57.js +780 -151
  130. package/dist/es/standards-sdk.es57.js.map +1 -1
  131. package/dist/es/standards-sdk.es58.js +357 -25
  132. package/dist/es/standards-sdk.es58.js.map +1 -1
  133. package/dist/es/standards-sdk.es59.js +5 -62
  134. package/dist/es/standards-sdk.es59.js.map +1 -1
  135. package/dist/es/standards-sdk.es60.js +173 -44
  136. package/dist/es/standards-sdk.es60.js.map +1 -1
  137. package/dist/es/standards-sdk.es61.js +80 -9
  138. package/dist/es/standards-sdk.es61.js.map +1 -1
  139. package/dist/es/standards-sdk.es62.js +106 -57
  140. package/dist/es/standards-sdk.es62.js.map +1 -1
  141. package/dist/es/standards-sdk.es63.js +52 -6
  142. package/dist/es/standards-sdk.es63.js.map +1 -1
  143. package/dist/es/standards-sdk.es64.js +9 -14
  144. package/dist/es/standards-sdk.es64.js.map +1 -1
  145. package/dist/es/standards-sdk.es65.js +97 -7
  146. package/dist/es/standards-sdk.es65.js.map +1 -1
  147. package/dist/es/standards-sdk.es66.js +4 -45
  148. package/dist/es/standards-sdk.es66.js.map +1 -1
  149. package/dist/es/standards-sdk.es67.js +2 -2
  150. package/dist/es/standards-sdk.es67.js.map +1 -1
  151. package/dist/es/standards-sdk.es68.js +105 -128
  152. package/dist/es/standards-sdk.es68.js.map +1 -1
  153. package/dist/es/standards-sdk.es69.js +70 -168
  154. package/dist/es/standards-sdk.es69.js.map +1 -1
  155. package/dist/es/standards-sdk.es7.js +4 -4
  156. package/dist/es/standards-sdk.es70.js +5 -3
  157. package/dist/es/standards-sdk.es70.js.map +1 -1
  158. package/dist/es/standards-sdk.es71.js +5 -13
  159. package/dist/es/standards-sdk.es71.js.map +1 -1
  160. package/dist/es/standards-sdk.es72.js +221 -13
  161. package/dist/es/standards-sdk.es72.js.map +1 -1
  162. package/dist/es/standards-sdk.es73.js +53 -7131
  163. package/dist/es/standards-sdk.es73.js.map +1 -1
  164. package/dist/es/standards-sdk.es74.js +68 -194
  165. package/dist/es/standards-sdk.es74.js.map +1 -1
  166. package/dist/es/standards-sdk.es75.js +86 -423
  167. package/dist/es/standards-sdk.es75.js.map +1 -1
  168. package/dist/es/standards-sdk.es76.js +2 -25
  169. package/dist/es/standards-sdk.es76.js.map +1 -1
  170. package/dist/es/standards-sdk.es77.js +15 -138
  171. package/dist/es/standards-sdk.es77.js.map +1 -1
  172. package/dist/es/standards-sdk.es78.js +405 -14
  173. package/dist/es/standards-sdk.es78.js.map +1 -1
  174. package/dist/es/standards-sdk.es79.js +24 -26
  175. package/dist/es/standards-sdk.es79.js.map +1 -1
  176. package/dist/es/standards-sdk.es8.js +3 -3
  177. package/dist/es/standards-sdk.es80.js +24 -27
  178. package/dist/es/standards-sdk.es80.js.map +1 -1
  179. package/dist/es/standards-sdk.es81.js +36 -19
  180. package/dist/es/standards-sdk.es81.js.map +1 -1
  181. package/dist/es/standards-sdk.es82.js +11 -34
  182. package/dist/es/standards-sdk.es82.js.map +1 -1
  183. package/dist/es/standards-sdk.es83.js +2282 -18
  184. package/dist/es/standards-sdk.es83.js.map +1 -1
  185. package/dist/es/standards-sdk.es84.js +158 -55
  186. package/dist/es/standards-sdk.es84.js.map +1 -1
  187. package/dist/es/standards-sdk.es85.js +3 -1289
  188. package/dist/es/standards-sdk.es85.js.map +1 -1
  189. package/dist/es/standards-sdk.es86.js +28 -144
  190. package/dist/es/standards-sdk.es86.js.map +1 -1
  191. package/dist/es/standards-sdk.es87.js +56 -84
  192. package/dist/es/standards-sdk.es87.js.map +1 -1
  193. package/dist/es/standards-sdk.es88.js +46 -80
  194. package/dist/es/standards-sdk.es88.js.map +1 -1
  195. package/dist/es/standards-sdk.es89.js +8 -30
  196. package/dist/es/standards-sdk.es89.js.map +1 -1
  197. package/dist/es/standards-sdk.es9.js +1 -1
  198. package/dist/es/standards-sdk.es90.js +64 -5
  199. package/dist/es/standards-sdk.es90.js.map +1 -1
  200. package/dist/es/standards-sdk.es91.js +6 -159
  201. package/dist/es/standards-sdk.es91.js.map +1 -1
  202. package/dist/es/standards-sdk.es92.js +14 -22
  203. package/dist/es/standards-sdk.es92.js.map +1 -1
  204. package/dist/es/standards-sdk.es93.js +6 -792
  205. package/dist/es/standards-sdk.es93.js.map +1 -1
  206. package/dist/es/standards-sdk.es94.js +43 -172
  207. package/dist/es/standards-sdk.es94.js.map +1 -1
  208. package/dist/es/standards-sdk.es95.js +2 -143
  209. package/dist/es/standards-sdk.es95.js.map +1 -1
  210. package/dist/es/standards-sdk.es96.js +134 -30
  211. package/dist/es/standards-sdk.es96.js.map +1 -1
  212. package/dist/es/standards-sdk.es97.js +171 -14
  213. package/dist/es/standards-sdk.es97.js.map +1 -1
  214. package/dist/es/standards-sdk.es98.js +7135 -3
  215. package/dist/es/standards-sdk.es98.js.map +1 -1
  216. package/dist/es/standards-sdk.es99.js +9 -3
  217. package/dist/es/standards-sdk.es99.js.map +1 -1
  218. package/dist/es/utils/transaction-parser.d.ts +27 -0
  219. package/dist/es/utils/transaction-parser.d.ts.map +1 -1
  220. package/dist/umd/services/mirror-node.d.ts +102 -1
  221. package/dist/umd/services/mirror-node.d.ts.map +1 -1
  222. package/dist/umd/services/types.d.ts +53 -0
  223. package/dist/umd/services/types.d.ts.map +1 -1
  224. package/dist/umd/standards-sdk.umd.js +1 -1
  225. package/dist/umd/standards-sdk.umd.js.map +1 -1
  226. package/dist/umd/utils/transaction-parser.d.ts +27 -0
  227. package/dist/umd/utils/transaction-parser.d.ts.map +1 -1
  228. package/package.json +2 -2
@@ -1,15 +1,32 @@
1
- import Buffer from "./standards-sdk.es28.js";
2
- import { PublicKey, Timestamp } from "@hashgraph/sdk";
1
+ import Buffer from "./standards-sdk.es25.js";
2
+ import { PublicKey, Timestamp, AccountId } from "@hashgraph/sdk";
3
3
  import "./standards-sdk.es26.js";
4
4
  import { proto } from "@hashgraph/proto";
5
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
- const maxRetries = 3;
50
- for (let attempt = 0; attempt < maxRetries; attempt++) {
51
- try {
52
- const accountInfoUrl = `${this.baseUrl}/api/v1/accounts/${accountId}`;
53
- const response = await axios.get(accountInfoUrl);
54
- const accountInfo = response.data;
55
- if (accountInfo && accountInfo.memo) {
56
- return accountInfo.memo;
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
- const response = await axios.get(topicInfoUrl);
83
- return response.data;
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 request = await fetch(
118
- `https://mainnet-public.mirrornode.hedera.com/api/v1/network/exchangerate?timestamp=${timestamp}`
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 response = await axios.get(tokenInfoUrl);
141
- if (response.data) {
142
- this.logger.trace(`Token info found for ${tokenId}:`, response.data);
143
- return response.data;
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 response = await axios.get(nextUrl);
167
- const data = response.data;
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
- return;
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: ${error.message} on ${topicId}`;
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
- const response = await axios.get(accountInfoUrl);
235
- if (!response.data) {
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
- `Failed to make request to mirror node for account: ${accountId}`
255
+ `No data received from mirror node for account: ${accountId}`
238
256
  );
239
257
  }
240
- return response.data;
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 response = await axios.get(url);
348
- if (response.data) {
349
- return response.data;
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(`Error fetching schedule info: ${error.message}`);
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