sorokeep 1.0.0

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 (275) hide show
  1. package/CONTRIBUTING.md +296 -0
  2. package/LICENSE +21 -0
  3. package/README.md +677 -0
  4. package/dist/alerts/alerts.d.ts +21 -0
  5. package/dist/alerts/alerts.d.ts.map +1 -0
  6. package/dist/alerts/alerts.js +18 -0
  7. package/dist/alerts/alerts.js.map +1 -0
  8. package/dist/alerts/discord.d.ts +3 -0
  9. package/dist/alerts/discord.d.ts.map +1 -0
  10. package/dist/alerts/discord.js +199 -0
  11. package/dist/alerts/discord.js.map +1 -0
  12. package/dist/alerts/dispatcher.d.ts +13 -0
  13. package/dist/alerts/dispatcher.d.ts.map +1 -0
  14. package/dist/alerts/dispatcher.js +91 -0
  15. package/dist/alerts/dispatcher.js.map +1 -0
  16. package/dist/alerts/keys.d.ts +24 -0
  17. package/dist/alerts/keys.d.ts.map +1 -0
  18. package/dist/alerts/keys.js +44 -0
  19. package/dist/alerts/keys.js.map +1 -0
  20. package/dist/alerts/pagerduty.d.ts +8 -0
  21. package/dist/alerts/pagerduty.d.ts.map +1 -0
  22. package/dist/alerts/pagerduty.js +164 -0
  23. package/dist/alerts/pagerduty.js.map +1 -0
  24. package/dist/alerts/resource.d.ts +23 -0
  25. package/dist/alerts/resource.d.ts.map +1 -0
  26. package/dist/alerts/resource.js +167 -0
  27. package/dist/alerts/resource.js.map +1 -0
  28. package/dist/alerts/slack.d.ts +7 -0
  29. package/dist/alerts/slack.d.ts.map +1 -0
  30. package/dist/alerts/slack.js +184 -0
  31. package/dist/alerts/slack.js.map +1 -0
  32. package/dist/alerts/telegram.d.ts +9 -0
  33. package/dist/alerts/telegram.d.ts.map +1 -0
  34. package/dist/alerts/telegram.js +133 -0
  35. package/dist/alerts/telegram.js.map +1 -0
  36. package/dist/alerts/templates.d.ts +109 -0
  37. package/dist/alerts/templates.d.ts.map +1 -0
  38. package/dist/alerts/templates.js +139 -0
  39. package/dist/alerts/templates.js.map +1 -0
  40. package/dist/alerts/types.d.ts +113 -0
  41. package/dist/alerts/types.d.ts.map +1 -0
  42. package/dist/alerts/types.js +87 -0
  43. package/dist/alerts/types.js.map +1 -0
  44. package/dist/alerts/webhook.d.ts +13 -0
  45. package/dist/alerts/webhook.d.ts.map +1 -0
  46. package/dist/alerts/webhook.js +56 -0
  47. package/dist/alerts/webhook.js.map +1 -0
  48. package/dist/cli/schemaFormatter.d.ts +11 -0
  49. package/dist/cli/schemaFormatter.d.ts.map +1 -0
  50. package/dist/cli/schemaFormatter.js +74 -0
  51. package/dist/cli/schemaFormatter.js.map +1 -0
  52. package/dist/commands/alerts.d.ts +3 -0
  53. package/dist/commands/alerts.d.ts.map +1 -0
  54. package/dist/commands/alerts.js +256 -0
  55. package/dist/commands/alerts.js.map +1 -0
  56. package/dist/commands/budget.d.ts +3 -0
  57. package/dist/commands/budget.d.ts.map +1 -0
  58. package/dist/commands/budget.js +47 -0
  59. package/dist/commands/budget.js.map +1 -0
  60. package/dist/commands/channels.d.ts +3 -0
  61. package/dist/commands/channels.d.ts.map +1 -0
  62. package/dist/commands/channels.js +84 -0
  63. package/dist/commands/channels.js.map +1 -0
  64. package/dist/commands/check.d.ts +3 -0
  65. package/dist/commands/check.d.ts.map +1 -0
  66. package/dist/commands/check.js +58 -0
  67. package/dist/commands/check.js.map +1 -0
  68. package/dist/commands/completion.d.ts +3 -0
  69. package/dist/commands/completion.d.ts.map +1 -0
  70. package/dist/commands/completion.js +36 -0
  71. package/dist/commands/completion.js.map +1 -0
  72. package/dist/commands/costs.d.ts +15 -0
  73. package/dist/commands/costs.d.ts.map +1 -0
  74. package/dist/commands/costs.js +280 -0
  75. package/dist/commands/costs.js.map +1 -0
  76. package/dist/commands/daemon.d.ts +3 -0
  77. package/dist/commands/daemon.d.ts.map +1 -0
  78. package/dist/commands/daemon.js +93 -0
  79. package/dist/commands/daemon.js.map +1 -0
  80. package/dist/commands/db.d.ts +3 -0
  81. package/dist/commands/db.d.ts.map +1 -0
  82. package/dist/commands/db.js +140 -0
  83. package/dist/commands/db.js.map +1 -0
  84. package/dist/commands/guard.d.ts +3 -0
  85. package/dist/commands/guard.d.ts.map +1 -0
  86. package/dist/commands/guard.js +181 -0
  87. package/dist/commands/guard.js.map +1 -0
  88. package/dist/commands/history.d.ts +3 -0
  89. package/dist/commands/history.d.ts.map +1 -0
  90. package/dist/commands/history.js +57 -0
  91. package/dist/commands/history.js.map +1 -0
  92. package/dist/commands/inspect.d.ts +3 -0
  93. package/dist/commands/inspect.d.ts.map +1 -0
  94. package/dist/commands/inspect.js +82 -0
  95. package/dist/commands/inspect.js.map +1 -0
  96. package/dist/commands/mcp.d.ts +3 -0
  97. package/dist/commands/mcp.d.ts.map +1 -0
  98. package/dist/commands/mcp.js +20 -0
  99. package/dist/commands/mcp.js.map +1 -0
  100. package/dist/commands/pause.d.ts +3 -0
  101. package/dist/commands/pause.d.ts.map +1 -0
  102. package/dist/commands/pause.js +32 -0
  103. package/dist/commands/pause.js.map +1 -0
  104. package/dist/commands/resources.d.ts +3 -0
  105. package/dist/commands/resources.d.ts.map +1 -0
  106. package/dist/commands/resources.js +95 -0
  107. package/dist/commands/resources.js.map +1 -0
  108. package/dist/commands/restore.d.ts +3 -0
  109. package/dist/commands/restore.d.ts.map +1 -0
  110. package/dist/commands/restore.js +92 -0
  111. package/dist/commands/restore.js.map +1 -0
  112. package/dist/commands/resume.d.ts +3 -0
  113. package/dist/commands/resume.d.ts.map +1 -0
  114. package/dist/commands/resume.js +31 -0
  115. package/dist/commands/resume.js.map +1 -0
  116. package/dist/commands/status.d.ts +3 -0
  117. package/dist/commands/status.d.ts.map +1 -0
  118. package/dist/commands/status.js +63 -0
  119. package/dist/commands/status.js.map +1 -0
  120. package/dist/commands/watch.d.ts +3 -0
  121. package/dist/commands/watch.d.ts.map +1 -0
  122. package/dist/commands/watch.js +163 -0
  123. package/dist/commands/watch.js.map +1 -0
  124. package/dist/core/aws_secrets.d.ts +13 -0
  125. package/dist/core/aws_secrets.d.ts.map +1 -0
  126. package/dist/core/aws_secrets.js +34 -0
  127. package/dist/core/aws_secrets.js.map +1 -0
  128. package/dist/core/budget.d.ts +10 -0
  129. package/dist/core/budget.d.ts.map +1 -0
  130. package/dist/core/budget.js +31 -0
  131. package/dist/core/budget.js.map +1 -0
  132. package/dist/core/channels.d.ts +67 -0
  133. package/dist/core/channels.d.ts.map +1 -0
  134. package/dist/core/channels.js +136 -0
  135. package/dist/core/channels.js.map +1 -0
  136. package/dist/core/check.d.ts +25 -0
  137. package/dist/core/check.d.ts.map +1 -0
  138. package/dist/core/check.js +85 -0
  139. package/dist/core/check.js.map +1 -0
  140. package/dist/core/completion.d.ts +9 -0
  141. package/dist/core/completion.d.ts.map +1 -0
  142. package/dist/core/completion.js +68 -0
  143. package/dist/core/completion.js.map +1 -0
  144. package/dist/core/costs.d.ts +90 -0
  145. package/dist/core/costs.d.ts.map +1 -0
  146. package/dist/core/costs.js +147 -0
  147. package/dist/core/costs.js.map +1 -0
  148. package/dist/core/decoder.d.ts +7 -0
  149. package/dist/core/decoder.d.ts.map +1 -0
  150. package/dist/core/decoder.js +38 -0
  151. package/dist/core/decoder.js.map +1 -0
  152. package/dist/core/discovery.d.ts +38 -0
  153. package/dist/core/discovery.d.ts.map +1 -0
  154. package/dist/core/discovery.js +208 -0
  155. package/dist/core/discovery.js.map +1 -0
  156. package/dist/core/extension.d.ts +83 -0
  157. package/dist/core/extension.d.ts.map +1 -0
  158. package/dist/core/extension.js +429 -0
  159. package/dist/core/extension.js.map +1 -0
  160. package/dist/core/inspect.d.ts +62 -0
  161. package/dist/core/inspect.d.ts.map +1 -0
  162. package/dist/core/inspect.js +303 -0
  163. package/dist/core/inspect.js.map +1 -0
  164. package/dist/core/introspection.d.ts +8 -0
  165. package/dist/core/introspection.d.ts.map +1 -0
  166. package/dist/core/introspection.js +8 -0
  167. package/dist/core/introspection.js.map +1 -0
  168. package/dist/core/monitor.d.ts +42 -0
  169. package/dist/core/monitor.d.ts.map +1 -0
  170. package/dist/core/monitor.js +188 -0
  171. package/dist/core/monitor.js.map +1 -0
  172. package/dist/core/rent_projection.d.ts +153 -0
  173. package/dist/core/rent_projection.d.ts.map +1 -0
  174. package/dist/core/rent_projection.js +146 -0
  175. package/dist/core/rent_projection.js.map +1 -0
  176. package/dist/core/scvalTranslator.d.ts +11 -0
  177. package/dist/core/scvalTranslator.d.ts.map +1 -0
  178. package/dist/core/scvalTranslator.js +36 -0
  179. package/dist/core/scvalTranslator.js.map +1 -0
  180. package/dist/core/state_diff.d.ts +45 -0
  181. package/dist/core/state_diff.d.ts.map +1 -0
  182. package/dist/core/state_diff.js +112 -0
  183. package/dist/core/state_diff.js.map +1 -0
  184. package/dist/core/status.d.ts +24 -0
  185. package/dist/core/status.d.ts.map +1 -0
  186. package/dist/core/status.js +57 -0
  187. package/dist/core/status.js.map +1 -0
  188. package/dist/core/vault.d.ts +37 -0
  189. package/dist/core/vault.d.ts.map +1 -0
  190. package/dist/core/vault.js +179 -0
  191. package/dist/core/vault.js.map +1 -0
  192. package/dist/core/watch.d.ts +36 -0
  193. package/dist/core/watch.d.ts.map +1 -0
  194. package/dist/core/watch.js +176 -0
  195. package/dist/core/watch.js.map +1 -0
  196. package/dist/daemon/loop.d.ts +38 -0
  197. package/dist/daemon/loop.d.ts.map +1 -0
  198. package/dist/daemon/loop.js +169 -0
  199. package/dist/daemon/loop.js.map +1 -0
  200. package/dist/db/backup.d.ts +12 -0
  201. package/dist/db/backup.d.ts.map +1 -0
  202. package/dist/db/backup.js +132 -0
  203. package/dist/db/backup.js.map +1 -0
  204. package/dist/db/budget.d.ts +17 -0
  205. package/dist/db/budget.d.ts.map +1 -0
  206. package/dist/db/budget.js +31 -0
  207. package/dist/db/budget.js.map +1 -0
  208. package/dist/db/database.d.ts +6 -0
  209. package/dist/db/database.d.ts.map +1 -0
  210. package/dist/db/database.js +134 -0
  211. package/dist/db/database.js.map +1 -0
  212. package/dist/db/migrations/.gitkeep +1 -0
  213. package/dist/db/migrations/001_resource_usage_logs.sql +28 -0
  214. package/dist/db/migrator.d.ts +28 -0
  215. package/dist/db/migrator.d.ts.map +1 -0
  216. package/dist/db/migrator.js +75 -0
  217. package/dist/db/migrator.js.map +1 -0
  218. package/dist/db/repositories.d.ts +474 -0
  219. package/dist/db/repositories.d.ts.map +1 -0
  220. package/dist/db/repositories.js +809 -0
  221. package/dist/db/repositories.js.map +1 -0
  222. package/dist/db/schema.sql +207 -0
  223. package/dist/index.d.ts +3 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +47 -0
  226. package/dist/index.js.map +1 -0
  227. package/dist/lib.d.ts +20 -0
  228. package/dist/lib.d.ts.map +1 -0
  229. package/dist/lib.js +16 -0
  230. package/dist/lib.js.map +1 -0
  231. package/dist/logging/index.d.ts +22 -0
  232. package/dist/logging/index.d.ts.map +1 -0
  233. package/dist/logging/index.js +34 -0
  234. package/dist/logging/index.js.map +1 -0
  235. package/dist/logging/logger.d.ts +14 -0
  236. package/dist/logging/logger.d.ts.map +1 -0
  237. package/dist/logging/logger.js +135 -0
  238. package/dist/logging/logger.js.map +1 -0
  239. package/dist/logging/types.d.ts +10 -0
  240. package/dist/logging/types.d.ts.map +1 -0
  241. package/dist/logging/types.js +2 -0
  242. package/dist/logging/types.js.map +1 -0
  243. package/dist/mcp/index.d.ts +3 -0
  244. package/dist/mcp/index.d.ts.map +1 -0
  245. package/dist/mcp/index.js +14 -0
  246. package/dist/mcp/index.js.map +1 -0
  247. package/dist/mcp/server.d.ts +10 -0
  248. package/dist/mcp/server.d.ts.map +1 -0
  249. package/dist/mcp/server.js +51 -0
  250. package/dist/mcp/server.js.map +1 -0
  251. package/dist/mcp/tools/get-extension-costs.d.ts +4 -0
  252. package/dist/mcp/tools/get-extension-costs.d.ts.map +1 -0
  253. package/dist/mcp/tools/get-extension-costs.js +33 -0
  254. package/dist/mcp/tools/get-extension-costs.js.map +1 -0
  255. package/dist/mcp/tools/get_contract_status.d.ts +8 -0
  256. package/dist/mcp/tools/get_contract_status.d.ts.map +1 -0
  257. package/dist/mcp/tools/get_contract_status.js +33 -0
  258. package/dist/mcp/tools/get_contract_status.js.map +1 -0
  259. package/dist/rpc/client.d.ts +154 -0
  260. package/dist/rpc/client.d.ts.map +1 -0
  261. package/dist/rpc/client.js +766 -0
  262. package/dist/rpc/client.js.map +1 -0
  263. package/dist/utils/config.d.ts +51 -0
  264. package/dist/utils/config.d.ts.map +1 -0
  265. package/dist/utils/config.js +86 -0
  266. package/dist/utils/config.js.map +1 -0
  267. package/dist/utils/formatting.d.ts +10 -0
  268. package/dist/utils/formatting.d.ts.map +1 -0
  269. package/dist/utils/formatting.js +66 -0
  270. package/dist/utils/formatting.js.map +1 -0
  271. package/dist/utils/watch-config.d.ts +13 -0
  272. package/dist/utils/watch-config.d.ts.map +1 -0
  273. package/dist/utils/watch-config.js +32 -0
  274. package/dist/utils/watch-config.js.map +1 -0
  275. package/package.json +73 -0
