@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.rn.mjs CHANGED
@@ -28,10 +28,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
 
29
29
  // ../../node_modules/@stellar/freighter-api/build/index.min.js
30
30
  var require_index_min = __commonJS({
31
- "../../node_modules/@stellar/freighter-api/build/index.min.js"(exports$1, module) {
31
+ "../../node_modules/@stellar/freighter-api/build/index.min.js"(exports, module) {
32
32
  !(function(e, r) {
33
- "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();
34
- })(exports$1, (() => (() => {
33
+ "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();
34
+ })(exports, (() => (() => {
35
35
  var e, r, E = { d: (e2, r2) => {
36
36
  for (var o2 in r2) E.o(r2, o2) && !E.o(e2, o2) && Object.defineProperty(e2, o2, { enumerable: true, get: r2[o2] });
37
37
  }, o: (e2, r2) => Object.prototype.hasOwnProperty.call(e2, r2), r: (e2) => {
@@ -450,9 +450,7 @@ var WebCryptoKeyManager = class {
450
450
  */
451
451
  this._initPromise = null;
452
452
  if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
453
- throw new Error(
454
- "[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost)."
455
- );
453
+ throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
456
454
  }
457
455
  this.apiKey = apiKey;
458
456
  }
@@ -567,7 +565,7 @@ function createClient(clientOptions) {
567
565
  const {
568
566
  baseUrl: localBaseUrl,
569
567
  fetch: fetch2 = baseFetch,
570
- Request = CustomRequest,
568
+ Request: Request2 = CustomRequest,
571
569
  headers,
572
570
  params = {},
573
571
  parseAs = "json",
@@ -619,7 +617,7 @@ function createClient(clientOptions) {
619
617
  };
620
618
  let id;
621
619
  let options;
622
- let request = new Request(
620
+ let request = new Request2(
623
621
  createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer, pathSerializer }),
624
622
  requestInit
625
623
  );
@@ -649,7 +647,7 @@ function createClient(clientOptions) {
649
647
  id
650
648
  });
651
649
  if (result) {
652
- if (result instanceof Request) {
650
+ if (result instanceof Request2) {
653
651
  request = result;
654
652
  } else if (result instanceof Response) {
655
653
  response = result;
@@ -1021,24 +1019,44 @@ function createApiClient(baseUrl) {
1021
1019
  return createClient({ baseUrl });
1022
1020
  }
1023
1021
 
1022
+ // src/api/endpoints/distribution.ts
1023
+ async function listDistributionRules(api) {
1024
+ const { data, error } = await api.GET("/distribution/rules");
1025
+ if (!data?.content || error) {
1026
+ throw new Error(
1027
+ error?.code ?? error?.error ?? "Failed to list distribution rules"
1028
+ );
1029
+ }
1030
+ return data.content.rules;
1031
+ }
1032
+ async function claimDistributionRule(api, body) {
1033
+ const { data, error } = await api.POST("/distribution/claim", { body });
1034
+ if (!data?.content || error) {
1035
+ throw new Error(
1036
+ error?.code ?? error?.error ?? "Failed to claim distribution rule"
1037
+ );
1038
+ }
1039
+ return data.content;
1040
+ }
1041
+
1024
1042
  // src/api/endpoints/kyc.ts
1025
1043
  async function getKycStatus(api, providerId) {
1026
1044
  const { data, error } = await api.GET("/kyc/status", {
1027
1045
  params: { query: providerId ? { providerId } : {} }
1028
1046
  });
1029
1047
  if (!data?.content || error) {
1030
- throw new Error(error?.error ?? "Failed to get KYC status");
1048
+ throw new Error(error?.code ?? error?.error ?? "Failed to get KYC status");
1031
1049
  }
1032
1050
  return data.content;
1033
1051
  }
1034
1052
  async function getKycProviders(api, country) {
1035
1053
  const { data, error } = await api.GET("/kyc/providers", { params: { query: { country } } });
1036
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to get KYC providers");
1054
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get KYC providers");
1037
1055
  return data.content;
1038
1056
  }
1039
1057
  async function startKyc(api, body) {
1040
1058
  const { data, error } = await api.POST("/kyc/start", { body });
1041
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to start KYC");
1059
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to start KYC");
1042
1060
  return data.content;
1043
1061
  }
1044
1062
  async function resolveKyc(api, providerId, level = "basic") {
@@ -1060,22 +1078,22 @@ async function pollKycStatus(api, providerId, { intervalMs = 3e3, timeoutMs = 3e
1060
1078
  // src/api/endpoints/ramps.ts
1061
1079
  async function getRampsQuote(api, query) {
1062
1080
  const { data, error } = await api.GET("/ramps/quote", { params: { query } });
1063
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to get ramp quotes");
1081
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get ramp quotes");
1064
1082
  return data.content;
1065
1083
  }
1066
1084
  async function createOnRamp(api, body) {
1067
1085
  const { data, error } = await api.POST("/ramps/onramp", { body });
1068
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to create onramp");
1086
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create onramp");
1069
1087
  return data.content;
1070
1088
  }
1071
1089
  async function createOffRamp(api, body) {
1072
1090
  const { data, error } = await api.POST("/ramps/offramp", { body });
1073
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to create offramp");
1091
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create offramp");
1074
1092
  return data.content;
1075
1093
  }
1076
1094
  async function getRampTransaction(api, txId) {
1077
1095
  const { data, error } = await api.GET("/ramps/transaction/{txId}", { params: { path: { txId } } });
1078
- if (!data?.content || error) throw new Error(error?.error ?? "Failed to get transaction");
1096
+ if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get transaction");
1079
1097
  return data.content;
1080
1098
  }
1081
1099
  async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e5 } = {}) {
@@ -1150,34 +1168,6 @@ function generateJti() {
1150
1168
  );
1151
1169
  }
1152
1170
 
1153
- // src/stellar/StellarClient.ts
1154
- var HORIZON_URLS = {
1155
- mainnet: "https://horizon.stellar.org",
1156
- testnet: "https://horizon-testnet.stellar.org"
1157
- };
1158
- var StellarClient = class {
1159
- constructor(config) {
1160
- this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
1161
- }
1162
- async submitTransaction(signedXdr) {
1163
- try {
1164
- const response = await fetch(`${this.horizonUrl}/transactions`, {
1165
- method: "POST",
1166
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1167
- body: new URLSearchParams({ tx: signedXdr })
1168
- });
1169
- if (!response.ok) {
1170
- const body = await response.json().catch(() => ({}));
1171
- return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
1172
- }
1173
- const data = await response.json();
1174
- return { success: true, hash: data.hash };
1175
- } catch {
1176
- return { success: false, errorCode: "NETWORK_ERROR" };
1177
- }
1178
- }
1179
- };
1180
-
1181
1171
  // src/storage/web.ts
1182
1172
  var LOG_PREFIX = "[PollarClient:storage]";
1183
1173
  function createMemoryAdapter() {
@@ -1265,6 +1255,57 @@ function defaultStorage(options = {}) {
1265
1255
  return createLocalStorageAdapter(options);
1266
1256
  }
1267
1257
 
1258
+ // src/visibility/noop.ts
1259
+ function createNoopVisibilityProvider() {
1260
+ return {
1261
+ isVisible: () => true,
1262
+ onChange: () => () => {
1263
+ }
1264
+ };
1265
+ }
1266
+
1267
+ // src/visibility/web.ts
1268
+ function createWebVisibilityProvider() {
1269
+ const isVisibleNow = () => typeof document === "undefined" || document.visibilityState === "visible";
1270
+ return {
1271
+ isVisible: isVisibleNow,
1272
+ onChange: (cb) => {
1273
+ if (typeof window === "undefined" || typeof document === "undefined") {
1274
+ return () => {
1275
+ };
1276
+ }
1277
+ let last = isVisibleNow();
1278
+ const handler = () => {
1279
+ const next = isVisibleNow();
1280
+ if (next !== last) {
1281
+ last = next;
1282
+ cb(next);
1283
+ }
1284
+ };
1285
+ document.addEventListener("visibilitychange", handler);
1286
+ window.addEventListener("pageshow", handler);
1287
+ window.addEventListener("pagehide", handler);
1288
+ window.addEventListener("focus", handler);
1289
+ window.addEventListener("blur", handler);
1290
+ return () => {
1291
+ document.removeEventListener("visibilitychange", handler);
1292
+ window.removeEventListener("pageshow", handler);
1293
+ window.removeEventListener("pagehide", handler);
1294
+ window.removeEventListener("focus", handler);
1295
+ window.removeEventListener("blur", handler);
1296
+ };
1297
+ }
1298
+ };
1299
+ }
1300
+
1301
+ // src/visibility/autodetect.ts
1302
+ function defaultVisibilityProvider() {
1303
+ if (typeof document !== "undefined" && typeof window !== "undefined") {
1304
+ return createWebVisibilityProvider();
1305
+ }
1306
+ return createNoopVisibilityProvider();
1307
+ }
1308
+
1268
1309
  // src/types.ts
1269
1310
  var AUTH_ERROR_CODES = {
1270
1311
  SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
@@ -1275,6 +1316,7 @@ var AUTH_ERROR_CODES = {
1275
1316
  AUTH_FAILED: "AUTH_FAILED",
1276
1317
  WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
1277
1318
  WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
1319
+ WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
1278
1320
  UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
1279
1321
  };
1280
1322
  var PollarFlowError = class extends Error {
@@ -1665,7 +1707,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1665
1707
  setAuthState({ step: "authenticating" });
1666
1708
  await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
1667
1709
  const dpopJwk = await deps.getPublicJwk();
1668
- const { data, error } = await api.POST("/auth/login", {
1710
+ const { data } = await api.POST("/auth/login", {
1669
1711
  body: {
1670
1712
  clientSessionId,
1671
1713
  dpopJwk,
@@ -1830,7 +1872,7 @@ async function loginWallet(type, deps) {
1830
1872
  let connectedWallet;
1831
1873
  try {
1832
1874
  setAuthState({ step: "connecting_wallet", walletType: type });
1833
- const adapter = await deps.resolveWalletAdapter(type);
1875
+ const adapter = await withSignal(deps.resolveWalletAdapter(type), signal);
1834
1876
  const available = await withSignal(adapter.isAvailable(), signal);
1835
1877
  if (!available) {
1836
1878
  setAuthState({ step: "wallet_not_installed", walletType: type });
@@ -1867,7 +1909,7 @@ async function loginWallet(type, deps) {
1867
1909
 
1868
1910
  // src/client/client.ts
1869
1911
  var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
1870
- var RETRIED_HEADER = "X-Pollar-Retried";
1912
+ var REFRESH_SKEW_SECONDS = 60;
1871
1913
  function warnServerSide(method) {
1872
1914
  console.warn(
1873
1915
  `[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
@@ -1885,9 +1927,21 @@ var PollarClient = class {
1885
1927
  this._profile = null;
1886
1928
  /** Last `DPoP-Nonce` we saw from a server response. Carried into the next proof. */
1887
1929
  this._dpopNonce = null;
1930
+ /**
1931
+ * Snapshot of each in-flight request's body, taken in `onRequest` before
1932
+ * `fetch()` consumes the stream. Needed because `Request.clone()` throws
1933
+ * once the body is disturbed, so the auto-retry path (DPoP nonce challenge
1934
+ * / 401 refresh) must rebuild the request from scratch instead of cloning.
1935
+ */
1936
+ this._requestBodyCache = /* @__PURE__ */ new WeakMap();
1888
1937
  /** Singleton in-flight refresh — concurrent 401s coalesce into one /auth/refresh call. */
1889
1938
  this._refreshPromise = null;
1890
1939
  this._storageEventHandler = null;
1940
+ /** Updated by the request middleware. Read by the silent-refresh scheduler
1941
+ * to skip proactive refreshes after `maxIdleMs` of no HTTP activity. */
1942
+ this._lastRequestAt = Date.now();
1943
+ this._refreshTimer = null;
1944
+ this._visibilityUnsubscribe = null;
1891
1945
  this._transactionState = null;
1892
1946
  this._transactionStateListeners = /* @__PURE__ */ new Set();
1893
1947
  this._txHistoryState = { step: "idle" };
@@ -1898,15 +1952,32 @@ var PollarClient = class {
1898
1952
  this._authStateListeners = /* @__PURE__ */ new Set();
1899
1953
  this._networkState = { step: "idle" };
1900
1954
  this._networkStateListeners = /* @__PURE__ */ new Set();
1955
+ /**
1956
+ * Latched once the storage adapter degrades. We dedupe (the adapter only
1957
+ * fires once anyway) and use it to replay state to late-subscribers — same
1958
+ * pattern as `onAuthStateChange` replaying `_authState` on subscribe.
1959
+ * Only populated when the SDK constructed the default storage adapter; if
1960
+ * the consumer passes `config.storage`, they own degradation notifications.
1961
+ */
1962
+ this._storageDegraded = null;
1963
+ this._storageDegradeListeners = /* @__PURE__ */ new Set();
1901
1964
  this._walletAdapter = null;
1902
1965
  this._loginController = null;
1903
1966
  this.apiKey = config.apiKey;
1904
1967
  this.id = crypto.randomUUID();
1905
1968
  this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1906
- this._storage = config.storage ?? defaultStorage(config.onStorageDegrade ? { onDegrade: config.onStorageDegrade } : void 0);
1969
+ this._storage = config.storage ?? defaultStorage({
1970
+ onDegrade: (reason, error) => {
1971
+ config.onStorageDegrade?.(reason, error);
1972
+ this._dispatchStorageDegrade(reason, error);
1973
+ }
1974
+ });
1907
1975
  this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
1908
1976
  this._walletAdapterResolver = config.walletAdapter ?? null;
1977
+ this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
1909
1978
  this._deviceLabel = config.deviceLabel;
1979
+ this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
1980
+ this._maxIdleMs = config.maxIdleMs;
1910
1981
  this._api = createApiClient(this.basePath);
1911
1982
  this._wireMiddlewares();
1912
1983
  this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
@@ -1952,6 +2023,9 @@ var PollarClient = class {
1952
2023
  console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
1953
2024
  }
1954
2025
  await this._restoreSession();
2026
+ this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
2027
+ if (visible) void this._maybeProactiveRefresh();
2028
+ });
1955
2029
  }
1956
2030
  /** Detach the cross-tab storage listener and abort any in-flight login. */
1957
2031
  destroy() {
@@ -1961,6 +2035,11 @@ var PollarClient = class {
1961
2035
  }
1962
2036
  this._loginController?.abort();
1963
2037
  this._loginController = null;
2038
+ this._clearRefreshTimer();
2039
+ if (this._visibilityUnsubscribe) {
2040
+ this._visibilityUnsubscribe();
2041
+ this._visibilityUnsubscribe = null;
2042
+ }
1964
2043
  }
1965
2044
  // ─── Middlewares (DPoP + auto-refresh) ────────────────────────────────────
1966
2045
  _wireMiddlewares() {
@@ -1968,7 +2047,15 @@ var PollarClient = class {
1968
2047
  this._api.use({
1969
2048
  onRequest: async ({ request }) => {
1970
2049
  request.headers.set("x-pollar-api-key", self.apiKey);
2050
+ self._lastRequestAt = Date.now();
1971
2051
  await self._initialized;
2052
+ if (request.body !== null) {
2053
+ try {
2054
+ self._requestBodyCache.set(request, await request.clone().arrayBuffer());
2055
+ } catch (err) {
2056
+ console.warn("[PollarClient] Could not snapshot request body for retry", err);
2057
+ }
2058
+ }
1972
2059
  const isRefresh = request.url.includes("/auth/refresh");
1973
2060
  if (!isRefresh && self._refreshPromise) await self._refreshPromise;
1974
2061
  if (isRefresh) {
@@ -1991,16 +2078,22 @@ var PollarClient = class {
1991
2078
  const newNonce = response.headers.get("DPoP-Nonce");
1992
2079
  if (newNonce) self._dpopNonce = newNonce;
1993
2080
  if (response.status !== 401) return response;
1994
- if (request.headers.get(RETRIED_HEADER)) return response;
1995
- if (request.url.includes("/auth/refresh")) return response;
1996
2081
  const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
1997
2082
  const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
2083
+ if (request.url.includes("/auth/refresh")) {
2084
+ if (isNonceChallenge) return self._retryRequest(request);
2085
+ return response;
2086
+ }
1998
2087
  if (!isNonceChallenge) {
1999
2088
  try {
2000
2089
  await self.refresh();
2001
2090
  } catch {
2002
2091
  return response;
2003
2092
  }
2093
+ const method = request.method.toUpperCase();
2094
+ if (method !== "GET" && method !== "HEAD") {
2095
+ return response;
2096
+ }
2004
2097
  }
2005
2098
  return self._retryRequest(request);
2006
2099
  }
@@ -2024,19 +2117,37 @@ var PollarClient = class {
2024
2117
  }
2025
2118
  }
2026
2119
  async _retryRequest(originalRequest) {
2027
- const clone = originalRequest.clone();
2028
- clone.headers.set(RETRIED_HEADER, "1");
2029
- const accessToken = this._session?.token?.accessToken;
2030
- if (accessToken) {
2031
- const proof = await this._buildProofForRequest(clone, accessToken);
2032
- if (proof) {
2033
- clone.headers.set("Authorization", `DPoP ${accessToken}`);
2034
- clone.headers.set("DPoP", proof);
2035
- } else {
2036
- clone.headers.set("Authorization", `Bearer ${accessToken}`);
2120
+ const headers = new Headers(originalRequest.headers);
2121
+ const isRefresh = originalRequest.url.includes("/auth/refresh");
2122
+ if (isRefresh) {
2123
+ const proof = await this._buildProofForRequest(originalRequest, void 0);
2124
+ headers.delete("Authorization");
2125
+ if (proof) headers.set("DPoP", proof);
2126
+ else headers.delete("DPoP");
2127
+ } else {
2128
+ const accessToken = this._session?.token?.accessToken;
2129
+ if (accessToken) {
2130
+ const proof = await this._buildProofForRequest(originalRequest, accessToken);
2131
+ if (proof) {
2132
+ headers.set("Authorization", `DPoP ${accessToken}`);
2133
+ headers.set("DPoP", proof);
2134
+ } else {
2135
+ headers.set("Authorization", `Bearer ${accessToken}`);
2136
+ }
2037
2137
  }
2038
2138
  }
2039
- return fetch(clone);
2139
+ const cachedBody = this._requestBodyCache.get(originalRequest);
2140
+ const retried = new Request(originalRequest.url, {
2141
+ method: originalRequest.method,
2142
+ headers,
2143
+ body: cachedBody ?? null,
2144
+ credentials: originalRequest.credentials,
2145
+ mode: originalRequest.mode,
2146
+ redirect: originalRequest.redirect,
2147
+ referrer: originalRequest.referrer,
2148
+ integrity: originalRequest.integrity
2149
+ });
2150
+ return fetch(retried);
2040
2151
  }
2041
2152
  // ─── Refresh (race-safe singleton) ───────────────────────────────────────
2042
2153
  /**
@@ -2093,6 +2204,65 @@ var PollarClient = class {
2093
2204
  } catch (err) {
2094
2205
  console.error("[PollarClient] Failed to persist refreshed session", err);
2095
2206
  }
2207
+ this._scheduleNextRefresh();
2208
+ }
2209
+ }
2210
+ // ─── Silent refresh scheduler ────────────────────────────────────────────────
2211
+ /**
2212
+ * Arm a single setTimeout to fire shortly before the current access token
2213
+ * expires. Idempotent — clearing any previous timer first. Safe to call
2214
+ * from any session-write site (initial login, restore-from-storage, after
2215
+ * a successful rotation). No-op if there's no session in memory.
2216
+ *
2217
+ * Browser/RN background-tab throttling makes long-running setTimeouts
2218
+ * unreliable on their own; the `visibilitychange` listener compensates by
2219
+ * re-invoking `_maybeProactiveRefresh` whenever the app comes back to the
2220
+ * foreground, catching any timer that fired late or never fired at all.
2221
+ */
2222
+ _scheduleNextRefresh() {
2223
+ this._clearRefreshTimer();
2224
+ const expiresAt = this._session?.token?.expiresAt;
2225
+ if (typeof expiresAt !== "number") return;
2226
+ const dueInMs = Math.max(0, (expiresAt - Math.floor(Date.now() / 1e3) - REFRESH_SKEW_SECONDS) * 1e3);
2227
+ this._refreshTimer = setTimeout(() => {
2228
+ void this._maybeProactiveRefresh();
2229
+ }, dueInMs);
2230
+ }
2231
+ /**
2232
+ * Decide whether to actually run a refresh right now. Called both from the
2233
+ * scheduler timer and from the visibility-change listener.
2234
+ *
2235
+ * Skip if:
2236
+ * - no session / no RT (nothing to refresh)
2237
+ * - app is hidden — wait for the visibility listener to re-trigger us
2238
+ * - `maxIdleMs` configured and no client request since that window — let
2239
+ * the next reactive 401-refresh handle it whenever the user comes back
2240
+ * - the AT still has more than `REFRESH_SKEW_SECONDS` of life — reschedule
2241
+ *
2242
+ * Otherwise call `refresh()`, which uses the existing in-flight singleton
2243
+ * so we never collide with a reactive 401-triggered refresh. On failure,
2244
+ * `_doRefresh` already calls `_clearSession`, so auth-state listeners see
2245
+ * `step:'idle'` — no extra event dispatch needed here.
2246
+ */
2247
+ async _maybeProactiveRefresh() {
2248
+ if (!this._session?.token?.refreshToken) return;
2249
+ if (!this._visibilityProvider.isVisible()) return;
2250
+ if (this._maxIdleMs !== void 0 && Date.now() - this._lastRequestAt > this._maxIdleMs) return;
2251
+ const expiresAt = this._session.token.expiresAt;
2252
+ if (Math.floor(Date.now() / 1e3) < expiresAt - REFRESH_SKEW_SECONDS) {
2253
+ this._scheduleNextRefresh();
2254
+ return;
2255
+ }
2256
+ try {
2257
+ await this.refresh();
2258
+ } catch (err) {
2259
+ console.warn("[PollarClient] Proactive refresh failed; session cleared", err);
2260
+ }
2261
+ }
2262
+ _clearRefreshTimer() {
2263
+ if (this._refreshTimer !== null) {
2264
+ clearTimeout(this._refreshTimer);
2265
+ this._refreshTimer = null;
2096
2266
  }
2097
2267
  }
2098
2268
  // ─── Auth state ──────────────────────────────────────────────────────────────
@@ -2104,6 +2274,38 @@ var PollarClient = class {
2104
2274
  cb(this._authState);
2105
2275
  return () => this._authStateListeners.delete(cb);
2106
2276
  }
2277
+ /**
2278
+ * Subscribe to persistent-storage degradation (Safari private mode,
2279
+ * sandboxed iframes, quota errors, etc.). The SDK keeps running off
2280
+ * in-memory storage after degrade, but sessions won't survive reload — a
2281
+ * host UI typically wants to show "your session won't be saved" so the
2282
+ * user isn't blindsided after a refresh.
2283
+ *
2284
+ * Fires at most once per client lifetime (the underlying adapter dedupes).
2285
+ * Late subscribers receive the latched state synchronously on subscribe.
2286
+ *
2287
+ * Only fires when the SDK constructs the default storage adapter. If you
2288
+ * pass a custom `config.storage`, wire your own notification path through
2289
+ * that adapter's API — the SDK has no hook into it.
2290
+ */
2291
+ onStorageDegrade(cb) {
2292
+ this._storageDegradeListeners.add(cb);
2293
+ if (this._storageDegraded) {
2294
+ cb(this._storageDegraded.reason, this._storageDegraded.error);
2295
+ }
2296
+ return () => this._storageDegradeListeners.delete(cb);
2297
+ }
2298
+ _dispatchStorageDegrade(reason, error) {
2299
+ if (this._storageDegraded) return;
2300
+ this._storageDegraded = { reason, error };
2301
+ for (const cb of this._storageDegradeListeners) {
2302
+ try {
2303
+ cb(reason, error);
2304
+ } catch (err) {
2305
+ console.error("[PollarClient] onStorageDegrade listener threw", err);
2306
+ }
2307
+ }
2308
+ }
2107
2309
  /** PII (email, names, avatar, providers). Held in memory only — never persisted. */
2108
2310
  getUserProfile() {
2109
2311
  return this._profile;
@@ -2340,10 +2542,16 @@ var PollarClient = class {
2340
2542
  }
2341
2543
  }
2342
2544
  // ─── Transactions ─────────────────────────────────────────────────────────
2545
+ /**
2546
+ * Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
2547
+ * AND returns a {@link BuildOutcome} so headless callers can `await` and
2548
+ * inspect the result without subscribing to state changes.
2549
+ */
2343
2550
  async buildTx(operation, params, options) {
2344
2551
  if (!this._session?.wallet?.publicKey) {
2345
- this._setTransactionState({ step: "error", details: "No wallet connected" });
2346
- return;
2552
+ const details = "No wallet connected";
2553
+ this._setTransactionState({ step: "error", phase: "building", details });
2554
+ return { status: "error", details };
2347
2555
  }
2348
2556
  const body = {
2349
2557
  network: this.getNetwork(),
@@ -2357,39 +2565,194 @@ var PollarClient = class {
2357
2565
  const { data, error } = await this._api.POST("/tx/build", { body });
2358
2566
  if (!error && data?.success && data.content) {
2359
2567
  this._setTransactionState({ step: "built", buildData: data.content });
2360
- } else {
2361
- const details = error?.details;
2362
- this._setTransactionState({ step: "error", ...details && { details } });
2568
+ return { status: "built", buildData: data.content };
2363
2569
  }
2364
- } catch {
2365
- this._setTransactionState({ step: "error" });
2570
+ const details = error?.details;
2571
+ this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
2572
+ return { status: "error", ...details && { details } };
2573
+ } catch (err) {
2574
+ console.error("[PollarClient] buildTx failed", err);
2575
+ this._setTransactionState({ step: "error", phase: "building" });
2576
+ return { status: "error" };
2366
2577
  }
2367
2578
  }
2368
2579
  getWalletType() {
2369
2580
  return this._walletAdapter?.type ?? null;
2370
2581
  }
2371
- async signAndSubmitTx(unsignedXdr) {
2372
- const state = this._transactionState;
2373
- const buildData = state?.step === "built" ? state.buildData : state?.step === "error" ? state.buildData : void 0;
2374
- const stateExtra = buildData ? { buildData } : { external: true };
2375
- this._setTransactionState({ step: "signing", ...stateExtra });
2376
- const accountToSign = this._session?.wallet?.publicKey;
2582
+ /**
2583
+ * Signs the given unsigned XDR and returns the signed XDR.
2584
+ *
2585
+ * - External wallets: signs locally via the wallet adapter.
2586
+ * - Custodial wallets: posts to `/tx/sign`. The backend signs (through
2587
+ * wallet-service or the app's customer-managed adapter) and returns the
2588
+ * signed XDR plus an `idempotencyKey` the caller should echo back to
2589
+ * `submitTx`.
2590
+ *
2591
+ * Drives `_setTransactionState`: emits `signing` while in flight and
2592
+ * `signed` on success (or `error[phase: 'signing']` on failure). `buildData`
2593
+ * is threaded through if the consumer previously called `buildTx`.
2594
+ */
2595
+ async signTx(unsignedXdr) {
2596
+ const buildData = this._currentBuildData();
2597
+ this._setTransactionState({ step: "signing", ...buildData && { buildData } });
2377
2598
  if (this._walletAdapter) {
2599
+ const accountToSign = this._session?.wallet?.publicKey;
2600
+ const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2378
2601
  try {
2379
- const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2380
2602
  const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
2381
- const stellarClient = new StellarClient(this.getNetwork());
2382
- const result = await stellarClient.submitTransaction(signedTxXdr);
2383
- if (result.success) {
2384
- this._setTransactionState({ step: "success", ...stateExtra, hash: result.hash });
2385
- } else {
2386
- this._setTransactionState({ step: "error", ...stateExtra, details: result.errorCode });
2603
+ this._setTransactionState({
2604
+ step: "signed",
2605
+ signedXdr: signedTxXdr,
2606
+ ...buildData && { buildData }
2607
+ });
2608
+ return { status: "signed", signedXdr: signedTxXdr };
2609
+ } catch (err) {
2610
+ const details = err instanceof Error ? err.message : void 0;
2611
+ this._setTransactionState({
2612
+ step: "error",
2613
+ phase: "signing",
2614
+ ...buildData && { buildData },
2615
+ ...details && { details }
2616
+ });
2617
+ return { status: "error", ...details && { details } };
2618
+ }
2619
+ }
2620
+ const publicKey = this._session?.wallet?.publicKey ?? "";
2621
+ try {
2622
+ const { data, error } = await this._api.POST("/tx/sign", {
2623
+ body: { network: this.getNetwork(), publicKey, unsignedXdr }
2624
+ });
2625
+ if (!error && data?.success && data.content?.signedXdr) {
2626
+ const { signedXdr, idempotencyKey } = data.content;
2627
+ this._setTransactionState({
2628
+ step: "signed",
2629
+ signedXdr,
2630
+ submissionToken: idempotencyKey,
2631
+ ...buildData && { buildData }
2632
+ });
2633
+ return { status: "signed", signedXdr, submissionToken: idempotencyKey };
2634
+ }
2635
+ const details = error?.details;
2636
+ this._setTransactionState({
2637
+ step: "error",
2638
+ phase: "signing",
2639
+ ...buildData && { buildData },
2640
+ ...details && { details }
2641
+ });
2642
+ return { status: "error", ...details && { details } };
2643
+ } catch (err) {
2644
+ const details = err instanceof Error ? err.message : void 0;
2645
+ this._setTransactionState({
2646
+ step: "error",
2647
+ phase: "signing",
2648
+ ...buildData && { buildData },
2649
+ ...details && { details }
2650
+ });
2651
+ return { status: "error", ...details && { details } };
2652
+ }
2653
+ }
2654
+ /**
2655
+ * Submits a signed XDR via `/tx/submit` regardless of wallet type
2656
+ * (custodial or external). Routing through sdk-api gives us:
2657
+ * - End-to-end tx_records persistence with full phase lifecycle so the
2658
+ * developer dashboard can show every tx (both custodial and external
2659
+ * wallet flows) at `/apps/:id/monitor/transactions`.
2660
+ * - Idempotency tracking via `submissionToken` (returned by `signTx`).
2661
+ * - A single response shape (SUCCESS / PENDING / FAILED) shared by both
2662
+ * flows — previously external wallets could only return SUCCESS or
2663
+ * error since the direct-to-Horizon path was synchronous.
2664
+ *
2665
+ * The extra hop adds ~50–150 ms vs. the legacy direct-Horizon path; the
2666
+ * persistence + observability win is worth it.
2667
+ *
2668
+ * Drives `_setTransactionState`: emits `submitting` while in flight,
2669
+ * `submitted` on Horizon ack (pending), `success` on ledger confirmation,
2670
+ * or `error[phase: 'submitting']` on failure.
2671
+ */
2672
+ async submitTx(signedXdr, opts) {
2673
+ const buildData = this._currentBuildData();
2674
+ const outcomeExtra = buildData ? { buildData } : {};
2675
+ this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
2676
+ const publicKey = this._session?.wallet?.publicKey ?? "";
2677
+ try {
2678
+ const { data, error } = await this._api.POST("/tx/submit", {
2679
+ body: {
2680
+ network: this.getNetwork(),
2681
+ publicKey,
2682
+ signedXdr,
2683
+ ...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
2387
2684
  }
2388
- } catch {
2389
- this._setTransactionState({ step: "error", ...stateExtra });
2685
+ });
2686
+ if (!error && data?.success && data.content) {
2687
+ const { hash, status: backendStatus, resultCode } = data.content;
2688
+ if (backendStatus === "SUCCESS") {
2689
+ this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
2690
+ return { status: "success", hash, ...outcomeExtra };
2691
+ }
2692
+ if (backendStatus === "PENDING") {
2693
+ this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
2694
+ return { status: "pending", hash, ...outcomeExtra };
2695
+ }
2696
+ this._setTransactionState({
2697
+ step: "error",
2698
+ phase: "submitting",
2699
+ ...buildData && { buildData },
2700
+ ...resultCode && { details: resultCode }
2701
+ });
2702
+ return {
2703
+ status: "error",
2704
+ hash,
2705
+ ...outcomeExtra,
2706
+ ...resultCode && { details: resultCode, resultCode }
2707
+ };
2390
2708
  }
2391
- return;
2709
+ const details = error?.details;
2710
+ this._setTransactionState({
2711
+ step: "error",
2712
+ phase: "submitting",
2713
+ ...buildData && { buildData },
2714
+ ...details && { details }
2715
+ });
2716
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2717
+ } catch (err) {
2718
+ const details = err instanceof Error ? err.message : void 0;
2719
+ this._setTransactionState({
2720
+ step: "error",
2721
+ phase: "submitting",
2722
+ ...buildData && { buildData },
2723
+ ...details && { details }
2724
+ });
2725
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2392
2726
  }
2727
+ }
2728
+ /**
2729
+ * Signs and submits in one logical step. Returns a {@link SubmitOutcome}.
2730
+ *
2731
+ * - **External wallets**: composes `signTx` + `submitTx` client-side. State
2732
+ * machine sees the full granular sequence `signing → signed → submitting
2733
+ * → success` because the underlying methods each emit.
2734
+ * - **Custodial wallets**: atomic `/tx/sign-and-send` round-trip. State
2735
+ * machine emits the compound `signing-submitting` step (the SDK can't
2736
+ * observe when one phase ends and the next begins inside that single
2737
+ * backend call) and then transitions to `submitted` (Horizon ack only) or
2738
+ * `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
2739
+ */
2740
+ async signAndSubmitTx(unsignedXdr) {
2741
+ if (this._walletAdapter) {
2742
+ const signed = await this.signTx(unsignedXdr);
2743
+ if (signed.status === "error") {
2744
+ const buildData2 = this._currentBuildData();
2745
+ return {
2746
+ status: "error",
2747
+ ...buildData2 && { buildData: buildData2 },
2748
+ ...signed.details && { details: signed.details }
2749
+ };
2750
+ }
2751
+ return this.submitTx(signed.signedXdr);
2752
+ }
2753
+ const buildData = this._currentBuildData();
2754
+ const outcomeExtra = buildData ? { buildData } : {};
2755
+ this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
2393
2756
  const body = {
2394
2757
  network: this.getNetwork(),
2395
2758
  publicKey: this._session?.wallet?.publicKey ?? "",
@@ -2398,14 +2761,128 @@ var PollarClient = class {
2398
2761
  try {
2399
2762
  const { data, error } = await this._api.POST("/tx/sign-and-send", { body });
2400
2763
  if (!error && data?.success && data.content?.hash) {
2401
- this._setTransactionState({ step: "success", ...stateExtra, hash: data.content.hash });
2402
- } else {
2403
- const details = error?.details;
2404
- this._setTransactionState({ step: "error", ...stateExtra, ...details && { details } });
2764
+ const {
2765
+ hash,
2766
+ status: backendStatus,
2767
+ resultCode
2768
+ } = data.content;
2769
+ if (backendStatus === "SUCCESS") {
2770
+ this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
2771
+ return { status: "success", hash, ...outcomeExtra };
2772
+ }
2773
+ if (backendStatus === "PENDING") {
2774
+ this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
2775
+ return { status: "pending", hash, ...outcomeExtra };
2776
+ }
2777
+ this._setTransactionState({
2778
+ step: "error",
2779
+ phase: "signing-submitting",
2780
+ ...buildData && { buildData },
2781
+ ...resultCode && { details: resultCode }
2782
+ });
2783
+ return {
2784
+ status: "error",
2785
+ hash,
2786
+ ...outcomeExtra,
2787
+ ...resultCode && { details: resultCode, resultCode }
2788
+ };
2405
2789
  }
2406
- } catch {
2407
- this._setTransactionState({ step: "error", ...stateExtra });
2790
+ const details = error?.details;
2791
+ this._setTransactionState({
2792
+ step: "error",
2793
+ phase: "signing-submitting",
2794
+ ...buildData && { buildData },
2795
+ ...details && { details }
2796
+ });
2797
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2798
+ } catch (err) {
2799
+ const details = err instanceof Error ? err.message : void 0;
2800
+ this._setTransactionState({
2801
+ step: "error",
2802
+ phase: "signing-submitting",
2803
+ ...buildData && { buildData },
2804
+ ...details && { details }
2805
+ });
2806
+ return { status: "error", ...outcomeExtra, ...details && { details } };
2807
+ }
2808
+ }
2809
+ /**
2810
+ * One-shot: build → sign → submit, returning the final {@link SubmitOutcome}.
2811
+ *
2812
+ * - **External wallets**: composes `buildTx` + `signAndSubmitTx` client-side.
2813
+ * State machine sees the full granular sequence (`building → built →
2814
+ * signing → signed → submitting → success`) because each composed call
2815
+ * emits its own transitions.
2816
+ * - **Custodial wallets**: single round-trip to `/tx/build-sign-submit`. The
2817
+ * signed XDR never leaves the backend. State machine emits the compound
2818
+ * `building-signing-submitting` step (the SDK can't observe individual
2819
+ * phase boundaries inside one atomic call) and then transitions to
2820
+ * `submitted` / `success` / `error[phase: 'building-signing-submitting']`.
2821
+ *
2822
+ * If you need granular UI feedback for custodial flows (separate
2823
+ * "Building…", "Signing…", "Submitting…" indicators), call `buildTx`,
2824
+ * `signTx`, and `submitTx` separately instead.
2825
+ */
2826
+ async buildAndSignAndSubmitTx(operation, params, options) {
2827
+ if (this._walletAdapter) {
2828
+ const built = await this.buildTx(operation, params, options);
2829
+ if (built.status === "error") {
2830
+ return { status: "error", ...built.details && { details: built.details } };
2831
+ }
2832
+ return this.signAndSubmitTx(built.buildData.unsignedXdr);
2408
2833
  }
2834
+ if (!this._session?.wallet?.publicKey) {
2835
+ this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
2836
+ return { status: "error", details: "No wallet connected" };
2837
+ }
2838
+ this._setTransactionState({ step: "building-signing-submitting" });
2839
+ try {
2840
+ const { data, error } = await this._api.POST("/tx/build-sign-submit", {
2841
+ body: {
2842
+ network: this.getNetwork(),
2843
+ publicKey: this._session.wallet.publicKey,
2844
+ operation,
2845
+ params,
2846
+ options: options ?? {}
2847
+ }
2848
+ });
2849
+ if (!error && data?.success && data.content) {
2850
+ const { hash, status: backendStatus, resultCode } = data.content;
2851
+ if (backendStatus === "SUCCESS") {
2852
+ this._setTransactionState({ step: "success", hash });
2853
+ return { status: "success", hash };
2854
+ }
2855
+ if (backendStatus === "PENDING") {
2856
+ this._setTransactionState({ step: "submitted", hash });
2857
+ return { status: "pending", hash };
2858
+ }
2859
+ this._setTransactionState({
2860
+ step: "error",
2861
+ phase: "building-signing-submitting",
2862
+ ...resultCode && { details: resultCode }
2863
+ });
2864
+ return { status: "error", hash, ...resultCode && { details: resultCode, resultCode } };
2865
+ }
2866
+ const details = error?.details;
2867
+ this._setTransactionState({
2868
+ step: "error",
2869
+ phase: "building-signing-submitting",
2870
+ ...details && { details }
2871
+ });
2872
+ return { status: "error", ...details && { details } };
2873
+ } catch (err) {
2874
+ const details = err instanceof Error ? err.message : void 0;
2875
+ this._setTransactionState({
2876
+ step: "error",
2877
+ phase: "building-signing-submitting",
2878
+ ...details && { details }
2879
+ });
2880
+ return { status: "error", ...details && { details } };
2881
+ }
2882
+ }
2883
+ /** Alias for {@link buildAndSignAndSubmitTx} — shorter "just do the thing" name. */
2884
+ async runTx(operation, params, options) {
2885
+ return this.buildAndSignAndSubmitTx(operation, params, options);
2409
2886
  }
2410
2887
  // ─── App config ───────────────────────────────────────────────────────────
2411
2888
  async getAppConfig() {
@@ -2449,6 +2926,13 @@ var PollarClient = class {
2449
2926
  pollRampTransaction(txId, opts) {
2450
2927
  return pollRampTransaction(this._api, txId, opts);
2451
2928
  }
2929
+ // ─── Distribution ─────────────────────────────────────────────────────────
2930
+ listDistributionRules() {
2931
+ return listDistributionRules(this._api);
2932
+ }
2933
+ claimDistributionRule(body) {
2934
+ return claimDistributionRule(this._api, body);
2935
+ }
2452
2936
  _setTxHistoryState(next) {
2453
2937
  this._txHistoryState = next;
2454
2938
  for (const cb of this._txHistoryStateListeners) cb(next);
@@ -2487,7 +2971,22 @@ var PollarClient = class {
2487
2971
  */
2488
2972
  async _resolveWalletAdapter(id) {
2489
2973
  if (this._walletAdapterResolver) {
2490
- return Promise.resolve(this._walletAdapterResolver(id));
2974
+ const timeoutMs = this._walletResolverTimeoutMs;
2975
+ let timeoutHandle;
2976
+ const timeoutPromise = new Promise((_, reject) => {
2977
+ timeoutHandle = setTimeout(() => {
2978
+ reject(
2979
+ Object.assign(new Error(`[PollarClient] Wallet adapter resolver for "${id}" timed out after ${timeoutMs}ms`), {
2980
+ code: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
2981
+ })
2982
+ );
2983
+ }, timeoutMs);
2984
+ });
2985
+ try {
2986
+ return await Promise.race([Promise.resolve(this._walletAdapterResolver(id)), timeoutPromise]);
2987
+ } finally {
2988
+ if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
2989
+ }
2491
2990
  }
2492
2991
  if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
2493
2992
  if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
@@ -2501,6 +3000,16 @@ var PollarClient = class {
2501
3000
  this._setAuthState({ step: "idle" });
2502
3001
  return;
2503
3002
  }
3003
+ if (error instanceof Error && error.code === AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT) {
3004
+ console.error("[PollarClient]", error.message);
3005
+ this._setAuthState({
3006
+ step: "error",
3007
+ previousStep: this._authState.step,
3008
+ message: error.message,
3009
+ errorCode: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
3010
+ });
3011
+ return;
3012
+ }
2504
3013
  console.error("[PollarClient] Unexpected error in auth flow", error);
2505
3014
  this._setAuthState({
2506
3015
  step: "error",
@@ -2522,6 +3031,7 @@ var PollarClient = class {
2522
3031
  }
2523
3032
  console.info("[PollarClient] Session restored from storage");
2524
3033
  this._setAuthState({ step: "authenticated", session: this._session });
3034
+ this._scheduleNextRefresh();
2525
3035
  } else {
2526
3036
  console.info("[PollarClient] No session in storage");
2527
3037
  }
@@ -2548,9 +3058,11 @@ var PollarClient = class {
2548
3058
  }
2549
3059
  await writeStorage(this._storage, this.apiKeyHash, persisted);
2550
3060
  this._setAuthState({ step: "authenticated", session: persisted });
3061
+ this._scheduleNextRefresh();
2551
3062
  }
2552
3063
  async _clearSession() {
2553
3064
  console.info("[PollarClient] Session cleared");
3065
+ this._clearRefreshTimer();
2554
3066
  this._session = null;
2555
3067
  this._profile = null;
2556
3068
  this._walletAdapter = null;
@@ -2583,6 +3095,46 @@ var PollarClient = class {
2583
3095
  console.info(`[PollarClient] transaction:${next.step}`);
2584
3096
  for (const cb of this._transactionStateListeners) cb(next);
2585
3097
  }
3098
+ /**
3099
+ * Threads `buildData` through state transitions. When the user has already
3100
+ * called `buildTx`, every subsequent state (signing, signed, submitting,
3101
+ * submitted, success, error) should carry the build summary so modal UIs
3102
+ * can keep showing "Send 5 USDC to G..." through the whole flow.
3103
+ */
3104
+ _currentBuildData() {
3105
+ const s = this._transactionState;
3106
+ if (!s) return void 0;
3107
+ if ("buildData" in s && s.buildData) return s.buildData;
3108
+ return void 0;
3109
+ }
3110
+ };
3111
+
3112
+ // src/stellar/StellarClient.ts
3113
+ var HORIZON_URLS = {
3114
+ mainnet: "https://horizon.stellar.org",
3115
+ testnet: "https://horizon-testnet.stellar.org"
3116
+ };
3117
+ var StellarClient = class {
3118
+ constructor(config) {
3119
+ this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
3120
+ }
3121
+ async submitTransaction(signedXdr) {
3122
+ try {
3123
+ const response = await fetch(`${this.horizonUrl}/transactions`, {
3124
+ method: "POST",
3125
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3126
+ body: new URLSearchParams({ tx: signedXdr })
3127
+ });
3128
+ if (!response.ok) {
3129
+ const body = await response.json().catch(() => ({}));
3130
+ return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
3131
+ }
3132
+ const data = await response.json();
3133
+ return { success: true, hash: data.hash };
3134
+ } catch {
3135
+ return { success: false, errorCode: "NETWORK_ERROR" };
3136
+ }
3137
+ }
2586
3138
  };
2587
3139
 
2588
3140
  // src/index.rn.ts
@@ -2594,6 +3146,6 @@ _setDefaultKeyManagerFactory((storage, apiKey) => {
2594
3146
  return new NobleKeyManager(storage, apiKey);
2595
3147
  });
2596
3148
 
2597
- export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, NobleKeyManager, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
3149
+ export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, NobleKeyManager, 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 };
2598
3150
  //# sourceMappingURL=index.rn.mjs.map
2599
3151
  //# sourceMappingURL=index.rn.mjs.map