@hsuite/smart-engines-sdk 3.0.3 → 3.2.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.
package/dist/index.js CHANGED
@@ -1014,6 +1014,7 @@ zod.z.object({
1014
1014
  // src/discovery/index.ts
1015
1015
  var discovery_exports = {};
1016
1016
  __export(discovery_exports, {
1017
+ ClusterDiscoveryClient: () => ClusterDiscoveryClient,
1017
1018
  MIRROR_NODE_URLS: () => MIRROR_NODE_URLS,
1018
1019
  MirrorNodeClient: () => MirrorNodeClient,
1019
1020
  MirrorNodeError: () => MirrorNodeError,
@@ -1386,6 +1387,185 @@ function normalizeRegistryEntry(raw) {
1386
1387
  return null;
1387
1388
  }
1388
1389
 
1390
+ // src/discovery/cluster-discovery.ts
1391
+ var ClusterDiscoveryClient = class {
1392
+ bootstrap;
1393
+ cacheTtlMs;
1394
+ fetchTimeoutMs;
1395
+ allowInsecure;
1396
+ trustAnchorClient;
1397
+ cache = null;
1398
+ constructor(config) {
1399
+ if (!config.bootstrap || config.bootstrap.length === 0) {
1400
+ throw new Error("ClusterDiscoveryClient: bootstrap must list at least one seed URL");
1401
+ }
1402
+ if (!config.allowInsecure) {
1403
+ for (const seed of config.bootstrap) {
1404
+ if (!seed.startsWith("https://")) {
1405
+ throw new Error(
1406
+ `ClusterDiscoveryClient: bootstrap seed "${seed}" is not HTTPS. Set allowInsecure=true for local development only.`
1407
+ );
1408
+ }
1409
+ }
1410
+ }
1411
+ this.bootstrap = config.bootstrap;
1412
+ this.cacheTtlMs = config.cacheTtlMs ?? 6e4;
1413
+ this.fetchTimeoutMs = config.fetchTimeoutMs ?? 5e3;
1414
+ this.allowInsecure = config.allowInsecure ?? false;
1415
+ this.trustAnchorClient = config.trustAnchor ? new ValidatorDiscoveryClient(config.trustAnchor) : null;
1416
+ }
1417
+ /**
1418
+ * Fetch the active-cluster set, with caching.
1419
+ *
1420
+ * Tries each bootstrap seed in order until one returns HTTP 200 with a
1421
+ * parseable cluster list. If all seeds fail, throws — callers can
1422
+ * handle by falling back to a previously-cached value if they want,
1423
+ * or by initializing with multiple seeds.
1424
+ */
1425
+ async getClusters(forceRefresh = false) {
1426
+ if (!forceRefresh && this.cache && this.isCacheValid()) {
1427
+ return this.cache.clusters;
1428
+ }
1429
+ const fetched = await this.fetchFromFirstAvailableSeed();
1430
+ const verified = this.trustAnchorClient ? await this.verifyAgainstTrustAnchor(fetched) : fetched;
1431
+ this.cache = { clusters: verified, lastUpdated: Date.now() };
1432
+ return verified;
1433
+ }
1434
+ /**
1435
+ * Random pick over the active-cluster set. Returns `null` when no
1436
+ * cluster passes verification (empty registry / all-rejected anchor).
1437
+ *
1438
+ * Uses `crypto.getRandomValues` when available (browsers, Deno, modern
1439
+ * Node) for cryptographic-strength randomness; `Math.random` is a
1440
+ * fallback for older runtimes where `globalThis.crypto` is undefined.
1441
+ * Discovery isn't cryptographically sensitive — load distribution is
1442
+ * the goal here.
1443
+ */
1444
+ async getRandomCluster(forceRefresh = false) {
1445
+ const clusters = await this.getClusters(forceRefresh);
1446
+ if (clusters.length === 0) return null;
1447
+ return clusters[this.pickRandomIndex(clusters.length)];
1448
+ }
1449
+ /**
1450
+ * Convenience wrapper returning just the gateway URL of a random
1451
+ * active cluster. The single most-used SDK entry point — most
1452
+ * downstream callers want `new SmartEngineClient({ baseUrl: ... })`
1453
+ * and don't care about the rest of the metadata.
1454
+ */
1455
+ async getRandomGatewayUrl(forceRefresh = false) {
1456
+ const cluster = await this.getRandomCluster(forceRefresh);
1457
+ return cluster?.endpoints.gatewayUrl ?? null;
1458
+ }
1459
+ /** Per-clusterId lookup, useful for "stick the SDK to cluster X" flows. */
1460
+ async getClusterById(clusterId, forceRefresh = false) {
1461
+ const clusters = await this.getClusters(forceRefresh);
1462
+ return clusters.find((c) => c.clusterId === clusterId) ?? null;
1463
+ }
1464
+ clearCache() {
1465
+ this.cache = null;
1466
+ }
1467
+ isCacheValid() {
1468
+ if (!this.cache) return false;
1469
+ return Date.now() - this.cache.lastUpdated < this.cacheTtlMs;
1470
+ }
1471
+ async fetchFromFirstAvailableSeed() {
1472
+ const errors = [];
1473
+ for (const seed of this.bootstrap) {
1474
+ try {
1475
+ return await this.fetchClustersFromSeed(seed);
1476
+ } catch (err) {
1477
+ errors.push(`${seed}: ${err.message}`);
1478
+ }
1479
+ }
1480
+ throw new Error(
1481
+ `ClusterDiscoveryClient: no bootstrap seed reachable. Attempts:
1482
+ ${errors.join("\n ")}`
1483
+ );
1484
+ }
1485
+ async fetchClustersFromSeed(seed) {
1486
+ const url = `${seed.replace(/\/$/, "")}/api/v3/discovery/clusters`;
1487
+ const ctrl = new AbortController();
1488
+ const timer = setTimeout(() => ctrl.abort(), this.fetchTimeoutMs);
1489
+ try {
1490
+ const res = await fetch(url, { signal: ctrl.signal });
1491
+ if (!res.ok) {
1492
+ throw new Error(`HTTP ${res.status}`);
1493
+ }
1494
+ const body = await res.json();
1495
+ if (!Array.isArray(body.clusters)) {
1496
+ throw new Error("response missing clusters[]");
1497
+ }
1498
+ return body.clusters.map((c) => this.normalizeClusterEntry(c)).filter((c) => c !== null);
1499
+ } finally {
1500
+ clearTimeout(timer);
1501
+ }
1502
+ }
1503
+ normalizeClusterEntry(raw) {
1504
+ if (typeof raw.clusterId !== "string" || !raw.clusterId) return null;
1505
+ if (!raw.endpoints || typeof raw.endpoints !== "object") return null;
1506
+ const ep = raw.endpoints;
1507
+ if (typeof ep.gatewayUrl !== "string" || !ep.gatewayUrl) return null;
1508
+ if (!this.allowInsecure && !ep.gatewayUrl.startsWith("https://")) {
1509
+ return null;
1510
+ }
1511
+ const nodeIds = Array.isArray(raw.nodeIds) ? raw.nodeIds.filter((n) => typeof n === "string") : [];
1512
+ return {
1513
+ clusterId: raw.clusterId,
1514
+ endpoints: {
1515
+ clusterId: raw.clusterId,
1516
+ gatewayUrl: ep.gatewayUrl,
1517
+ harborUrl: typeof ep.harborUrl === "string" ? ep.harborUrl : void 0,
1518
+ natsUrl: typeof ep.natsUrl === "string" ? ep.natsUrl : void 0,
1519
+ publicIp: typeof ep.publicIp === "string" ? ep.publicIp : void 0,
1520
+ region: typeof ep.region === "string" ? ep.region : void 0
1521
+ },
1522
+ nodeIds
1523
+ };
1524
+ }
1525
+ /**
1526
+ * Cross-check the HTTP-fetched cluster set against the HCS validator
1527
+ * registry. Clusters whose nodeIds are not on-chain are dropped.
1528
+ *
1529
+ * Two failure modes are deliberately silent here:
1530
+ * - the HCS read itself throws → original list is returned
1531
+ * un-verified. Trust-anchor is advisory; a network partition
1532
+ * between SDK and Hedera mirror shouldn't strand the SDK.
1533
+ * - the HCS read returns empty (mirror node lag, wrong topicId)
1534
+ * → original list is returned. Better to keep working off
1535
+ * possibly-stale-but-real data than reject every cluster.
1536
+ *
1537
+ * Both behaviors are why this is called "trust *anchor*", not
1538
+ * "trust gate". Crypto-strong gating is a follow-up.
1539
+ */
1540
+ async verifyAgainstTrustAnchor(clusters) {
1541
+ if (!this.trustAnchorClient || clusters.length === 0) return clusters;
1542
+ let onChainNodeIds;
1543
+ try {
1544
+ const validators = await this.trustAnchorClient.getValidators();
1545
+ if (validators.length === 0) return clusters;
1546
+ onChainNodeIds = new Set(validators.map((v) => v.nodeId));
1547
+ } catch {
1548
+ return clusters;
1549
+ }
1550
+ return clusters.filter((c) => {
1551
+ if (c.nodeIds.length === 0) {
1552
+ return false;
1553
+ }
1554
+ return c.nodeIds.some((id) => onChainNodeIds.has(id));
1555
+ });
1556
+ }
1557
+ pickRandomIndex(n) {
1558
+ if (n <= 1) return 0;
1559
+ const g = globalThis.crypto;
1560
+ if (g?.getRandomValues) {
1561
+ const buf = new Uint32Array(1);
1562
+ g.getRandomValues(buf);
1563
+ return buf[0] % n;
1564
+ }
1565
+ return Math.floor(Math.random() * n);
1566
+ }
1567
+ };
1568
+
1389
1569
  // src/auth/index.ts
1390
1570
  var auth_exports = {};
1391
1571
  __export(auth_exports, {
@@ -1784,6 +1964,17 @@ function createHttpClient(config) {
1784
1964
  function encodePathParam(param) {
1785
1965
  return encodeURIComponent(param).replace(/%2F/gi, "");
1786
1966
  }
1967
+ function isRuleRejected(err) {
1968
+ if (!(err instanceof SdkHttpError)) return false;
1969
+ if (err.statusCode !== 403) return false;
1970
+ const d = err.details;
1971
+ if (d === null || typeof d !== "object") return false;
1972
+ const obj = d;
1973
+ if (obj.error !== "rule_rejected") return false;
1974
+ if (typeof obj.reason !== "string") return false;
1975
+ if (!Array.isArray(obj.ruleAtoms)) return false;
1976
+ return obj.ruleAtoms.every((a) => typeof a === "string");
1977
+ }
1787
1978
 
1788
1979
  // src/subscription/index.ts
1789
1980
  var subscription_exports = {};
@@ -2037,80 +2228,134 @@ var IPFSClient = class {
2037
2228
  }
2038
2229
  };
2039
2230
 
2231
+ // src/transactions/chains/hedera.ts
2232
+ var HederaTransactionsClient = class {
2233
+ constructor(http) {
2234
+ this.http = http;
2235
+ }
2236
+ http;
2237
+ /** Prepare an HCS topic creation transaction. */
2238
+ async prepareTopicCreate(request) {
2239
+ return this.http.post("/topic/create/prepare", { ...request, chain: "hedera" });
2240
+ }
2241
+ /** Prepare an HCS topic message submission. */
2242
+ async prepareTopicMessage(request) {
2243
+ return this.http.post("/topic/message/prepare", { ...request, chain: "hedera" });
2244
+ }
2245
+ /** Prepare a token compliance-enable (KYC grant) transaction. */
2246
+ async prepareTokenComplianceEnable(request) {
2247
+ return this.http.post("/token/compliance/enable/prepare", { ...request, chain: "hedera" });
2248
+ }
2249
+ /** Prepare a token compliance-disable (KYC revoke) transaction. */
2250
+ async prepareTokenComplianceDisable(request) {
2251
+ return this.http.post("/token/compliance/disable/prepare", { ...request, chain: "hedera" });
2252
+ }
2253
+ /** Prepare a token wipe transaction (force-remove tokens from an account). */
2254
+ async prepareTokenWipe(request) {
2255
+ return this.http.post("/token/wipe/prepare", { ...request, chain: "hedera" });
2256
+ }
2257
+ };
2258
+
2259
+ // src/transactions/chains/xrpl.ts
2260
+ var XrplTransactionsClient = class {
2261
+ constructor(http) {
2262
+ this.http = http;
2263
+ }
2264
+ http;
2265
+ /** Prepare an XRPL TrustSet (trust line) transaction. */
2266
+ async prepareTrustLine(request) {
2267
+ return this.http.post("/trustline/prepare", { ...request, chain: "xrpl" });
2268
+ }
2269
+ };
2270
+
2271
+ // src/transactions/chains/solana.ts
2272
+ var SolanaTransactionsClient = class {
2273
+ constructor(http) {
2274
+ this.http = http;
2275
+ }
2276
+ http;
2277
+ /** Prepare an SPL Token close-account transaction. */
2278
+ async prepareTokenCloseAccount(request) {
2279
+ return this.http.post("/token/close-account/prepare", { ...request, chain: "solana" });
2280
+ }
2281
+ };
2282
+
2283
+ // src/transactions/chains/polkadot.ts
2284
+ var PolkadotTransactionsClient = class {
2285
+ constructor(http) {
2286
+ this.http = http;
2287
+ }
2288
+ http;
2289
+ /** Prepare a pallet-assets.setTeam transaction. */
2290
+ async prepareAssetSetTeam(request) {
2291
+ return this.http.post("/token/set-team/prepare", { ...request, chain: "polkadot" });
2292
+ }
2293
+ /** Prepare a pallet-assets.setMetadata transaction. */
2294
+ async prepareAssetSetMetadata(request) {
2295
+ return this.http.post("/token/set-metadata/prepare", { ...request, chain: "polkadot" });
2296
+ }
2297
+ };
2298
+
2040
2299
  // src/transactions/index.ts
2041
2300
  var TransactionsClient = class {
2042
2301
  constructor(http) {
2043
2302
  this.http = http;
2044
2303
  }
2045
2304
  http;
2046
- /**
2047
- * Get transaction preparation service info
2048
- */
2305
+ /** Get transaction preparation service info. */
2049
2306
  async getInfo() {
2050
2307
  return this.http.get("/info");
2051
2308
  }
2052
- /**
2053
- * Prepare a transfer transaction for local signing
2054
- */
2309
+ /** Prepare a transfer transaction (any supported chain). */
2055
2310
  async prepareTransfer(request) {
2056
2311
  return this.http.post("/transfer/prepare", request);
2057
2312
  }
2058
- /**
2059
- * Prepare an NFT mint transaction
2060
- */
2313
+ /** Prepare an NFT mint transaction (any supported chain). */
2061
2314
  async prepareNftMint(request) {
2062
2315
  return this.http.post("/nft/mint/prepare", request);
2063
2316
  }
2064
- /**
2065
- * Prepare an NFT burn transaction
2066
- */
2317
+ /** Prepare an NFT burn transaction (any supported chain). */
2067
2318
  async prepareNftBurn(request) {
2068
2319
  return this.http.post("/nft/burn/prepare", request);
2069
2320
  }
2070
- /**
2071
- * Prepare an NFT transfer transaction
2072
- */
2321
+ /** Prepare an NFT transfer transaction (any supported chain). */
2073
2322
  async prepareNftTransfer(request) {
2074
2323
  return this.http.post("/nft/transfer/prepare", request);
2075
2324
  }
2076
- /**
2077
- * Prepare a token creation transaction
2078
- */
2325
+ /** Prepare a token creation transaction (Hedera, Solana, Polkadot, Cardano). */
2079
2326
  async prepareTokenCreate(request) {
2080
2327
  return this.http.post("/token/create/prepare", request);
2081
2328
  }
2082
- /**
2083
- * Prepare a token mint transaction
2084
- */
2329
+ /** Prepare a token mint transaction (any supported chain). */
2085
2330
  async prepareTokenMint(request) {
2086
2331
  return this.http.post("/token/mint/prepare", request);
2087
2332
  }
2088
2333
  /**
2089
- * Prepare a fungible token burn transaction (Hedera).
2090
- *
2091
- * For NFT burn use `prepareNftBurn` with a serialNumber.
2334
+ * Prepare a fungible token burn transaction (Hedera, Solana, Polkadot,
2335
+ * Cardano). For NFT burn use `prepareNftBurn` with a `serialNumber`.
2092
2336
  */
2093
2337
  async prepareTokenBurn(request) {
2094
2338
  return this.http.post("/token/burn/prepare", request);
2095
2339
  }
2096
2340
  /**
2097
- * Prepare a token association transaction
2341
+ * Prepare a token association transaction (Hedera HTS associate / Solana
2342
+ * ATA create). On chains that don't need explicit association (XRPL uses
2343
+ * trustlines — see `client.xrpl.prepareTrustLine`), the server returns 400.
2098
2344
  */
2099
- async prepareTokenAssociation(request) {
2345
+ async prepareTokenAssociate(request) {
2100
2346
  return this.http.post("/token/associate/prepare", request);
2101
2347
  }
2102
- // ── Capability actions (Hedera) ──────────────────────────────────────
2103
- /** Prepare a token pause transaction (capability: pausable). */
2348
+ /** Prepare a token pause transaction (Hedera, Polkadot, Solana). */
2104
2349
  async prepareTokenPause(request) {
2105
2350
  return this.http.post("/token/pause/prepare", request);
2106
2351
  }
2107
- /** Prepare a token unpause transaction (capability: pausable). */
2352
+ /** Prepare a token unpause transaction (Hedera, Polkadot, Solana). */
2108
2353
  async prepareTokenUnpause(request) {
2109
2354
  return this.http.post("/token/unpause/prepare", request);
2110
2355
  }
2111
2356
  /**
2112
- * Prepare a token restrict (freeze account) transaction (capability:
2113
- * restrictable). Freezes `accountId` from transacting `tokenId`.
2357
+ * Prepare a token restrict (freeze account) transaction (Hedera, Polkadot,
2358
+ * Solana). Freezes `accountId`/`who`/`account` from transacting the token.
2114
2359
  */
2115
2360
  async prepareTokenRestrict(request) {
2116
2361
  return this.http.post("/token/restrict/prepare", request);
@@ -2119,42 +2364,6 @@ var TransactionsClient = class {
2119
2364
  async prepareTokenUnrestrict(request) {
2120
2365
  return this.http.post("/token/unrestrict/prepare", request);
2121
2366
  }
2122
- /**
2123
- * Prepare a token compliance-enable (grant KYC) transaction (capability:
2124
- * compliant).
2125
- */
2126
- async prepareTokenComplianceEnable(request) {
2127
- return this.http.post("/token/compliance/enable/prepare", request);
2128
- }
2129
- /** Prepare a token compliance-disable (revoke KYC) transaction. */
2130
- async prepareTokenComplianceDisable(request) {
2131
- return this.http.post("/token/compliance/disable/prepare", request);
2132
- }
2133
- /**
2134
- * Prepare a token wipe transaction (capability: wipeable). Force-removes
2135
- * `amount` of `tokenId` from `accountId`.
2136
- */
2137
- async prepareTokenWipe(request) {
2138
- return this.http.post("/token/wipe/prepare", request);
2139
- }
2140
- /**
2141
- * Prepare a topic creation transaction
2142
- */
2143
- async prepareTopicCreate(request) {
2144
- return this.http.post("/topic/create/prepare", request);
2145
- }
2146
- /**
2147
- * Prepare a topic message submission transaction
2148
- */
2149
- async prepareTopicMessage(request) {
2150
- return this.http.post("/topic/message/prepare", request);
2151
- }
2152
- /**
2153
- * Prepare a trust line transaction (e.g. XRPL trust lines)
2154
- */
2155
- async prepareTrustLine(request) {
2156
- return this.http.post("/trustline/prepare", request);
2157
- }
2158
2367
  };
2159
2368
 
2160
2369
  // src/snapshots/index.ts
@@ -2207,6 +2416,167 @@ var SnapshotsClient = class {
2207
2416
  }
2208
2417
  };
2209
2418
 
2419
+ // src/historical-balance/historical-balance-client.ts
2420
+ var DEFAULT_TIMEOUT_MS = 3e4;
2421
+ var ENDPOINT_PATH = "/api/v3/historical-balance/query";
2422
+ var HistoricalBalanceClientError = class extends Error {
2423
+ constructor(message, statusCode, details) {
2424
+ super(message);
2425
+ this.statusCode = statusCode;
2426
+ this.details = details;
2427
+ this.name = "HistoricalBalanceClientError";
2428
+ }
2429
+ statusCode;
2430
+ details;
2431
+ };
2432
+ var HistoricalBalanceClient = class _HistoricalBalanceClient {
2433
+ // Standalone-mode fields (set when the client builds its own fetch).
2434
+ baseUrl;
2435
+ authToken;
2436
+ apiKey;
2437
+ timeoutMs;
2438
+ fetchImpl;
2439
+ // Sub-client-mode field (set when wired via SmartEngineClient).
2440
+ http;
2441
+ constructor(config) {
2442
+ if ("http" in config) {
2443
+ this.http = config.http;
2444
+ this.timeoutMs = DEFAULT_TIMEOUT_MS;
2445
+ return;
2446
+ }
2447
+ if (!config.baseUrl) {
2448
+ throw new HistoricalBalanceClientError(
2449
+ "HistoricalBalanceClient: baseUrl is required for standalone construction",
2450
+ 400
2451
+ );
2452
+ }
2453
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
2454
+ this.authToken = config.authToken;
2455
+ this.apiKey = config.apiKey;
2456
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2457
+ this.fetchImpl = config.fetchImpl;
2458
+ }
2459
+ /**
2460
+ * Factory used by `SmartEngineClient` to wire this sub-client onto the
2461
+ * parent's shared `HttpClient` (which already carries auth + baseUrl + a
2462
+ * `/api/v3` prefix). Routes through `/historical-balance/query` since the
2463
+ * parent's HttpClient is rooted at `/api/v3`.
2464
+ */
2465
+ static fromHttp(http) {
2466
+ return new _HistoricalBalanceClient({ http });
2467
+ }
2468
+ /**
2469
+ * Query a point-in-time balance.
2470
+ *
2471
+ * The validator returns the on-chain bigint as a base-10 decimal string.
2472
+ * Callers needing arithmetic precision should wrap the result:
2473
+ *
2474
+ * ```ts
2475
+ * const { balance } = await client.historicalBalance.getBalance({ ... });
2476
+ * const value = BigInt(balance);
2477
+ * ```
2478
+ */
2479
+ async getBalance(params) {
2480
+ this.validateParams(params);
2481
+ if (this.http) {
2482
+ return this.http.post("/historical-balance/query", params);
2483
+ }
2484
+ return this.standaloneFetch(params);
2485
+ }
2486
+ validateParams(params) {
2487
+ if (!params || typeof params !== "object") {
2488
+ throw new HistoricalBalanceClientError(
2489
+ "HistoricalBalanceClient.getBalance: params object is required",
2490
+ 400
2491
+ );
2492
+ }
2493
+ const { chain, entityId, account, atTimestamp } = params;
2494
+ if (chain !== "hedera" && chain !== "xrpl" && chain !== "polkadot") {
2495
+ throw new HistoricalBalanceClientError(
2496
+ `HistoricalBalanceClient.getBalance: unsupported chain "${String(chain)}" (expected hedera | xrpl | polkadot)`,
2497
+ 400
2498
+ );
2499
+ }
2500
+ if (typeof entityId !== "string" || entityId.length === 0) {
2501
+ throw new HistoricalBalanceClientError(
2502
+ "HistoricalBalanceClient.getBalance: entityId must be a non-empty string",
2503
+ 400
2504
+ );
2505
+ }
2506
+ if (typeof account !== "string" || account.length === 0) {
2507
+ throw new HistoricalBalanceClientError(
2508
+ "HistoricalBalanceClient.getBalance: account must be a non-empty string",
2509
+ 400
2510
+ );
2511
+ }
2512
+ if (typeof atTimestamp !== "number" || !Number.isInteger(atTimestamp) || atTimestamp <= 0) {
2513
+ throw new HistoricalBalanceClientError(
2514
+ "HistoricalBalanceClient.getBalance: atTimestamp must be a positive integer (unix seconds)",
2515
+ 400
2516
+ );
2517
+ }
2518
+ }
2519
+ async standaloneFetch(params) {
2520
+ const url = `${this.baseUrl}${ENDPOINT_PATH}`;
2521
+ const headers = {
2522
+ "Content-Type": "application/json",
2523
+ Accept: "application/json"
2524
+ };
2525
+ if (this.authToken) headers.Authorization = `Bearer ${this.authToken}`;
2526
+ if (this.apiKey) headers["X-API-Key"] = this.apiKey;
2527
+ const fetchFn = this.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
2528
+ if (!fetchFn) {
2529
+ throw new HistoricalBalanceClientError(
2530
+ "HistoricalBalanceClient: no fetch implementation available (provide fetchImpl)",
2531
+ 500
2532
+ );
2533
+ }
2534
+ const controller = new AbortController();
2535
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
2536
+ let response;
2537
+ try {
2538
+ response = await fetchFn(url, {
2539
+ method: "POST",
2540
+ headers,
2541
+ body: JSON.stringify(params),
2542
+ signal: controller.signal
2543
+ });
2544
+ } catch (err) {
2545
+ clearTimeout(timer);
2546
+ const message = err instanceof Error ? err.message : String(err);
2547
+ throw new HistoricalBalanceClientError(
2548
+ `HistoricalBalanceClient: transport error: ${message}`,
2549
+ 0,
2550
+ err
2551
+ );
2552
+ }
2553
+ clearTimeout(timer);
2554
+ let body;
2555
+ const text = await response.text();
2556
+ if (text.length > 0) {
2557
+ try {
2558
+ body = JSON.parse(text);
2559
+ } catch (err) {
2560
+ const message = err instanceof Error ? err.message : String(err);
2561
+ throw new HistoricalBalanceClientError(
2562
+ `HistoricalBalanceClient: non-JSON response (status=${response.status}): ${message}`,
2563
+ response.status,
2564
+ { raw: text }
2565
+ );
2566
+ }
2567
+ }
2568
+ if (!response.ok) {
2569
+ const detail = body && typeof body === "object" && "message" in body ? String(body.message) : `HTTP ${response.status}`;
2570
+ throw new HistoricalBalanceClientError(
2571
+ `HistoricalBalanceClient: ${detail}`,
2572
+ response.status,
2573
+ body
2574
+ );
2575
+ }
2576
+ return body;
2577
+ }
2578
+ };
2579
+
2210
2580
  // src/settlement/index.ts
2211
2581
  var settlement_exports = {};
2212
2582
  __export(settlement_exports, {
@@ -2245,6 +2615,75 @@ var SettlementClient = class {
2245
2615
  }
2246
2616
  };
2247
2617
 
2618
+ // src/governance/index.ts
2619
+ var governance_exports = {};
2620
+ __export(governance_exports, {
2621
+ GovernanceClient: () => GovernanceClient
2622
+ });
2623
+
2624
+ // src/governance/governance-client.ts
2625
+ var GovernanceClient = class {
2626
+ constructor(http) {
2627
+ this.http = http;
2628
+ }
2629
+ http;
2630
+ /**
2631
+ * Dry-run a governance proposal/vote/etc. against `GovernanceMolecule.validate(...)`.
2632
+ *
2633
+ * Returns whatever `ValidationResult` the molecule produced. A `false`
2634
+ * `isValid` is **not** an error — it is a successful "this proposal is
2635
+ * invalid" outcome and is surfaced via the normal return value.
2636
+ *
2637
+ * HTTP errors (400 malformed body, 500 unexpected) bubble up as
2638
+ * `SdkHttpError` via the shared `HttpClient` layer.
2639
+ */
2640
+ async simulate(params) {
2641
+ return this.http.post("/governance/simulate", params);
2642
+ }
2643
+ };
2644
+
2645
+ // src/personhood/index.ts
2646
+ var personhood_exports = {};
2647
+ __export(personhood_exports, {
2648
+ PERSONHOOD_VERIFIER_NOT_CONFIGURED: () => PERSONHOOD_VERIFIER_NOT_CONFIGURED,
2649
+ PersonhoodClient: () => PersonhoodClient,
2650
+ isPersonhoodVerifierNotConfigured: () => isPersonhoodVerifierNotConfigured
2651
+ });
2652
+
2653
+ // src/personhood/personhood-client.ts
2654
+ var PERSONHOOD_VERIFIER_NOT_CONFIGURED = "personhood_verifier_not_configured";
2655
+ var PersonhoodClient = class {
2656
+ constructor(http) {
2657
+ this.http = http;
2658
+ }
2659
+ http;
2660
+ /**
2661
+ * Verify a personhood proof for `candidate`.
2662
+ *
2663
+ * Returns the issued cert on accept, `null` on clean rejection. All
2664
+ * other failure modes (validation, transport, 5xx) propagate as
2665
+ * `SdkHttpError`.
2666
+ */
2667
+ async verify(params) {
2668
+ const result = await this.http.post(
2669
+ "/personhood/verify",
2670
+ {
2671
+ candidate: params.candidate,
2672
+ proof: params.proof
2673
+ }
2674
+ );
2675
+ if (result === void 0) return null;
2676
+ return result;
2677
+ }
2678
+ };
2679
+ function isPersonhoodVerifierNotConfigured(err) {
2680
+ if (!(err instanceof SdkHttpError)) return false;
2681
+ if (err.statusCode !== 503) return false;
2682
+ const d = err.details;
2683
+ if (d === null || typeof d !== "object") return false;
2684
+ return d.error === PERSONHOOD_VERIFIER_NOT_CONFIGURED;
2685
+ }
2686
+
2248
2687
  // src/client.ts
2249
2688
  var SmartEngineClient = class _SmartEngineClient {
2250
2689
  baseUrl;
@@ -2261,12 +2700,26 @@ var SmartEngineClient = class _SmartEngineClient {
2261
2700
  tss;
2262
2701
  /** IPFS decentralized file storage */
2263
2702
  ipfs;
2264
- /** Transaction preparation for local signing (sovereignty model) */
2703
+ /** Transaction preparation for local signing (sovereignty model) — chain-agnostic */
2265
2704
  transactions;
2705
+ /** Hedera-specific transaction preparation (HCS topics, KYC, wipe) */
2706
+ hedera;
2707
+ /** XRPL-specific transaction preparation (trust lines) */
2708
+ xrpl;
2709
+ /** Solana-specific transaction preparation (close-account) */
2710
+ solana;
2711
+ /** Polkadot-specific transaction preparation (asset setTeam/setMetadata) */
2712
+ polkadot;
2266
2713
  /** Token holder snapshot generation and retrieval */
2267
2714
  snapshots;
2715
+ /** Historical balance archive reads (chain-native bigint, returned as decimal string) */
2716
+ historicalBalance;
2268
2717
  /** Cross-chain settlement operations */
2269
2718
  settlement;
2719
+ /** Governance proposal dry-run (simulate-only) */
2720
+ governance;
2721
+ /** Personhood verification (HPP one-human-one-member) */
2722
+ personhood;
2270
2723
  constructor(config) {
2271
2724
  this.allowInsecure = config.allowInsecure ?? false;
2272
2725
  this.baseUrl = validateClientUrl(config.baseUrl, this.allowInsecure);
@@ -2286,8 +2739,15 @@ var SmartEngineClient = class _SmartEngineClient {
2286
2739
  this.tss = new TSSClient(this.http);
2287
2740
  this.ipfs = new IPFSClient(this.http);
2288
2741
  this.transactions = new TransactionsClient(this.txHttp);
2742
+ this.hedera = new HederaTransactionsClient(this.txHttp);
2743
+ this.xrpl = new XrplTransactionsClient(this.txHttp);
2744
+ this.solana = new SolanaTransactionsClient(this.txHttp);
2745
+ this.polkadot = new PolkadotTransactionsClient(this.txHttp);
2289
2746
  this.snapshots = new SnapshotsClient(this.http);
2747
+ this.historicalBalance = HistoricalBalanceClient.fromHttp(this.http);
2290
2748
  this.settlement = new SettlementClient(this.http);
2749
+ this.governance = new GovernanceClient(this.http);
2750
+ this.personhood = new PersonhoodClient(this.http);
2291
2751
  }
2292
2752
  /**
2293
2753
  * Connect to the smart-engines network with auto-discovery and authentication
@@ -2332,6 +2792,67 @@ var SmartEngineClient = class _SmartEngineClient {
2332
2792
  });
2333
2793
  return { client, validator, session };
2334
2794
  }
2795
+ /**
2796
+ * Connect to the smart-engines network via the **service-registry**
2797
+ * (PR-1 of the cluster-discovery arc). Preferred over
2798
+ * {@link connectToNetwork} once the validator pods in the target network
2799
+ * have published their cluster endpoints — the SDK auto-balances across
2800
+ * the active cluster set and rides permissionless cluster join/leave
2801
+ * without code edits.
2802
+ *
2803
+ * Fallback ladder (per `docs/ops/HANDOFF-service-registry-distribution-layer.md` §6):
2804
+ * 1. HTTP fetch `/api/v3/discovery/clusters` from each bootstrap seed.
2805
+ * 2. (Optional) HCS trust-anchor membership cross-check.
2806
+ * 3. Random-pick over the verified set.
2807
+ *
2808
+ * @example
2809
+ * ```ts
2810
+ * const { client, cluster, session } = await SmartEngineClient.connectToCluster({
2811
+ * bootstrap: ['https://sn1.testnet.hsuite.network', 'https://sn2.testnet.hsuite.network'],
2812
+ * chain: 'xrpl',
2813
+ * address: '...',
2814
+ * publicKey: '...',
2815
+ * signFn: async (challenge) => sign(challenge),
2816
+ * });
2817
+ * ```
2818
+ */
2819
+ static async connectToCluster(config) {
2820
+ const allowInsecure = config.allowInsecure ?? false;
2821
+ const discovery = new ClusterDiscoveryClient({
2822
+ bootstrap: config.bootstrap,
2823
+ allowInsecure,
2824
+ trustAnchor: config.trustAnchor ? {
2825
+ network: config.trustAnchor.network,
2826
+ registryTopicId: config.trustAnchor.registryTopicId,
2827
+ mirrorNodeUrl: config.trustAnchor.mirrorNodeUrl,
2828
+ allowInsecure
2829
+ } : void 0
2830
+ });
2831
+ const cluster = await discovery.getRandomCluster();
2832
+ if (!cluster) {
2833
+ throw new SmartEngineError2(
2834
+ "No active clusters available via bootstrap seeds. Check bootstrap URLs and network reachability.",
2835
+ 503
2836
+ );
2837
+ }
2838
+ const gatewayUrl = cluster.endpoints.gatewayUrl;
2839
+ validateClientUrl(gatewayUrl, allowInsecure);
2840
+ const auth = new ValidatorAuthClient({ security: { allowInsecure } });
2841
+ const session = await auth.authenticateWithSigner(
2842
+ gatewayUrl,
2843
+ config.chain,
2844
+ config.address,
2845
+ config.publicKey,
2846
+ config.signFn,
2847
+ config.metadata
2848
+ );
2849
+ const client = new _SmartEngineClient({
2850
+ baseUrl: gatewayUrl,
2851
+ authToken: session.token,
2852
+ allowInsecure
2853
+ });
2854
+ return { client, cluster, session };
2855
+ }
2335
2856
  /** Get the current validator URL */
2336
2857
  getBaseUrl() {
2337
2858
  return this.baseUrl;
@@ -4137,11 +4658,18 @@ exports.DnsClient = DnsClient;
4137
4658
  exports.DomainsClient = DomainsClient;
4138
4659
  exports.ErrorCode = ErrorCode;
4139
4660
  exports.FunctionsClient = FunctionsClient;
4661
+ exports.GovernanceClient = GovernanceClient;
4662
+ exports.HederaTransactionsClient = HederaTransactionsClient;
4663
+ exports.HistoricalBalanceClient = HistoricalBalanceClient;
4664
+ exports.HistoricalBalanceClientError = HistoricalBalanceClientError;
4140
4665
  exports.IPFSClient = IPFSClient;
4141
4666
  exports.MIRROR_NODE_URLS = MIRROR_NODE_URLS;
4142
4667
  exports.MessagingClient = MessagingClient;
4143
4668
  exports.MirrorNodeClient = MirrorNodeClient;
4144
4669
  exports.MirrorNodeError = MirrorNodeError;
4670
+ exports.PERSONHOOD_VERIFIER_NOT_CONFIGURED = PERSONHOOD_VERIFIER_NOT_CONFIGURED;
4671
+ exports.PersonhoodClient = PersonhoodClient;
4672
+ exports.PolkadotTransactionsClient = PolkadotTransactionsClient;
4145
4673
  exports.RateLimiter = RateLimiter;
4146
4674
  exports.RoutingClient = RoutingClient;
4147
4675
  exports.SdkHttpError = SdkHttpError;
@@ -4150,6 +4678,7 @@ exports.SmartEngineClient = SmartEngineClient;
4150
4678
  exports.SmartEngineError = SmartEngineError;
4151
4679
  exports.SmartGatewayClient = SmartGatewayClient;
4152
4680
  exports.SnapshotsClient = SnapshotsClient;
4681
+ exports.SolanaTransactionsClient = SolanaTransactionsClient;
4153
4682
  exports.StorageClient = StorageClient;
4154
4683
  exports.SubscriptionClient = SubscriptionClient;
4155
4684
  exports.TSSClient = TSSClient;
@@ -4158,6 +4687,7 @@ exports.UnsupportedCapabilityError = UnsupportedCapabilityError;
4158
4687
  exports.ValidatorAuthClient = ValidatorAuthClient;
4159
4688
  exports.ValidatorAuthError = ValidatorAuthError;
4160
4689
  exports.ValidatorDiscoveryClient = ValidatorDiscoveryClient;
4690
+ exports.XrplTransactionsClient = XrplTransactionsClient;
4161
4691
  exports.auth = auth_exports;
4162
4692
  exports.baas = baas_exports;
4163
4693
  exports.chains = chains_exports;
@@ -4165,6 +4695,10 @@ exports.createHttpClient = createHttpClient;
4165
4695
  exports.createResilientFetchWithBreaker = createResilientFetchWithBreaker;
4166
4696
  exports.discovery = discovery_exports;
4167
4697
  exports.encodePathParam = encodePathParam;
4698
+ exports.governance = governance_exports;
4699
+ exports.isPersonhoodVerifierNotConfigured = isPersonhoodVerifierNotConfigured;
4700
+ exports.isRuleRejected = isRuleRejected;
4701
+ exports.personhood = personhood_exports;
4168
4702
  exports.resilientFetch = resilientFetch;
4169
4703
  exports.settlement = settlement_exports;
4170
4704
  exports.subscription = subscription_exports;