@@ -0,0 +1,766 @@
1
+ import { Contract, rpc, xdr, TransactionBuilder, Networks, Account, Operation, Keypair, SorobanDataBuilder, Asset, } from "@stellar/stellar-sdk";
2
+ import { getLogger } from "../logging/index.js";
3
+ // CostSummary removed — no longer exported from costs.js
4
+ export function assertSimulationSuccess(sim) {
5
+ if (rpc.Api.isSimulationError(sim)) {
6
+ if (sim.error?.includes("txBadSeq")) {
7
+ throw new Error("Simulation failed: Expired sequence number");
8
+ }
9
+ if (sim.error?.includes("txInsufficientBalance")) {
10
+ throw new Error("Simulation failed: Insufficient wallet balance");
11
+ }
12
+ if (sim.error?.includes("invalid footprint")) {
13
+ throw new Error("Simulation failed: Invalid footprint key");
14
+ }
15
+ throw new Error(`Simulation failed: ${sim.error ?? "unknown error"}`);
16
+ }
17
+ }
18
+ /**
19
+ * Executes an RPC action with exponential backoff on network timeouts or 429/5xx errors.
20
+ * Starts at 1 second, doubling up to 3 retries (max 4 attempts).
21
+ */
22
+ export async function executeWithRetry(action) {
23
+ const MAX_RETRIES = 3;
24
+ let delayMs = 1000;
25
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
26
+ try {
27
+ return await action();
28
+ }
29
+ catch (error) {
30
+ const isTimeout = error?.code === "ETIMEDOUT" || error?.code === "ECONNRESET" || error?.message?.includes("timeout");
31
+ const status = error?.response?.status;
32
+ const isRetryableHttp = status === 429 || (status >= 500 && status < 600);
33
+ if ((isTimeout || isRetryableHttp) && attempt < MAX_RETRIES) {
34
+ await new Promise(resolve => setTimeout(resolve, delayMs));
35
+ delayMs *= 2;
36
+ continue;
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ throw new Error("Unreachable");
42
+ }
43
+ const logger = getLogger().child({ component: "StellarRpcClient" });
44
+ const RPC_URLS = {
45
+ testnet: "https://soroban-testnet.stellar.org",
46
+ mainnet: "https://mainnet.sorobanrpc.com",
47
+ };
48
+ /**
49
+ * Parse a simulateTransaction RPC response into a structured ResourceEstimate.
50
+ *
51
+ * Extracts `cpuInstructions` from `response.cost.cpuInsns`,
52
+ * `memoryBytes` from `response.cost.memBytes`, and
53
+ * `minResourceFee` from `response.minResourceFee`.
54
+ *
55
+ * Returns `null` when:
56
+ * - The input is null, undefined, or not a plain object.
57
+ * - The response contains an `error` field (simulation failed).
58
+ * - Neither `cost` nor `minResourceFee` fields are present.
59
+ *
60
+ * Missing numeric fields default to `0` rather than `NaN`.
61
+ *
62
+ * @param response - The raw simulation response object (or null/undefined).
63
+ * @returns A ResourceEstimate on success, or null on failure.
64
+ */
65
+ export function parseResourceEstimate(response) {
66
+ if (response === null || response === undefined)
67
+ return null;
68
+ if (typeof response !== "object" || Array.isArray(response))
69
+ return null;
70
+ const sim = response;
71
+ // Simulation error responses have an `error` field — always return null.
72
+ if (typeof sim["error"] === "string" && sim["error"].length > 0)
73
+ return null;
74
+ // Need at least one useful field to return a meaningful estimate.
75
+ const hasCost = sim["cost"] !== undefined && sim["cost"] !== null;
76
+ const hasFee = sim["minResourceFee"] !== undefined && sim["minResourceFee"] !== null;
77
+ if (!hasCost && !hasFee)
78
+ return null;
79
+ // Parse minResourceFee (may be a string or number in the Soroban RPC response)
80
+ const rawFee = sim["minResourceFee"];
81
+ const minResourceFee = rawFee !== undefined && rawFee !== null
82
+ ? safeParseNumber(rawFee)
83
+ : 0;
84
+ // Parse cost fields
85
+ let cpuInstructions = 0;
86
+ let memoryBytes = 0;
87
+ if (hasCost && typeof sim["cost"] === "object" && !Array.isArray(sim["cost"])) {
88
+ const cost = sim["cost"];
89
+ cpuInstructions = safeParseNumber(cost["cpuInsns"]);
90
+ memoryBytes = safeParseNumber(cost["memBytes"]);
91
+ }
92
+ return { cpuInstructions, memoryBytes, minResourceFee };
93
+ }
94
+ /** Parse a value to a non-negative finite integer, defaulting to 0. */
95
+ function safeParseNumber(value) {
96
+ if (value === undefined || value === null)
97
+ return 0;
98
+ const n = typeof value === "number" ? value : Number(value);
99
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
100
+ }
101
+ export function extractResourceCosts(resultMetaXdrBase64) {
102
+ if (!resultMetaXdrBase64)
103
+ return null;
104
+ try {
105
+ const meta = xdr.TransactionMeta.fromXDR(resultMetaXdrBase64, "base64");
106
+ const v3 = typeof meta.v3 === 'function' ? meta.v3() : undefined;
107
+ if (v3) {
108
+ const sorobanMeta = typeof v3.sorobanMeta === 'function' ? v3.sorobanMeta() : undefined;
109
+ if (sorobanMeta) {
110
+ const anyMeta = sorobanMeta;
111
+ const cpuInstructions = typeof anyMeta.cpuInstructions === 'function' ? Number(anyMeta.cpuInstructions()) : undefined;
112
+ const memoryBytes = typeof anyMeta.memoryBytes === 'function' ? Number(anyMeta.memoryBytes()) : undefined;
113
+ if (cpuInstructions !== undefined && memoryBytes !== undefined) {
114
+ return { cpuInstructions, memoryBytes };
115
+ }
116
+ }
117
+ }
118
+ }
119
+ catch (error) {
120
+ logger.debug("Failed to decode resultMetaXdr for resource costs", { error: String(error) });
121
+ }
122
+ return null;
123
+ }
124
+ const NETWORK_PASSPHRASES = {
125
+ testnet: Networks.TESTNET,
126
+ mainnet: Networks.PUBLIC,
127
+ };
128
+ export class StellarRpcClient {
129
+ network;
130
+ server;
131
+ maxRequestsPerSecond;
132
+ requestIntervalMs;
133
+ recentRequestTimes = [];
134
+ constructor(network, customUrl, options = {}) {
135
+ this.network = network;
136
+ const configured = options.maxRequestsPerSecond ?? 5;
137
+ this.maxRequestsPerSecond = configured > 0 ? configured : 5;
138
+ this.requestIntervalMs = Math.ceil(1000 / this.maxRequestsPerSecond);
139
+ const url = customUrl ?? RPC_URLS[network];
140
+ if (!url) {
141
+ throw new Error(`Unknown network "${network}". Use "testnet", "mainnet", or provide a custom URL.`);
142
+ }
143
+ this.server = new rpc.Server(url, { allowHttp: url.startsWith("http://") });
144
+ }
145
+ getNetwork() {
146
+ return this.network;
147
+ }
148
+ async checkHealth() {
149
+ return await this.withRateLimit(() => this.server.getHealth());
150
+ }
151
+ async getCurrentLedger() {
152
+ const serverAny = this.server;
153
+ if (typeof serverAny.getLatestLedger === "function") {
154
+ try {
155
+ const response = await this.withRateLimit(() => serverAny.getLatestLedger());
156
+ const seq = response?.sequence;
157
+ if (typeof seq === "number" && seq > 0)
158
+ return seq;
159
+ }
160
+ catch (error) {
161
+ logger.debug("getLatestLedger failed, falling back to getHealth", error);
162
+ }
163
+ }
164
+ const health = await this.withRateLimit(() => this.server.getHealth());
165
+ if (health && typeof health.latestLedger === "number") {
166
+ return health.latestLedger;
167
+ }
168
+ throw new Error("Unable to determine latest ledger from RPC server");
169
+ }
170
+ async getFeeStats() {
171
+ return await this.withRateLimit(async () => {
172
+ const serverAny = this.server;
173
+ if (typeof serverAny.getFeeStats !== "function") {
174
+ throw new Error("RPC server does not support getFeeStats");
175
+ }
176
+ const response = await serverAny.getFeeStats();
177
+ const inclusionFee = response.sorobanInclusionFee ?? response.inclusionFee;
178
+ if (!inclusionFee) {
179
+ throw new Error("RPC fee stats response did not include inclusion fee data");
180
+ }
181
+ const baseFeeStroops = parseFeeStat(inclusionFee.p50 ?? inclusionFee.mode ?? inclusionFee.min);
182
+ const surgeFeeStroops = parseFeeStat(inclusionFee.p95 ?? inclusionFee.p90 ?? inclusionFee.max ?? baseFeeStroops);
183
+ const surgePricingMultiplier = baseFeeStroops > 0
184
+ ? Math.max(surgeFeeStroops / baseFeeStroops, 1)
185
+ : 1;
186
+ return {
187
+ latestLedger: typeof response.latestLedger === "number" ? response.latestLedger : undefined,
188
+ baseFeeStroops,
189
+ surgeFeeStroops,
190
+ surgePricingMultiplier,
191
+ };
192
+ });
193
+ }
194
+ async getContractInstanceEntry(contractId) {
195
+ const contract = new Contract(contractId);
196
+ const instanceKey = contract.getFootprint();
197
+ const entryKeyXdr = instanceKey.toXDR("base64");
198
+ const response = await this.server.getLedgerEntries(instanceKey);
199
+ if (!response.entries || response.entries.length === 0)
200
+ return null;
201
+ const entry = response.entries[0];
202
+ const latestLedger = response.latestLedger;
203
+ const liveUntilLedgerSeq = entry.liveUntilLedgerSeq ?? 0;
204
+ const lastModifiedLedgerSeq = entry.lastModifiedLedgerSeq ?? 0;
205
+ const remainingTTL = liveUntilLedgerSeq - latestLedger;
206
+ let executableType = "unknown";
207
+ let wasmHash = null;
208
+ try {
209
+ const contractData = entry.val.contractData();
210
+ const instance = contractData.val().instance();
211
+ const executable = instance.executable();
212
+ executableType = executable.switch().name;
213
+ if (executableType === "contractExecutableWasm") {
214
+ wasmHash = executable.wasmHash().toString("hex");
215
+ }
216
+ }
217
+ catch (error) {
218
+ logger.error("Error extracting executable info from contract instance entry", error);
219
+ }
220
+ return {
221
+ entryKeyXdr,
222
+ executableType,
223
+ latestLedger,
224
+ liveUntilLedgerSeq,
225
+ lastModifiedLedgerSeq,
226
+ remainingTTL,
227
+ wasmHash,
228
+ };
229
+ }
230
+ async getWasmCodeEntry(wasmHashHex) {
231
+ const wasmHash = Buffer.from(wasmHashHex, "hex");
232
+ const wasmKey = xdr.LedgerKey.contractCode(new xdr.LedgerKeyContractCode({ hash: wasmHash }));
233
+ const entryKeyXdr = wasmKey.toXDR("base64");
234
+ const response = await this.server.getLedgerEntries(wasmKey);
235
+ if (!response.entries || response.entries.length === 0)
236
+ return null;
237
+ const entry = response.entries[0];
238
+ const latestLedger = response.latestLedger;
239
+ const liveUntilLedgerSeq = entry.liveUntilLedgerSeq ?? 0;
240
+ const lastModifiedLedgerSeq = entry.lastModifiedLedgerSeq ?? 0;
241
+ return {
242
+ entryKeyXdr,
243
+ latestLedger,
244
+ liveUntilLedgerSeq,
245
+ lastModifiedLedgerSeq,
246
+ remainingTTL: liveUntilLedgerSeq - latestLedger,
247
+ };
248
+ }
249
+ async getEntryTTLs(entryKeyXdrs) {
250
+ const keys = entryKeyXdrs.map((xdrStr) => xdr.LedgerKey.fromXDR(xdrStr, "base64"));
251
+ const response = await this.server.getLedgerEntries(...keys);
252
+ const latestLedger = response.latestLedger;
253
+ const entries = (response.entries ?? []).map((entry) => {
254
+ const liveUntilLedgerSeq = entry.liveUntilLedgerSeq ?? 0;
255
+ const lastModifiedLedgerSeq = entry.lastModifiedLedgerSeq ?? 0;
256
+ return {
257
+ entryKeyXdr: entry.key.toXDR("base64"),
258
+ latestLedger,
259
+ liveUntilLedgerSeq,
260
+ lastModifiedLedgerSeq,
261
+ remainingTTL: liveUntilLedgerSeq - latestLedger,
262
+ };
263
+ });
264
+ return { latestLedger, entries };
265
+ }
266
+ async getContractStorageEntries(entryKeyXdrs) {
267
+ if (entryKeyXdrs.length === 0)
268
+ return [];
269
+ const keys = entryKeyXdrs.map((xdrStr) => xdr.LedgerKey.fromXDR(xdrStr, "base64"));
270
+ const response = await this.server.getLedgerEntries(...keys);
271
+ const latestLedger = response.latestLedger;
272
+ return (response.entries ?? []).map((entry) => {
273
+ const liveUntilLedgerSeq = entry.liveUntilLedgerSeq ?? 0;
274
+ const lastModifiedLedgerSeq = entry.lastModifiedLedgerSeq ?? 0;
275
+ let valXdr;
276
+ try {
277
+ if (entry.val && entry.val.switch().name === "contractData") {
278
+ valXdr = entry.val.contractData().val().toXDR("base64");
279
+ }
280
+ }
281
+ catch {
282
+ // ignore
283
+ }
284
+ return {
285
+ entryKeyXdr: entry.key.toXDR("base64"),
286
+ latestLedger,
287
+ liveUntilLedgerSeq,
288
+ lastModifiedLedgerSeq,
289
+ remainingTTL: liveUntilLedgerSeq - latestLedger,
290
+ valXdr,
291
+ };
292
+ });
293
+ }
294
+ async getSacDecimals(contractId) {
295
+ try {
296
+ const passphrase = await this.getNetworkPassphrase();
297
+ const contract = new Contract(contractId);
298
+ const op = contract.call("decimals");
299
+ const account = new Account("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", "0");
300
+ const tx = new TransactionBuilder(account, {
301
+ fee: "100",
302
+ networkPassphrase: passphrase,
303
+ })
304
+ .addOperation(op)
305
+ .setTimeout(30)
306
+ .build();
307
+ const sim = await this.server.simulateTransaction(tx);
308
+ if (!rpc.Api.isSimulationError(sim)) {
309
+ const successSim = sim;
310
+ if (successSim.result?.retval) {
311
+ const retval = successSim.result.retval;
312
+ if (retval.switch().name === "scvU32") {
313
+ return retval.u32();
314
+ }
315
+ }
316
+ }
317
+ }
318
+ catch {
319
+ // fallback below
320
+ }
321
+ return 7; // standard SAC / XLM asset decimals
322
+ }
323
+ /**
324
+ * Call the 'get_monitored_keys' view method on a contract.
325
+ * Returns an array of XDR strings for the keys.
326
+ */
327
+ async getMonitoredKeys(contractId) {
328
+ const passphrase = await this.getNetworkPassphrase();
329
+ const contract = new Contract(contractId);
330
+ const op = contract.call("get_monitored_keys");
331
+ const account = new Account("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", "0");
332
+ const tx = new TransactionBuilder(account, {
333
+ fee: "100",
334
+ networkPassphrase: passphrase,
335
+ })
336
+ .addOperation(op)
337
+ .setTimeout(30)
338
+ .build();
339
+ const sim = await this.server.simulateTransaction(tx);
340
+ assertSimulationSuccess(sim);
341
+ const successSim = sim;
342
+ const scv = successSim.result.retval;
343
+ if (scv.switch().name === "scvVec") {
344
+ const vec = scv.vec();
345
+ return vec.map(val => val.toXDR("base64"));
346
+ }
347
+ return [];
348
+ }
349
+ async simulateExtension(entryKeyXdrs, extendToLedgers, sourcePublicKey) {
350
+ const passphrase = await this.getNetworkPassphrase();
351
+ const accountResponse = await this.server.getAccount(sourcePublicKey);
352
+ const account = new Account(sourcePublicKey, accountResponse.sequenceNumber());
353
+ const keys = entryKeyXdrs.map(k => xdr.LedgerKey.fromXDR(k, "base64"));
354
+ const tx = new TransactionBuilder(account, {
355
+ fee: "100",
356
+ networkPassphrase: passphrase,
357
+ })
358
+ .addOperation(Operation.extendFootprintTtl({
359
+ extendTo: extendToLedgers,
360
+ }))
361
+ .setTimeout(30)
362
+ .setSorobanData(new SorobanDataBuilder()
363
+ .setReadOnly(keys)
364
+ .build())
365
+ .build();
366
+ const sim = await this.server.simulateTransaction(tx);
367
+ if (rpc.Api.isSimulationError(sim)) {
368
+ return {
369
+ success: false,
370
+ minResourceFee: 0,
371
+ error: sim.error ?? "Simulation failed",
372
+ };
373
+ }
374
+ const successSim = sim;
375
+ return {
376
+ success: true,
377
+ minResourceFee: Number(successSim.minResourceFee ?? 0),
378
+ cpuInstructions: Number(successSim.cost?.cpuInsns ?? 0),
379
+ memoryBytes: Number(successSim.cost?.memBytes ?? 0),
380
+ readBytes: Number(successSim.cost?.readBytes ?? 0),
381
+ writeBytes: Number(successSim.cost?.writeBytes ?? 0),
382
+ };
383
+ }
384
+ async simulateRestore(entryKeyXdrs, sourcePublicKey) {
385
+ const passphrase = await this.getNetworkPassphrase();
386
+ const accountResponse = await this.server.getAccount(sourcePublicKey);
387
+ const account = new Account(sourcePublicKey, accountResponse.sequenceNumber());
388
+ const keys = entryKeyXdrs.map(k => xdr.LedgerKey.fromXDR(k, "base64"));
389
+ const tx = new TransactionBuilder(account, {
390
+ fee: "100",
391
+ networkPassphrase: passphrase,
392
+ })
393
+ .addOperation(Operation.restoreFootprint({}))
394
+ .setTimeout(30)
395
+ .setSorobanData(new SorobanDataBuilder()
396
+ .setReadWrite(keys)
397
+ .build())
398
+ .build();
399
+ const sim = await this.server.simulateTransaction(tx);
400
+ if (rpc.Api.isSimulationError(sim)) {
401
+ return {
402
+ success: false,
403
+ minResourceFee: 0,
404
+ error: sim.error ?? "Simulation failed",
405
+ };
406
+ }
407
+ const successSim = sim;
408
+ return {
409
+ success: true,
410
+ minResourceFee: Number(successSim.minResourceFee ?? 0),
411
+ };
412
+ }
413
+ /**
414
+ * Build, sign, and submit an ExtendFootprintTTLOp transaction.
415
+ * Uses simulation to prepare the transaction with correct resource parameters.
416
+ * Recovers once from txBadSeq errors by refreshing the account sequence.
417
+ */
418
+ async submitExtension(entryKeyXdrs, extendToLedgers, secretKey) {
419
+ const passphrase = await this.getNetworkPassphrase();
420
+ const keypair = Keypair.fromSecret(secretKey);
421
+ const publicKey = keypair.publicKey();
422
+ const keys = entryKeyXdrs.map(k => xdr.LedgerKey.fromXDR(k, "base64"));
423
+ const buildTx = async () => {
424
+ const accountResponse = await this.server.getAccount(publicKey);
425
+ const account = new Account(publicKey, accountResponse.sequenceNumber());
426
+ return new TransactionBuilder(account, { fee: "100", networkPassphrase: passphrase })
427
+ .addOperation(Operation.extendFootprintTtl({ extendTo: extendToLedgers }))
428
+ .setTimeout(30)
429
+ .setSorobanData(new SorobanDataBuilder().setReadOnly(keys).build())
430
+ .build();
431
+ };
432
+ const tx = await buildTx();
433
+ const sim = await this.server.simulateTransaction(tx);
434
+ assertSimulationSuccess(sim);
435
+ const prepared = rpc.assembleTransaction(tx, sim).build();
436
+ prepared.sign(keypair);
437
+ const sendResult = await this.server.sendTransaction(prepared);
438
+ if (sendResult.status === "ERROR") {
439
+ if (this.isBadSeqError(sendResult)) {
440
+ logger.warn("Sequence mismatch detected on ExtendFootprintTTL — refreshing account sequence and retrying");
441
+ const retryTx = await buildTx();
442
+ const retrySim = await this.server.simulateTransaction(retryTx);
443
+ if (rpc.Api.isSimulationError(retrySim)) {
444
+ return { success: false, txHash: "", ledger: 0, cpuInsns: 0, memBytes: 0, error: retrySim.error ?? "Simulation failed on retry" };
445
+ }
446
+ const retryPrepared = rpc.assembleTransaction(retryTx, retrySim).build();
447
+ retryPrepared.sign(keypair);
448
+ const retrySendResult = await this.server.sendTransaction(retryPrepared);
449
+ if (retrySendResult.status === "ERROR") {
450
+ const diagnostics = retrySendResult.errorResult ?? retrySendResult.diagnosticEventsXdr ?? "";
451
+ return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number(retrySim.cost?.cpuInsns ?? 0), memBytes: Number(retrySim.cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` };
452
+ }
453
+ const txResult = await this.pollTransaction(retrySendResult.hash);
454
+ return txResult.success ? this.addResourcesToSuccess(txResult, retrySim) : txResult;
455
+ }
456
+ const diagnostics = sendResult.errorResult ?? sendResult.diagnosticEventsXdr ?? "";
457
+ return {
458
+ success: false,
459
+ txHash: sendResult.hash,
460
+ ledger: 0,
461
+ cpuInsns: Number(sim.cost?.cpuInsns ?? 0),
462
+ memBytes: Number(sim.cost?.memBytes ?? 0),
463
+ error: `Transaction send error: ${diagnostics || sendResult.status}`,
464
+ };
465
+ }
466
+ const txResult = await this.pollTransaction(sendResult.hash);
467
+ return txResult.success ? this.addResourcesToSuccess(txResult, sim) : txResult;
468
+ }
469
+ // Helper to add resource usage to a successful transaction result
470
+ addResourcesToSuccess(result, sim) {
471
+ return { ...result, cpuInsns: Number(sim.cost?.cpuInsns ?? 0), memBytes: Number(sim.cost?.memBytes ?? 0) };
472
+ }
473
+ /**
474
+ * Build an ExtendFootprintTTLOp transaction, wrap it in a FeeBumpTransaction
475
+ * signed by the sponsor keypair, and submit. The sponsor account pays all fees
476
+ * while the inner transaction's source account provides the sequence number.
477
+ */
478
+ async submitExtensionWithFeeBump(entryKeyXdrs, extendToLedgers, secretKey, sponsorSecretKey) {
479
+ const passphrase = await this.getNetworkPassphrase();
480
+ const keypair = Keypair.fromSecret(secretKey);
481
+ const sponsorKeypair = Keypair.fromSecret(sponsorSecretKey);
482
+ const publicKey = keypair.publicKey();
483
+ const keys = entryKeyXdrs.map(k => xdr.LedgerKey.fromXDR(k, "base64"));
484
+ const buildTx = async () => {
485
+ const accountResponse = await this.server.getAccount(publicKey);
486
+ const account = new Account(publicKey, accountResponse.sequenceNumber());
487
+ return new TransactionBuilder(account, { fee: "100", networkPassphrase: passphrase })
488
+ .addOperation(Operation.extendFootprintTtl({ extendTo: extendToLedgers }))
489
+ .setTimeout(30)
490
+ .setSorobanData(new SorobanDataBuilder().setReadOnly(keys).build())
491
+ .build();
492
+ };
493
+ const tx = await buildTx();
494
+ const sim = await this.server.simulateTransaction(tx);
495
+ if (rpc.Api.isSimulationError(sim)) {
496
+ return { success: false, txHash: "", ledger: 0, cpuInsns: 0, memBytes: 0, error: sim.error ?? "Simulation failed" };
497
+ }
498
+ const buildAndSignFeeBump = (innerTx, simResult) => {
499
+ const prepared = rpc.assembleTransaction(innerTx, simResult).build();
500
+ prepared.sign(keypair);
501
+ const feeBump = TransactionBuilder.buildFeeBumpTransaction(sponsorKeypair, (parseInt(prepared.fee, 10) + 10000).toString(), prepared, passphrase);
502
+ feeBump.sign(sponsorKeypair);
503
+ return feeBump;
504
+ };
505
+ const feeBump = buildAndSignFeeBump(tx, sim);
506
+ const sendResult = await this.server.sendTransaction(feeBump);
507
+ if (sendResult.status === "ERROR") {
508
+ if (this.isBadSeqError(sendResult)) {
509
+ logger.warn("Sequence mismatch detected on feeBump ExtendFootprintTTL — refreshing account sequence and retrying");
510
+ const retryTx = await buildTx();
511
+ const retrySim = await this.server.simulateTransaction(retryTx);
512
+ if (rpc.Api.isSimulationError(retrySim)) {
513
+ return { success: false, txHash: "", ledger: 0, cpuInsns: 0, memBytes: 0, error: retrySim.error ?? "Simulation failed on retry" };
514
+ }
515
+ const retryFeeBump = buildAndSignFeeBump(retryTx, retrySim);
516
+ const retrySendResult = await this.server.sendTransaction(retryFeeBump);
517
+ if (retrySendResult.status === "ERROR") {
518
+ const diagnostics = retrySendResult.errorResult ?? retrySendResult.diagnosticEventsXdr ?? "";
519
+ return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number(retrySim.cost?.cpuInsns ?? 0), memBytes: Number(retrySim.cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` };
520
+ }
521
+ const txResult = await this.pollTransaction(retrySendResult.hash);
522
+ return txResult.success ? this.addResourcesToSuccess(txResult, retrySim) : txResult;
523
+ }
524
+ const diagnostics = sendResult.errorResult ?? sendResult.diagnosticEventsXdr ?? "";
525
+ return {
526
+ success: false,
527
+ txHash: sendResult.hash,
528
+ ledger: 0,
529
+ cpuInsns: Number(sim.cost?.cpuInsns ?? 0),
530
+ memBytes: Number(sim.cost?.memBytes ?? 0),
531
+ error: `Transaction send error: ${diagnostics || sendResult.status}`,
532
+ };
533
+ }
534
+ const txResult = await this.pollTransaction(sendResult.hash);
535
+ return txResult.success ? this.addResourcesToSuccess(txResult, sim) : txResult;
536
+ }
537
+ /**
538
+ * Build, sign, and submit a RestoreFootprintOp transaction to restore archived entries.
539
+ * Recovers once from txBadSeq errors by refreshing the account sequence.
540
+ */
541
+ async submitRestore(entryKeyXdrs, secretKey) {
542
+ const passphrase = await this.getNetworkPassphrase();
543
+ const keypair = Keypair.fromSecret(secretKey);
544
+ const publicKey = keypair.publicKey();
545
+ const keys = entryKeyXdrs.map(k => xdr.LedgerKey.fromXDR(k, "base64"));
546
+ const buildTx = async () => {
547
+ const accountResponse = await this.server.getAccount(publicKey);
548
+ const account = new Account(publicKey, accountResponse.sequenceNumber());
549
+ return new TransactionBuilder(account, { fee: "100", networkPassphrase: passphrase })
550
+ .addOperation(Operation.restoreFootprint({}))
551
+ .setTimeout(30)
552
+ .setSorobanData(new SorobanDataBuilder().setReadWrite(keys).build())
553
+ .build();
554
+ };
555
+ const tx = await buildTx();
556
+ const sim = await this.server.simulateTransaction(tx);
557
+ if (rpc.Api.isSimulationError(sim)) {
558
+ return {
559
+ success: false,
560
+ txHash: "",
561
+ ledger: 0,
562
+ cpuInsns: 0,
563
+ memBytes: 0,
564
+ error: sim.error ?? "Simulation failed",
565
+ };
566
+ }
567
+ const prepared = rpc.assembleTransaction(tx, sim).build();
568
+ prepared.sign(keypair);
569
+ const sendResult = await this.server.sendTransaction(prepared);
570
+ if (sendResult.status === "ERROR") {
571
+ if (this.isBadSeqError(sendResult)) {
572
+ logger.warn("Sequence mismatch detected on RestoreFootprint — refreshing account sequence and retrying");
573
+ const retryTx = await buildTx();
574
+ const retrySim = await this.server.simulateTransaction(retryTx);
575
+ if (rpc.Api.isSimulationError(retrySim)) {
576
+ return { success: false, txHash: "", ledger: 0, cpuInsns: 0, memBytes: 0, error: retrySim.error ?? "Simulation failed on retry" };
577
+ }
578
+ const retryPrepared = rpc.assembleTransaction(retryTx, retrySim).build();
579
+ retryPrepared.sign(keypair);
580
+ const retrySendResult = await this.server.sendTransaction(retryPrepared);
581
+ if (retrySendResult.status === "ERROR") {
582
+ const diagnostics = retrySendResult.errorResult ?? retrySendResult.diagnosticEventsXdr ?? "";
583
+ return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number(retrySim.cost?.cpuInsns ?? 0), memBytes: Number(retrySim.cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` };
584
+ }
585
+ const txResult = await this.pollTransaction(retrySendResult.hash);
586
+ return txResult.success ? this.addResourcesToSuccess(txResult, retrySim) : txResult;
587
+ }
588
+ const diagnostics = sendResult.errorResult ?? sendResult.diagnosticEventsXdr ?? "";
589
+ return {
590
+ success: false,
591
+ txHash: sendResult.hash,
592
+ ledger: 0,
593
+ cpuInsns: Number(sim.cost?.cpuInsns ?? 0),
594
+ memBytes: Number(sim.cost?.memBytes ?? 0),
595
+ error: `Transaction send error: ${diagnostics || sendResult.status}`,
596
+ };
597
+ }
598
+ const txResult = await this.pollTransaction(sendResult.hash);
599
+ return txResult.success ? this.addResourcesToSuccess(txResult, sim) : txResult;
600
+ }
601
+ async sendPayments(destinations, secretKey) {
602
+ if (destinations.length === 0) {
603
+ return { success: true, txHash: "", ledger: 0 };
604
+ }
605
+ const passphrase = await this.getNetworkPassphrase();
606
+ const keypair = Keypair.fromSecret(secretKey);
607
+ const publicKey = keypair.publicKey();
608
+ const accountResponse = await this.server.getAccount(publicKey);
609
+ const account = new Account(publicKey, accountResponse.sequenceNumber());
610
+ const builder = new TransactionBuilder(account, {
611
+ fee: String(100 * destinations.length),
612
+ networkPassphrase: passphrase,
613
+ });
614
+ for (const dest of destinations) {
615
+ builder.addOperation(Operation.payment({
616
+ destination: dest.publicKey,
617
+ asset: Asset.native(),
618
+ amount: dest.amountXlm,
619
+ }));
620
+ }
621
+ const tx = builder.setTimeout(30).build();
622
+ tx.sign(keypair);
623
+ const sendResult = await this.server.sendTransaction(tx);
624
+ if (sendResult.status === "ERROR") {
625
+ const diagnostics = sendResult.errorResult ?? "";
626
+ return {
627
+ success: false,
628
+ txHash: sendResult.hash,
629
+ ledger: 0,
630
+ error: `Transaction send error: ${diagnostics || sendResult.status}`,
631
+ };
632
+ }
633
+ return this.pollTransaction(sendResult.hash);
634
+ }
635
+ /**
636
+ * Returns true if the sendTransaction ERROR response indicates a txBadSeq result code.
637
+ * The SDK parses errorResultXdr into `errorResult` as an xdr.TransactionResult.
638
+ */
639
+ isBadSeqError(sendResult) {
640
+ try {
641
+ const errorResult = sendResult.errorResult;
642
+ if (!errorResult)
643
+ return false;
644
+ // errorResult may be a base64 string or a pre-parsed xdr.TransactionResult
645
+ const parsed = typeof errorResult === "string"
646
+ ? xdr.TransactionResult.fromXDR(errorResult, "base64")
647
+ : errorResult;
648
+ return parsed.result().switch().name === "txBadSeq";
649
+ }
650
+ catch {
651
+ return false;
652
+ }
653
+ }
654
+ _cachedPassphrase;
655
+ async getNetworkPassphrase() {
656
+ if (this._cachedPassphrase)
657
+ return this._cachedPassphrase;
658
+ return await this.withRateLimit(async () => {
659
+ // Try fetching from the RPC server first
660
+ try {
661
+ const networkInfo = await this.server.getNetwork();
662
+ if (networkInfo.passphrase) {
663
+ this._cachedPassphrase = networkInfo.passphrase;
664
+ return networkInfo.passphrase;
665
+ }
666
+ }
667
+ catch {
668
+ // Fall through to hardcoded table
669
+ }
670
+ const passphrase = NETWORK_PASSPHRASES[this.network];
671
+ if (!passphrase) {
672
+ throw new Error(`No network passphrase for "${this.network}". Use "testnet" or "mainnet".`);
673
+ }
674
+ this._cachedPassphrase = passphrase;
675
+ return passphrase;
676
+ });
677
+ }
678
+ async pollTransaction(txHash, maxAttempts = 30, intervalMs = 1000) {
679
+ for (let i = 0; i < maxAttempts; i++) {
680
+ const txResponse = await this.withRateLimit(() => this.server.getTransaction(txHash));
681
+ if (txResponse.status === "SUCCESS") {
682
+ const resultMetaXdr = txResponse.resultMetaXdr;
683
+ let cpuInstructions = undefined;
684
+ let memoryBytes = undefined;
685
+ if (resultMetaXdr) {
686
+ const costs = extractResourceCosts(resultMetaXdr);
687
+ if (costs) {
688
+ cpuInstructions = costs.cpuInstructions;
689
+ memoryBytes = costs.memoryBytes;
690
+ logger.info("Extracted transaction resource costs successfully", { txHash, cpuInstructions, memoryBytes });
691
+ }
692
+ }
693
+ const rawFee = txResponse.feeCharged;
694
+ const feeCharged = rawFee !== undefined ? Number(rawFee) : undefined;
695
+ return {
696
+ success: true,
697
+ txHash,
698
+ ledger: txResponse.ledger ?? txResponse.latestLedger,
699
+ cpuInstructions,
700
+ memoryBytes,
701
+ feeCharged,
702
+ };
703
+ }
704
+ if (txResponse.status === "FAILED") {
705
+ return {
706
+ success: false,
707
+ txHash,
708
+ ledger: txResponse.ledger ?? txResponse.latestLedger,
709
+ error: "Transaction failed on-chain",
710
+ };
711
+ }
712
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
713
+ }
714
+ return {
715
+ success: false,
716
+ txHash,
717
+ ledger: 0,
718
+ error: `Transaction polling timed out after ${maxAttempts} attempts`,
719
+ };
720
+ }
721
+ async withRateLimit(operation) {
722
+ const waitMs = this.acquireSlot();
723
+ if (waitMs > 0) {
724
+ await this.sleep(waitMs);
725
+ }
726
+ return operation();
727
+ }
728
+ acquireSlot() {
729
+ const now = Date.now();
730
+ const windowStart = now - 1000;
731
+ this.recentRequestTimes = this.recentRequestTimes.filter(t => t > windowStart);
732
+ if (this.recentRequestTimes.length < this.maxRequestsPerSecond) {
733
+ this.recentRequestTimes.push(now);
734
+ return 0;
735
+ }
736
+ // Wait until the slot-blocking request in the window falls out
737
+ const blockingRequest = this.recentRequestTimes[this.recentRequestTimes.length - this.maxRequestsPerSecond];
738
+ const waitMs = blockingRequest + 1000 - now;
739
+ this.recentRequestTimes.push(now + waitMs);
740
+ return waitMs;
741
+ }
742
+ /** @deprecated Use acquireSlot-based withRateLimit instead. Kept for compatibility. */
743
+ calculateWaitMs() {
744
+ const now = Date.now();
745
+ const windowStart = now - 1000;
746
+ const recent = this.recentRequestTimes.filter(t => t > windowStart);
747
+ if (recent.length < this.maxRequestsPerSecond)
748
+ return 0;
749
+ const oldest = recent[0];
750
+ return Math.max(0, oldest + 1000 - now);
751
+ }
752
+ async sleep(ms) {
753
+ if (ms <= 0)
754
+ return;
755
+ await new Promise((resolve) => setTimeout(resolve, ms));
756
+ }
757
+ }
758
+ function parseFeeStat(value) {
759
+ if (value === undefined)
760
+ return 0;
761
+ if (typeof value === "bigint")
762
+ return Number(value);
763
+ const parsed = Number(value);
764
+ return Number.isFinite(parsed) ? parsed : 0;
765
+ }
766
+ //# sourceMappingURL=client.js.map