@pollar/core 0.7.0 → 0.8.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.mjs CHANGED
@@ -26,10 +26,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
 
27
27
  // ../../node_modules/@stellar/freighter-api/build/index.min.js
28
28
  var require_index_min = __commonJS({
29
- "../../node_modules/@stellar/freighter-api/build/index.min.js"(exports$1, module) {
29
+ "../../node_modules/@stellar/freighter-api/build/index.min.js"(exports, module) {
30
30
  !(function(e, r) {
31
- "object" == typeof exports$1 && "object" == typeof module ? module.exports = r() : "function" == typeof define && define.amd ? define([], r) : "object" == typeof exports$1 ? exports$1.freighterApi = r() : e.freighterApi = r();
32
- })(exports$1, (() => (() => {
31
+ "object" == typeof exports && "object" == typeof module ? module.exports = r() : "function" == typeof define && define.amd ? define([], r) : "object" == typeof exports ? exports.freighterApi = r() : e.freighterApi = r();
32
+ })(exports, (() => (() => {
33
33
  var e, r, E = { d: (e2, r2) => {
34
34
  for (var o2 in r2) E.o(r2, o2) && !E.o(e2, o2) && Object.defineProperty(e2, o2, { enumerable: true, get: r2[o2] });
35
35
  }, o: (e2, r2) => Object.prototype.hasOwnProperty.call(e2, r2), r: (e2) => {
@@ -323,9 +323,7 @@ var WebCryptoKeyManager = class {
323
323
  */
324
324
  this._initPromise = null;
325
325
  if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
326
- throw new Error(
327
- "[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost)."
328
- );
326
+ throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
329
327
  }
330
328
  this.apiKey = apiKey;
331
329
  }
@@ -440,7 +438,7 @@ function createClient(clientOptions) {
440
438
  const {
441
439
  baseUrl: localBaseUrl,
442
440
  fetch: fetch2 = baseFetch,
443
- Request = CustomRequest,
441
+ Request: Request2 = CustomRequest,
444
442
  headers,
445
443
  params = {},
446
444
  parseAs = "json",
@@ -492,7 +490,7 @@ function createClient(clientOptions) {
492
490
  };
493
491
  let id;
494
492
  let options;
495
- let request = new Request(
493
+ let request = new Request2(
496
494
  createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer, pathSerializer }),
497
495
  requestInit
498
496
  );
@@ -522,7 +520,7 @@ function createClient(clientOptions) {
522
520
  id
523
521
  });
524
522
  if (result) {
525
- if (result instanceof Request) {
523
+ if (result instanceof Request2) {
526
524
  request = result;
527
525
  } else if (result instanceof Response) {
528
526
  response = result;
@@ -894,24 +892,44 @@ function createApiClient(baseUrl) {
894
892
  return createClient({ baseUrl });
895
893
  }
896
894
 
895
+ // src/api/endpoints/distribution.ts
896
+ async function listDistributionRules(api) {
897
+ const { data, error } = await api.GET("/distribution/rules");
898
+ if (!data?.content || error) {
899
+ throw new Error(
900
+ error?.code ?? error?.error ?? "Failed to list distribution rules"
901
+ );
902
+ }
903
+ return data.content.rules;
904
+ }
905
+ async function claimDistributionRule(api, body) {
906
+ const { data, error } = await api.POST("/distribution/claim", { body });
907
+ if (!data?.content || error) {
908
+ throw new Error(
909
+ error?.code ?? error?.error ?? "Failed to claim distribution rule"
910
+ );
911
+ }
912
+ return data.content;
913
+ }
914
+
897
915
  // src/api/endpoints/kyc.ts
898
916
  async function getKycStatus(api, providerId) {
899
917
  const { data, error } = await api.GET("/kyc/status", {
900
918
  params: { query: providerId ? { providerId } : {} }
901
919
  });
902
920
  if (!data?.content || error) {
903
- throw new Error(error?.error ?? "Failed to get KYC status");
921
+ throw new Error(error?.code ?? error?.error ?? "Failed to get KYC status");
904
922
  }
905
923
  return data.content;
906
924
  }
907
925
  async function getKycProviders(api, country) {
908
926
  const { data, error } = await api.GET("/kyc/providers", { params: { query: { country } } });
909
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to get KYC providers");
927
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get KYC providers");
910
928
  return data.content;
911
929
  }
912
930
  async function startKyc(api, body) {
913
931
  const { data, error } = await api.POST("/kyc/start", { body });
914
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to start KYC");
932
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to start KYC");
915
933
  return data.content;
916
934
  }
917
935
  async function resolveKyc(api, providerId, level = "basic") {
@@ -933,22 +951,22 @@ async function pollKycStatus(api, providerId, { intervalMs = 3e3, timeoutMs = 3e
933
951
  // src/api/endpoints/ramps.ts
934
952
  async function getRampsQuote(api, query) {
935
953
  const { data, error } = await api.GET("/ramps/quote", { params: { query } });
936
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to get ramp quotes");
954
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get ramp quotes");
937
955
  return data.content;
938
956
  }
939
957
  async function createOnRamp(api, body) {
940
958
  const { data, error } = await api.POST("/ramps/onramp", { body });
941
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to create onramp");
959
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create onramp");
942
960
  return data.content;
943
961
  }
944
962
  async function createOffRamp(api, body) {
945
963
  const { data, error } = await api.POST("/ramps/offramp", { body });
946
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to create offramp");
964
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create offramp");
947
965
  return data.content;
948
966
  }
949
967
  async function getRampTransaction(api, txId) {
950
968
  const { data, error } = await api.GET("/ramps/transaction/{txId}", { params: { path: { txId } } });
951
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to get transaction");
969
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get transaction");
952
970
  return data.content;
953
971
  }
954
972
  async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e5 } = {}) {
@@ -1023,34 +1041,6 @@ function generateJti() {
1023
1041
  );
1024
1042
  }
1025
1043
 
1026
- // src/stellar/StellarClient.ts
1027
- var HORIZON_URLS = {
1028
- mainnet: "https://horizon.stellar.org",
1029
- testnet: "https://horizon-testnet.stellar.org"
1030
- };
1031
- var StellarClient = class {
1032
- constructor(config) {
1033
- this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
1034
- }
1035
- async submitTransaction(signedXdr) {
1036
- try {
1037
- const response = await fetch(`${this.horizonUrl}/transactions`, {
1038
- method: "POST",
1039
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1040
- body: new URLSearchParams({ tx: signedXdr })
1041
- });
1042
- if (!response.ok) {
1043
- const body = await response.json().catch(() => ({}));
1044
- return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
1045
- }
1046
- const data = await response.json();
1047
- return { success: true, hash: data.hash };
1048
- } catch {
1049
- return { success: false, errorCode: "NETWORK_ERROR" };
1050
- }
1051
- }
1052
- };
1053
-
1054
1044
  // src/storage/web.ts
1055
1045
  var LOG_PREFIX = "[PollarClient:storage]";
1056
1046
  function createMemoryAdapter() {
@@ -1138,6 +1128,57 @@ function defaultStorage(options = {}) {
1138
1128
  return createLocalStorageAdapter(options);
1139
1129
  }
1140
1130
 
1131
+ // src/visibility/noop.ts
1132
+ function createNoopVisibilityProvider() {
1133
+ return {
1134
+ isVisible: () => true,
1135
+ onChange: () => () => {
1136
+ }
1137
+ };
1138
+ }
1139
+
1140
+ // src/visibility/web.ts
1141
+ function createWebVisibilityProvider() {
1142
+ const isVisibleNow = () => typeof document === "undefined" || document.visibilityState === "visible";
1143
+ return {
1144
+ isVisible: isVisibleNow,
1145
+ onChange: (cb) => {
1146
+ if (typeof window === "undefined" || typeof document === "undefined") {
1147
+ return () => {
1148
+ };
1149
+ }
1150
+ let last = isVisibleNow();
1151
+ const handler = () => {
1152
+ const next = isVisibleNow();
1153
+ if (next !== last) {
1154
+ last = next;
1155
+ cb(next);
1156
+ }
1157
+ };
1158
+ document.addEventListener("visibilitychange", handler);
1159
+ window.addEventListener("pageshow", handler);
1160
+ window.addEventListener("pagehide", handler);
1161
+ window.addEventListener("focus", handler);
1162
+ window.addEventListener("blur", handler);
1163
+ return () => {
1164
+ document.removeEventListener("visibilitychange", handler);
1165
+ window.removeEventListener("pageshow", handler);
1166
+ window.removeEventListener("pagehide", handler);
1167
+ window.removeEventListener("focus", handler);
1168
+ window.removeEventListener("blur", handler);
1169
+ };
1170
+ }
1171
+ };
1172
+ }
1173
+
1174
+ // src/visibility/autodetect.ts
1175
+ function defaultVisibilityProvider() {
1176
+ if (typeof document !== "undefined" && typeof window !== "undefined") {
1177
+ return createWebVisibilityProvider();
1178
+ }
1179
+ return createNoopVisibilityProvider();
1180
+ }
1181
+
1141
1182
  // src/types.ts
1142
1183
  var AUTH_ERROR_CODES = {
1143
1184
  SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
@@ -1148,6 +1189,7 @@ var AUTH_ERROR_CODES = {
1148
1189
  AUTH_FAILED: "AUTH_FAILED",
1149
1190
  WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
1150
1191
  WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
1192
+ WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
1151
1193
  UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
1152
1194
  };
1153
1195
  var PollarFlowError = class extends Error {
@@ -1538,7 +1580,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1538
1580
  setAuthState({ step: "authenticating" });
1539
1581
  await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
1540
1582
  const dpopJwk = await deps.getPublicJwk();
1541
- const { data, error } = await api.POST("/auth/login", {
1583
+ const { data } = await api.POST("/auth/login", {
1542
1584
  body: {
1543
1585
  clientSessionId,
1544
1586
  dpopJwk,
@@ -1703,7 +1745,7 @@ async function loginWallet(type, deps) {
1703
1745
  let connectedWallet;
1704
1746
  try {
1705
1747
  setAuthState({ step: "connecting_wallet", walletType: type });
1706
- const adapter = await deps.resolveWalletAdapter(type);
1748
+ const adapter = await withSignal(deps.resolveWalletAdapter(type), signal);
1707
1749
  const available = await withSignal(adapter.isAvailable(), signal);
1708
1750
  if (!available) {
1709
1751
  setAuthState({ step: "wallet_not_installed", walletType: type });
@@ -1740,7 +1782,7 @@ async function loginWallet(type, deps) {
1740
1782
 
1741
1783
  // src/client/client.ts
1742
1784
  var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
1743
- var RETRIED_HEADER = "X-Pollar-Retried";
1785
+ var REFRESH_SKEW_SECONDS = 60;
1744
1786
  function warnServerSide(method) {
1745
1787
  console.warn(
1746
1788
  `[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
@@ -1758,9 +1800,21 @@ var PollarClient = class {
1758
1800
  this._profile = null;
1759
1801
  /** Last `DPoP-Nonce` we saw from a server response. Carried into the next proof. */
1760
1802
  this._dpopNonce = null;
1803
+ /**
1804
+ * Snapshot of each in-flight request's body, taken in `onRequest` before
1805
+ * `fetch()` consumes the stream. Needed because `Request.clone()` throws
1806
+ * once the body is disturbed, so the auto-retry path (DPoP nonce challenge
1807
+ * / 401 refresh) must rebuild the request from scratch instead of cloning.
1808
+ */
1809
+ this._requestBodyCache = /* @__PURE__ */ new WeakMap();
1761
1810
  /** Singleton in-flight refresh — concurrent 401s coalesce into one /auth/refresh call. */
1762
1811
  this._refreshPromise = null;
1763
1812
  this._storageEventHandler = null;
1813
+ /** Updated by the request middleware. Read by the silent-refresh scheduler
1814
+ * to skip proactive refreshes after `maxIdleMs` of no HTTP activity. */
1815
+ this._lastRequestAt = Date.now();
1816
+ this._refreshTimer = null;
1817
+ this._visibilityUnsubscribe = null;
1764
1818
  this._transactionState = null;
1765
1819
  this._transactionStateListeners = /* @__PURE__ */ new Set();
1766
1820
  this._txHistoryState = { step: "idle" };
@@ -1771,15 +1825,32 @@ var PollarClient = class {
1771
1825
  this._authStateListeners = /* @__PURE__ */ new Set();
1772
1826
  this._networkState = { step: "idle" };
1773
1827
  this._networkStateListeners = /* @__PURE__ */ new Set();
1828
+ /**
1829
+ * Latched once the storage adapter degrades. We dedupe (the adapter only
1830
+ * fires once anyway) and use it to replay state to late-subscribers — same
1831
+ * pattern as `onAuthStateChange` replaying `_authState` on subscribe.
1832
+ * Only populated when the SDK constructed the default storage adapter; if
1833
+ * the consumer passes `config.storage`, they own degradation notifications.
1834
+ */
1835
+ this._storageDegraded = null;
1836
+ this._storageDegradeListeners = /* @__PURE__ */ new Set();
1774
1837
  this._walletAdapter = null;
1775
1838
  this._loginController = null;
1776
1839
  this.apiKey = config.apiKey;
1777
1840
  this.id = crypto.randomUUID();
1778
1841
  this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1779
- this._storage = config.storage ?? defaultStorage(config.onStorageDegrade ? { onDegrade: config.onStorageDegrade } : void 0);
1842
+ this._storage = config.storage ?? defaultStorage({
1843
+ onDegrade: (reason, error) => {
1844
+ config.onStorageDegrade?.(reason, error);
1845
+ this._dispatchStorageDegrade(reason, error);
1846
+ }
1847
+ });
1780
1848
  this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
1781
1849
  this._walletAdapterResolver = config.walletAdapter ?? null;
1850
+ this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
1782
1851
  this._deviceLabel = config.deviceLabel;
1852
+ this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
1853
+ this._maxIdleMs = config.maxIdleMs;
1783
1854
  this._api = createApiClient(this.basePath);
1784
1855
  this._wireMiddlewares();
1785
1856
  this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
@@ -1825,6 +1896,9 @@ var PollarClient = class {
1825
1896
  console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
1826
1897
  }
1827
1898
  await this._restoreSession();
1899
+ this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
1900
+ if (visible) void this._maybeProactiveRefresh();
1901
+ });
1828
1902
  }
1829
1903
  /** Detach the cross-tab storage listener and abort any in-flight login. */
1830
1904
  destroy() {
@@ -1834,6 +1908,11 @@ var PollarClient = class {
1834
1908
  }
1835
1909
  this._loginController?.abort();
1836
1910
  this._loginController = null;
1911
+ this._clearRefreshTimer();
1912
+ if (this._visibilityUnsubscribe) {
1913
+ this._visibilityUnsubscribe();
1914
+ this._visibilityUnsubscribe = null;
1915
+ }
1837
1916
  }
1838
1917
  // ─── Middlewares (DPoP + auto-refresh) ────────────────────────────────────
1839
1918
  _wireMiddlewares() {
@@ -1841,7 +1920,15 @@ var PollarClient = class {
1841
1920
  this._api.use({
1842
1921
  onRequest: async ({ request }) => {
1843
1922
  request.headers.set("x-pollar-api-key", self.apiKey);
1923
+ self._lastRequestAt = Date.now();
1844
1924
  await self._initialized;
1925
+ if (request.body !== null) {
1926
+ try {
1927
+ self._requestBodyCache.set(request, await request.clone().arrayBuffer());
1928
+ } catch (err) {
1929
+ console.warn("[PollarClient] Could not snapshot request body for retry", err);
1930
+ }
1931
+ }
1845
1932
  const isRefresh = request.url.includes("/auth/refresh");
1846
1933
  if (!isRefresh && self._refreshPromise) await self._refreshPromise;
1847
1934
  if (isRefresh) {
@@ -1864,16 +1951,22 @@ var PollarClient = class {
1864
1951
  const newNonce = response.headers.get("DPoP-Nonce");
1865
1952
  if (newNonce) self._dpopNonce = newNonce;
1866
1953
  if (response.status !== 401) return response;
1867
- if (request.headers.get(RETRIED_HEADER)) return response;
1868
- if (request.url.includes("/auth/refresh")) return response;
1869
1954
  const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
1870
1955
  const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
1956
+ if (request.url.includes("/auth/refresh")) {
1957
+ if (isNonceChallenge) return self._retryRequest(request);
1958
+ return response;
1959
+ }
1871
1960
  if (!isNonceChallenge) {
1872
1961
  try {
1873
1962
  await self.refresh();
1874
1963
  } catch {
1875
1964
  return response;
1876
1965
  }
1966
+ const method = request.method.toUpperCase();
1967
+ if (method !== "GET" && method !== "HEAD") {
1968
+ return response;
1969
+ }
1877
1970
  }
1878
1971
  return self._retryRequest(request);
1879
1972
  }
@@ -1897,19 +1990,37 @@ var PollarClient = class {
1897
1990
  }
1898
1991
  }
1899
1992
  async _retryRequest(originalRequest) {
1900
- const clone = originalRequest.clone();
1901
- clone.headers.set(RETRIED_HEADER, "1");
1902
- const accessToken = this._session?.token?.accessToken;
1903
- if (accessToken) {
1904
- const proof = await this._buildProofForRequest(clone, accessToken);
1905
- if (proof) {
1906
- clone.headers.set("Authorization", `DPoP ${accessToken}`);
1907
- clone.headers.set("DPoP", proof);
1908
- } else {
1909
- clone.headers.set("Authorization", `Bearer ${accessToken}`);
1993
+ const headers = new Headers(originalRequest.headers);
1994
+ const isRefresh = originalRequest.url.includes("/auth/refresh");
1995
+ if (isRefresh) {
1996
+ const proof = await this._buildProofForRequest(originalRequest, void 0);
1997
+ headers.delete("Authorization");
1998
+ if (proof) headers.set("DPoP", proof);
1999
+ else headers.delete("DPoP");
2000
+ } else {
2001
+ const accessToken = this._session?.token?.accessToken;
2002
+ if (accessToken) {
2003
+ const proof = await this._buildProofForRequest(originalRequest, accessToken);
2004
+ if (proof) {
2005
+ headers.set("Authorization", `DPoP ${accessToken}`);
2006
+ headers.set("DPoP", proof);
2007
+ } else {
2008
+ headers.set("Authorization", `Bearer ${accessToken}`);
2009
+ }
1910
2010
  }
1911
2011
  }
1912
- return fetch(clone);
2012
+ const cachedBody = this._requestBodyCache.get(originalRequest);
2013
+ const retried = new Request(originalRequest.url, {
2014
+ method: originalRequest.method,
2015
+ headers,
2016
+ body: cachedBody ?? null,
2017
+ credentials: originalRequest.credentials,
2018
+ mode: originalRequest.mode,
2019
+ redirect: originalRequest.redirect,
2020
+ referrer: originalRequest.referrer,
2021
+ integrity: originalRequest.integrity
2022
+ });
2023
+ return fetch(retried);
1913
2024
  }
1914
2025
  // ─── Refresh (race-safe singleton) ───────────────────────────────────────
1915
2026
  /**
@@ -1966,6 +2077,65 @@ var PollarClient = class {
1966
2077
  } catch (err) {
1967
2078
  console.error("[PollarClient] Failed to persist refreshed session", err);
1968
2079
  }
2080
+ this._scheduleNextRefresh();
2081
+ }
2082
+ }
2083
+ // ─── Silent refresh scheduler ────────────────────────────────────────────────
2084
+ /**
2085
+ * Arm a single setTimeout to fire shortly before the current access token
2086
+ * expires. Idempotent — clearing any previous timer first. Safe to call
2087
+ * from any session-write site (initial login, restore-from-storage, after
2088
+ * a successful rotation). No-op if there's no session in memory.
2089
+ *
2090
+ * Browser/RN background-tab throttling makes long-running setTimeouts
2091
+ * unreliable on their own; the `visibilitychange` listener compensates by
2092
+ * re-invoking `_maybeProactiveRefresh` whenever the app comes back to the
2093
+ * foreground, catching any timer that fired late or never fired at all.
2094
+ */
2095
+ _scheduleNextRefresh() {
2096
+ this._clearRefreshTimer();
2097
+ const expiresAt = this._session?.token?.expiresAt;
2098
+ if (typeof expiresAt !== "number") return;
2099
+ const dueInMs = Math.max(0, (expiresAt - Math.floor(Date.now() / 1e3) - REFRESH_SKEW_SECONDS) * 1e3);
2100
+ this._refreshTimer = setTimeout(() => {
2101
+ void this._maybeProactiveRefresh();
2102
+ }, dueInMs);
2103
+ }
2104
+ /**
2105
+ * Decide whether to actually run a refresh right now. Called both from the
2106
+ * scheduler timer and from the visibility-change listener.
2107
+ *
2108
+ * Skip if:
2109
+ * - no session / no RT (nothing to refresh)
2110
+ * - app is hidden — wait for the visibility listener to re-trigger us
2111
+ * - `maxIdleMs` configured and no client request since that window — let
2112
+ * the next reactive 401-refresh handle it whenever the user comes back
2113
+ * - the AT still has more than `REFRESH_SKEW_SECONDS` of life — reschedule
2114
+ *
2115
+ * Otherwise call `refresh()`, which uses the existing in-flight singleton
2116
+ * so we never collide with a reactive 401-triggered refresh. On failure,
2117
+ * `_doRefresh` already calls `_clearSession`, so auth-state listeners see
2118
+ * `step:'idle'` — no extra event dispatch needed here.
2119
+ */
2120
+ async _maybeProactiveRefresh() {
2121
+ if (!this._session?.token?.refreshToken) return;
2122
+ if (!this._visibilityProvider.isVisible()) return;
2123
+ if (this._maxIdleMs !== void 0 && Date.now() - this._lastRequestAt > this._maxIdleMs) return;
2124
+ const expiresAt = this._session.token.expiresAt;
2125
+ if (Math.floor(Date.now() / 1e3) < expiresAt - REFRESH_SKEW_SECONDS) {
2126
+ this._scheduleNextRefresh();
2127
+ return;
2128
+ }
2129
+ try {
2130
+ await this.refresh();
2131
+ } catch (err) {
2132
+ console.warn("[PollarClient] Proactive refresh failed; session cleared", err);
2133
+ }
2134
+ }
2135
+ _clearRefreshTimer() {
2136
+ if (this._refreshTimer !== null) {
2137
+ clearTimeout(this._refreshTimer);
2138
+ this._refreshTimer = null;
1969
2139
  }
1970
2140
  }
1971
2141
  // ─── Auth state ──────────────────────────────────────────────────────────────
@@ -1977,6 +2147,38 @@ var PollarClient = class {
1977
2147
  cb(this._authState);
1978
2148
  return () => this._authStateListeners.delete(cb);
1979
2149
  }
2150
+ /**
2151
+ * Subscribe to persistent-storage degradation (Safari private mode,
2152
+ * sandboxed iframes, quota errors, etc.). The SDK keeps running off
2153
+ * in-memory storage after degrade, but sessions won't survive reload — a
2154
+ * host UI typically wants to show "your session won't be saved" so the
2155
+ * user isn't blindsided after a refresh.
2156
+ *
2157
+ * Fires at most once per client lifetime (the underlying adapter dedupes).
2158
+ * Late subscribers receive the latched state synchronously on subscribe.
2159
+ *
2160
+ * Only fires when the SDK constructs the default storage adapter. If you
2161
+ * pass a custom `config.storage`, wire your own notification path through
2162
+ * that adapter's API — the SDK has no hook into it.
2163
+ */
2164
+ onStorageDegrade(cb) {
2165
+ this._storageDegradeListeners.add(cb);
2166
+ if (this._storageDegraded) {
2167
+ cb(this._storageDegraded.reason, this._storageDegraded.error);
2168
+ }
2169
+ return () => this._storageDegradeListeners.delete(cb);
2170
+ }
2171
+ _dispatchStorageDegrade(reason, error) {
2172
+ if (this._storageDegraded) return;
2173
+ this._storageDegraded = { reason, error };
2174
+ for (const cb of this._storageDegradeListeners) {
2175
+ try {
2176
+ cb(reason, error);
2177
+ } catch (err) {
2178
+ console.error("[PollarClient] onStorageDegrade listener threw", err);
2179
+ }
2180
+ }
2181
+ }
1980
2182
  /** PII (email, names, avatar, providers). Held in memory only — never persisted. */
1981
2183
  getUserProfile() {
1982
2184
  return this._profile;
@@ -2213,10 +2415,16 @@ var PollarClient = class {
2213
2415
  }
2214
2416
  }
2215
2417
  // ─── Transactions ─────────────────────────────────────────────────────────
2418
+ /**
2419
+ * Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
2420
+ * AND returns a {@link BuildOutcome} so headless callers can `await` and
2421
+ * inspect the result without subscribing to state changes.
2422
+ */
2216
2423
  async buildTx(operation, params, options) {
2217
2424
  if (!this._session?.wallet?.publicKey) {
2218
- this._setTransactionState({ step: "error", details: "No wallet connected" });
2219
- return;
2425
+ const details = "No wallet connected";
2426
+ this._setTransactionState({ step: "error", phase: "building", details });
2427
+ return { status: "error", details };
2220
2428
  }
2221
2429
  const body = {
2222
2430
  network: this.getNetwork(),
@@ -2230,39 +2438,194 @@ var PollarClient = class {
2230
2438
  const { data, error } = await this._api.POST("/tx/build", { body });
2231
2439
  if (!error && data?.success && data.content) {
2232
2440
  this._setTransactionState({ step: "built", buildData: data.content });
2233
- } else {
2234
- const details = error?.details;
2235
- this._setTransactionState({ step: "error", ...details && { details } });
2441
+ return { status: "built", buildData: data.content };
2236
2442
  }
2237
- } catch {
2238
- this._setTransactionState({ step: "error" });
2443
+ const details = error?.details;
2444
+ this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
2445
+ return { status: "error", ...details && { details } };
2446
+ } catch (err) {
2447
+ console.error("[PollarClient] buildTx failed", err);
2448
+ this._setTransactionState({ step: "error", phase: "building" });
2449
+ return { status: "error" };
2239
2450
  }
2240
2451
  }
2241
2452
  getWalletType() {
2242
2453
  return this._walletAdapter?.type ?? null;
2243
2454
  }
2244
- async signAndSubmitTx(unsignedXdr) {
2245
- const state = this._transactionState;
2246
- const buildData = state?.step === "built" ? state.buildData : state?.step === "error" ? state.buildData : void 0;
2247
- const stateExtra = buildData ? { buildData } : { external: true };
2248
- this._setTransactionState({ step: "signing", ...stateExtra });
2249
- const accountToSign = this._session?.wallet?.publicKey;
2455
+ /**
2456
+ * Signs the given unsigned XDR and returns the signed XDR.
2457
+ *
2458
+ * - External wallets: signs locally via the wallet adapter.
2459
+ * - Custodial wallets: posts to `/tx/sign`. The backend signs (through
2460
+ * wallet-service or the app's customer-managed adapter) and returns the
2461
+ * signed XDR plus an `idempotencyKey` the caller should echo back to
2462
+ * `submitTx`.
2463
+ *
2464
+ * Drives `_setTransactionState`: emits `signing` while in flight and
2465
+ * `signed` on success (or `error[phase: 'signing']` on failure). `buildData`
2466
+ * is threaded through if the consumer previously called `buildTx`.
2467
+ */
2468
+ async signTx(unsignedXdr) {
2469
+ const buildData = this._currentBuildData();
2470
+ this._setTransactionState({ step: "signing", ...buildData && { buildData } });
2250
2471
  if (this._walletAdapter) {
2472
+ const accountToSign = this._session?.wallet?.publicKey;
2473
+ const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2251
2474
  try {
2252
- const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2253
2475
  const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
2254
- const stellarClient = new StellarClient(this.getNetwork());
2255
- const result = await stellarClient.submitTransaction(signedTxXdr);
2256
- if (result.success) {
2257
- this._setTransactionState({ step: "success", ...stateExtra, hash: result.hash });
2258
- } else {
2259
- this._setTransactionState({ step: "error", ...stateExtra, details: result.errorCode });
2476
+ this._setTransactionState({
2477
+ step: "signed",
2478
+ signedXdr: signedTxXdr,
2479
+ ...buildData && { buildData }
2480
+ });
2481
+ return { status: "signed", signedXdr: signedTxXdr };
2482
+ } catch (err) {
2483
+ const details = err instanceof Error ? err.message : void 0;
2484
+ this._setTransactionState({
2485
+ step: "error",
2486
+ phase: "signing",
2487
+ ...buildData && { buildData },
2488
+ ...details && { details }
2489
+ });
2490
+ return { status: "error", ...details && { details } };
2491
+ }
2492
+ }
2493
+ const publicKey = this._session?.wallet?.publicKey ?? "";
2494
+ try {
2495
+ const { data, error } = await this._api.POST("/tx/sign", {
2496
+ body: { network: this.getNetwork(), publicKey, unsignedXdr }
2497
+ });
2498
+ if (!error && data?.success && data.content?.signedXdr) {
2499
+ const { signedXdr, idempotencyKey } = data.content;
2500
+ this._setTransactionState({
2501
+ step: "signed",
2502
+ signedXdr,
2503
+ submissionToken: idempotencyKey,
2504
+ ...buildData && { buildData }
2505
+ });
2506
+ return { status: "signed", signedXdr, submissionToken: idempotencyKey };
2507
+ }
2508
+ const details = error?.details;
2509
+ this._setTransactionState({
2510
+ step: "error",
2511
+ phase: "signing",
2512
+ ...buildData && { buildData },
2513
+ ...details && { details }
2514
+ });
2515
+ return { status: "error", ...details && { details } };
2516
+ } catch (err) {
2517
+ const details = err instanceof Error ? err.message : void 0;
2518
+ this._setTransactionState({
2519
+ step: "error",
2520
+ phase: "signing",
2521
+ ...buildData && { buildData },
2522
+ ...details && { details }
2523
+ });
2524
+ return { status: "error", ...details && { details } };
2525
+ }
2526
+ }
2527
+ /**
2528
+ * Submits a signed XDR via `/tx/submit` regardless of wallet type
2529
+ * (custodial or external). Routing through sdk-api gives us:
2530
+ * - End-to-end tx_records persistence with full phase lifecycle so the
2531
+ * developer dashboard can show every tx (both custodial and external
2532
+ * wallet flows) at `/apps/:id/monitor/transactions`.
2533
+ * - Idempotency tracking via `submissionToken` (returned by `signTx`).
2534
+ * - A single response shape (SUCCESS / PENDING / FAILED) shared by both
2535
+ * flows — previously external wallets could only return SUCCESS or
2536
+ * error since the direct-to-Horizon path was synchronous.
2537
+ *
2538
+ * The extra hop adds ~50–150 ms vs. the legacy direct-Horizon path; the
2539
+ * persistence + observability win is worth it.
2540
+ *
2541
+ * Drives `_setTransactionState`: emits `submitting` while in flight,
2542
+ * `submitted` on Horizon ack (pending), `success` on ledger confirmation,
2543
+ * or `error[phase: 'submitting']` on failure.
2544
+ */
2545
+ async submitTx(signedXdr, opts) {
2546
+ const buildData = this._currentBuildData();
2547
+ const outcomeExtra = buildData ? { buildData } : {};
2548
+ this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
2549
+ const publicKey = this._session?.wallet?.publicKey ?? "";
2550
+ try {
2551
+ const { data, error } = await this._api.POST("/tx/submit", {
2552
+ body: {
2553
+ network: this.getNetwork(),
2554
+ publicKey,
2555
+ signedXdr,
2556
+ ...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
2260
2557
  }
2261
- } catch {
2262
- this._setTransactionState({ step: "error", ...stateExtra });
2558
+ });
2559
+ if (!error && data?.success && data.content) {
2560
+ const { hash, status: backendStatus, resultCode } = data.content;
2561
+ if (backendStatus === "SUCCESS") {
2562
+ this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
2563
+ return { status: "success", hash, ...outcomeExtra };
2564
+ }
2565
+ if (backendStatus === "PENDING") {
2566
+ this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
2567
+ return { status: "pending", hash, ...outcomeExtra };
2568
+ }
2569
+ this._setTransactionState({
2570
+ step: "error",
2571
+ phase: "submitting",
2572
+ ...buildData && { buildData },
2573
+ ...resultCode && { details: resultCode }
2574
+ });
2575
+ return {
2576
+ status: "error",
2577
+ hash,
2578
+ ...outcomeExtra,
2579
+ ...resultCode && { details: resultCode, resultCode }
2580
+ };
2263
2581
  }
2264
- return;
2582
+ const details = error?.details;
2583
+ this._setTransactionState({
2584
+ step: "error",
2585
+ phase: "submitting",
2586
+ ...buildData && { buildData },
2587
+ ...details && { details }
2588
+ });
2589
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2590
+ } catch (err) {
2591
+ const details = err instanceof Error ? err.message : void 0;
2592
+ this._setTransactionState({
2593
+ step: "error",
2594
+ phase: "submitting",
2595
+ ...buildData && { buildData },
2596
+ ...details && { details }
2597
+ });
2598
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2265
2599
  }
2600
+ }
2601
+ /**
2602
+ * Signs and submits in one logical step. Returns a {@link SubmitOutcome}.
2603
+ *
2604
+ * - **External wallets**: composes `signTx` + `submitTx` client-side. State
2605
+ * machine sees the full granular sequence `signing → signed → submitting
2606
+ * → success` because the underlying methods each emit.
2607
+ * - **Custodial wallets**: atomic `/tx/sign-and-send` round-trip. State
2608
+ * machine emits the compound `signing-submitting` step (the SDK can't
2609
+ * observe when one phase ends and the next begins inside that single
2610
+ * backend call) and then transitions to `submitted` (Horizon ack only) or
2611
+ * `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
2612
+ */
2613
+ async signAndSubmitTx(unsignedXdr) {
2614
+ if (this._walletAdapter) {
2615
+ const signed = await this.signTx(unsignedXdr);
2616
+ if (signed.status === "error") {
2617
+ const buildData2 = this._currentBuildData();
2618
+ return {
2619
+ status: "error",
2620
+ ...buildData2 && { buildData: buildData2 },
2621
+ ...signed.details && { details: signed.details }
2622
+ };
2623
+ }
2624
+ return this.submitTx(signed.signedXdr);
2625
+ }
2626
+ const buildData = this._currentBuildData();
2627
+ const outcomeExtra = buildData ? { buildData } : {};
2628
+ this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
2266
2629
  const body = {
2267
2630
  network: this.getNetwork(),
2268
2631
  publicKey: this._session?.wallet?.publicKey ?? "",
@@ -2271,14 +2634,128 @@ var PollarClient = class {
2271
2634
  try {
2272
2635
  const { data, error } = await this._api.POST("/tx/sign-and-send", { body });
2273
2636
  if (!error && data?.success && data.content?.hash) {
2274
- this._setTransactionState({ step: "success", ...stateExtra, hash: data.content.hash });
2275
- } else {
2276
- const details = error?.details;
2277
- this._setTransactionState({ step: "error", ...stateExtra, ...details && { details } });
2637
+ const {
2638
+ hash,
2639
+ status: backendStatus,
2640
+ resultCode
2641
+ } = data.content;
2642
+ if (backendStatus === "SUCCESS") {
2643
+ this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
2644
+ return { status: "success", hash, ...outcomeExtra };
2645
+ }
2646
+ if (backendStatus === "PENDING") {
2647
+ this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
2648
+ return { status: "pending", hash, ...outcomeExtra };
2649
+ }
2650
+ this._setTransactionState({
2651
+ step: "error",
2652
+ phase: "signing-submitting",
2653
+ ...buildData && { buildData },
2654
+ ...resultCode && { details: resultCode }
2655
+ });
2656
+ return {
2657
+ status: "error",
2658
+ hash,
2659
+ ...outcomeExtra,
2660
+ ...resultCode && { details: resultCode, resultCode }
2661
+ };
2278
2662
  }
2279
- } catch {
2280
- this._setTransactionState({ step: "error", ...stateExtra });
2663
+ const details = error?.details;
2664
+ this._setTransactionState({
2665
+ step: "error",
2666
+ phase: "signing-submitting",
2667
+ ...buildData && { buildData },
2668
+ ...details && { details }
2669
+ });
2670
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2671
+ } catch (err) {
2672
+ const details = err instanceof Error ? err.message : void 0;
2673
+ this._setTransactionState({
2674
+ step: "error",
2675
+ phase: "signing-submitting",
2676
+ ...buildData && { buildData },
2677
+ ...details && { details }
2678
+ });
2679
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2680
+ }
2681
+ }
2682
+ /**
2683
+ * One-shot: build → sign → submit, returning the final {@link SubmitOutcome}.
2684
+ *
2685
+ * - **External wallets**: composes `buildTx` + `signAndSubmitTx` client-side.
2686
+ * State machine sees the full granular sequence (`building → built →
2687
+ * signing → signed → submitting → success`) because each composed call
2688
+ * emits its own transitions.
2689
+ * - **Custodial wallets**: single round-trip to `/tx/build-sign-submit`. The
2690
+ * signed XDR never leaves the backend. State machine emits the compound
2691
+ * `building-signing-submitting` step (the SDK can't observe individual
2692
+ * phase boundaries inside one atomic call) and then transitions to
2693
+ * `submitted` / `success` / `error[phase: 'building-signing-submitting']`.
2694
+ *
2695
+ * If you need granular UI feedback for custodial flows (separate
2696
+ * "Building…", "Signing…", "Submitting…" indicators), call `buildTx`,
2697
+ * `signTx`, and `submitTx` separately instead.
2698
+ */
2699
+ async buildAndSignAndSubmitTx(operation, params, options) {
2700
+ if (this._walletAdapter) {
2701
+ const built = await this.buildTx(operation, params, options);
2702
+ if (built.status === "error") {
2703
+ return { status: "error", ...built.details && { details: built.details } };
2704
+ }
2705
+ return this.signAndSubmitTx(built.buildData.unsignedXdr);
2281
2706
  }
2707
+ if (!this._session?.wallet?.publicKey) {
2708
+ this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
2709
+ return { status: "error", details: "No wallet connected" };
2710
+ }
2711
+ this._setTransactionState({ step: "building-signing-submitting" });
2712
+ try {
2713
+ const { data, error } = await this._api.POST("/tx/build-sign-submit", {
2714
+ body: {
2715
+ network: this.getNetwork(),
2716
+ publicKey: this._session.wallet.publicKey,
2717
+ operation,
2718
+ params,
2719
+ options: options ?? {}
2720
+ }
2721
+ });
2722
+ if (!error && data?.success && data.content) {
2723
+ const { hash, status: backendStatus, resultCode } = data.content;
2724
+ if (backendStatus === "SUCCESS") {
2725
+ this._setTransactionState({ step: "success", hash });
2726
+ return { status: "success", hash };
2727
+ }
2728
+ if (backendStatus === "PENDING") {
2729
+ this._setTransactionState({ step: "submitted", hash });
2730
+ return { status: "pending", hash };
2731
+ }
2732
+ this._setTransactionState({
2733
+ step: "error",
2734
+ phase: "building-signing-submitting",
2735
+ ...resultCode && { details: resultCode }
2736
+ });
2737
+ return { status: "error", hash, ...resultCode && { details: resultCode, resultCode } };
2738
+ }
2739
+ const details = error?.details;
2740
+ this._setTransactionState({
2741
+ step: "error",
2742
+ phase: "building-signing-submitting",
2743
+ ...details && { details }
2744
+ });
2745
+ return { status: "error", ...details && { details } };
2746
+ } catch (err) {
2747
+ const details = err instanceof Error ? err.message : void 0;
2748
+ this._setTransactionState({
2749
+ step: "error",
2750
+ phase: "building-signing-submitting",
2751
+ ...details && { details }
2752
+ });
2753
+ return { status: "error", ...details && { details } };
2754
+ }
2755
+ }
2756
+ /** Alias for {@link buildAndSignAndSubmitTx} — shorter "just do the thing" name. */
2757
+ async runTx(operation, params, options) {
2758
+ return this.buildAndSignAndSubmitTx(operation, params, options);
2282
2759
  }
2283
2760
  // ─── App config ───────────────────────────────────────────────────────────
2284
2761
  async getAppConfig() {
@@ -2322,6 +2799,13 @@ var PollarClient = class {
2322
2799
  pollRampTransaction(txId, opts) {
2323
2800
  return pollRampTransaction(this._api, txId, opts);
2324
2801
  }
2802
+ // ─── Distribution ─────────────────────────────────────────────────────────
2803
+ listDistributionRules() {
2804
+ return listDistributionRules(this._api);
2805
+ }
2806
+ claimDistributionRule(body) {
2807
+ return claimDistributionRule(this._api, body);
2808
+ }
2325
2809
  _setTxHistoryState(next) {
2326
2810
  this._txHistoryState = next;
2327
2811
  for (const cb of this._txHistoryStateListeners) cb(next);
@@ -2360,7 +2844,22 @@ var PollarClient = class {
2360
2844
  */
2361
2845
  async _resolveWalletAdapter(id) {
2362
2846
  if (this._walletAdapterResolver) {
2363
- return Promise.resolve(this._walletAdapterResolver(id));
2847
+ const timeoutMs = this._walletResolverTimeoutMs;
2848
+ let timeoutHandle;
2849
+ const timeoutPromise = new Promise((_, reject) => {
2850
+ timeoutHandle = setTimeout(() => {
2851
+ reject(
2852
+ Object.assign(new Error(`[PollarClient] Wallet adapter resolver for "${id}" timed out after ${timeoutMs}ms`), {
2853
+ code: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
2854
+ })
2855
+ );
2856
+ }, timeoutMs);
2857
+ });
2858
+ try {
2859
+ return await Promise.race([Promise.resolve(this._walletAdapterResolver(id)), timeoutPromise]);
2860
+ } finally {
2861
+ if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
2862
+ }
2364
2863
  }
2365
2864
  if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
2366
2865
  if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
@@ -2374,6 +2873,16 @@ var PollarClient = class {
2374
2873
  this._setAuthState({ step: "idle" });
2375
2874
  return;
2376
2875
  }
2876
+ if (error instanceof Error && error.code === AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT) {
2877
+ console.error("[PollarClient]", error.message);
2878
+ this._setAuthState({
2879
+ step: "error",
2880
+ previousStep: this._authState.step,
2881
+ message: error.message,
2882
+ errorCode: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
2883
+ });
2884
+ return;
2885
+ }
2377
2886
  console.error("[PollarClient] Unexpected error in auth flow", error);
2378
2887
  this._setAuthState({
2379
2888
  step: "error",
@@ -2395,6 +2904,7 @@ var PollarClient = class {
2395
2904
  }
2396
2905
  console.info("[PollarClient] Session restored from storage");
2397
2906
  this._setAuthState({ step: "authenticated", session: this._session });
2907
+ this._scheduleNextRefresh();
2398
2908
  } else {
2399
2909
  console.info("[PollarClient] No session in storage");
2400
2910
  }
@@ -2421,9 +2931,11 @@ var PollarClient = class {
2421
2931
  }
2422
2932
  await writeStorage(this._storage, this.apiKeyHash, persisted);
2423
2933
  this._setAuthState({ step: "authenticated", session: persisted });
2934
+ this._scheduleNextRefresh();
2424
2935
  }
2425
2936
  async _clearSession() {
2426
2937
  console.info("[PollarClient] Session cleared");
2938
+ this._clearRefreshTimer();
2427
2939
  this._session = null;
2428
2940
  this._profile = null;
2429
2941
  this._walletAdapter = null;
@@ -2456,11 +2968,51 @@ var PollarClient = class {
2456
2968
  console.info(`[PollarClient] transaction:${next.step}`);
2457
2969
  for (const cb of this._transactionStateListeners) cb(next);
2458
2970
  }
2971
+ /**
2972
+ * Threads `buildData` through state transitions. When the user has already
2973
+ * called `buildTx`, every subsequent state (signing, signed, submitting,
2974
+ * submitted, success, error) should carry the build summary so modal UIs
2975
+ * can keep showing "Send 5 USDC to G..." through the whole flow.
2976
+ */
2977
+ _currentBuildData() {
2978
+ const s = this._transactionState;
2979
+ if (!s) return void 0;
2980
+ if ("buildData" in s && s.buildData) return s.buildData;
2981
+ return void 0;
2982
+ }
2983
+ };
2984
+
2985
+ // src/stellar/StellarClient.ts
2986
+ var HORIZON_URLS = {
2987
+ mainnet: "https://horizon.stellar.org",
2988
+ testnet: "https://horizon-testnet.stellar.org"
2989
+ };
2990
+ var StellarClient = class {
2991
+ constructor(config) {
2992
+ this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
2993
+ }
2994
+ async submitTransaction(signedXdr) {
2995
+ try {
2996
+ const response = await fetch(`${this.horizonUrl}/transactions`, {
2997
+ method: "POST",
2998
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2999
+ body: new URLSearchParams({ tx: signedXdr })
3000
+ });
3001
+ if (!response.ok) {
3002
+ const body = await response.json().catch(() => ({}));
3003
+ return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
3004
+ }
3005
+ const data = await response.json();
3006
+ return { success: true, hash: data.hash };
3007
+ } catch {
3008
+ return { success: false, errorCode: "NETWORK_ERROR" };
3009
+ }
3010
+ }
2459
3011
  };
2460
3012
 
2461
3013
  // src/index.ts
2462
3014
  _setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKey));
2463
3015
 
2464
- export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
3016
+ export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
2465
3017
  //# sourceMappingURL=index.mjs.map
2466
3018
  //# sourceMappingURL=index.mjs.map