@pollar/core 0.9.0-rc.1 → 0.9.0-rc.3

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
@@ -1107,7 +1107,7 @@ function defaultStorage(options = {}) {
1107
1107
  }
1108
1108
 
1109
1109
  // src/version.ts
1110
- var POLLAR_CORE_VERSION = "0.9.0-rc.1" ;
1110
+ var POLLAR_CORE_VERSION = "0.9.0-rc.3" ;
1111
1111
 
1112
1112
  // src/visibility/noop.ts
1113
1113
  function createNoopVisibilityProvider() {
@@ -1279,7 +1279,10 @@ function openAlbedoPopup(url) {
1279
1279
  }
1280
1280
  function waitForAlbedoPopup() {
1281
1281
  return new Promise((resolve, reject) => {
1282
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1282
+ const timeout = setTimeout(() => {
1283
+ window.removeEventListener("message", handler);
1284
+ reject(new Error("Albedo response timeout"));
1285
+ }, 2 * 60 * 1e3);
1283
1286
  function handler(event) {
1284
1287
  if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
1285
1288
  clearTimeout(timeout);
@@ -1289,24 +1292,6 @@ function waitForAlbedoPopup() {
1289
1292
  window.addEventListener("message", handler);
1290
1293
  });
1291
1294
  }
1292
- function waitForAlbedoResult() {
1293
- return new Promise((resolve, reject) => {
1294
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1295
- const parseResult = () => {
1296
- const params = new URLSearchParams(window.location.search);
1297
- if (!params.has("pubkey") && !params.has("signed_envelope_xdr") && !params.has("signed_xdr")) return;
1298
- clearTimeout(timeout);
1299
- const result = {};
1300
- params.forEach((value, key) => {
1301
- result[key] = value;
1302
- });
1303
- window.history.replaceState({}, document.title, window.location.pathname);
1304
- resolve(result);
1305
- };
1306
- parseResult();
1307
- window.addEventListener("popstate", parseResult);
1308
- });
1309
- }
1310
1295
  var AlbedoAdapter = class {
1311
1296
  /**
1312
1297
  * Network used for `connect` and `signAuthEntry` (which carry no per-call
@@ -1348,10 +1333,10 @@ var AlbedoAdapter = class {
1348
1333
  url.searchParams.set("xdr", xdr);
1349
1334
  url.searchParams.set("app_name", "Pollar");
1350
1335
  url.searchParams.set("network", albedoNetwork(options, this.network));
1351
- url.searchParams.set("callback", window.location.href);
1336
+ url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1352
1337
  url.searchParams.set("origin", window.location.origin);
1353
- window.location.href = url.toString();
1354
- const result = await waitForAlbedoResult();
1338
+ openAlbedoPopup(url.toString());
1339
+ const result = await waitForAlbedoPopup();
1355
1340
  if (!result.signed_envelope_xdr) throw new Error("Albedo signing rejected");
1356
1341
  return { signedTxXdr: result.signed_envelope_xdr };
1357
1342
  }
@@ -1361,10 +1346,10 @@ var AlbedoAdapter = class {
1361
1346
  url.searchParams.set("xdr", entryXdr);
1362
1347
  url.searchParams.set("app_name", "Pollar");
1363
1348
  url.searchParams.set("network", this.network);
1364
- url.searchParams.set("callback", window.location.href);
1349
+ url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1365
1350
  url.searchParams.set("origin", window.location.origin);
1366
- window.location.href = url.toString();
1367
- const result = await waitForAlbedoResult();
1351
+ openAlbedoPopup(url.toString());
1352
+ const result = await waitForAlbedoPopup();
1368
1353
  if (!result.signed_xdr) throw new Error("Albedo auth entry signing rejected");
1369
1354
  return { signedAuthEntry: result.signed_xdr };
1370
1355
  }
@@ -1447,8 +1432,8 @@ function isValidSession(value, logger = console) {
1447
1432
  return false;
1448
1433
  }
1449
1434
  const w = wallet;
1450
- if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
1451
- logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
1435
+ if (w["type"] !== "internal" && w["type"] !== "smart" && w["type"] !== "external") {
1436
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be internal|smart|external");
1452
1437
  return false;
1453
1438
  }
1454
1439
  if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
@@ -1479,6 +1464,9 @@ async function readStorage(storage, apiKeyHash, logger = console) {
1479
1464
  if (w && w["address"] == null && typeof w["publicKey"] === "string") {
1480
1465
  w["address"] = w["publicKey"];
1481
1466
  }
1467
+ if (w && w["type"] === "custodial") {
1468
+ w["type"] = "internal";
1469
+ }
1482
1470
  }
1483
1471
  if (!isValidSession(session, logger)) {
1484
1472
  await storage.remove(sessionStorageKey(apiKeyHash));
@@ -1579,16 +1567,12 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1579
1567
  }
1580
1568
  const reader = data.getReader();
1581
1569
  const decoder = new TextDecoder();
1582
- let streamDone = false;
1583
1570
  let sawAnyChunk = false;
1584
1571
  try {
1585
1572
  while (true) {
1586
1573
  throwIfAborted(signal);
1587
1574
  const { done, value } = await reader.read();
1588
- if (done) {
1589
- streamDone = true;
1590
- break;
1591
- }
1575
+ if (done) break;
1592
1576
  sawAnyChunk = true;
1593
1577
  const chunk = decoder.decode(value);
1594
1578
  for (const message of chunk.split("\n\n").filter(Boolean)) {
@@ -1616,8 +1600,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1616
1600
  }
1617
1601
  if (sawAnyChunk) backoff = retryDelayMs;
1618
1602
  else backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
1619
- const delay = streamDone ? backoff : 0;
1620
- if (delay) await sleep(delay);
1603
+ await sleep(backoff);
1621
1604
  }
1622
1605
  }
1623
1606
  async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal, logger = console) {
@@ -1697,17 +1680,18 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1697
1680
  signal
1698
1681
  });
1699
1682
  if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content, logger)) {
1700
- if (expectedWallet && data.content.data.providers.wallet?.address !== expectedWallet) {
1683
+ const sessionWallet = data.content.data?.providers?.wallet?.address;
1684
+ if (expectedWallet && sessionWallet !== expectedWallet) {
1701
1685
  setAuthState({
1702
1686
  step: "error",
1703
1687
  previousStep: "authenticating",
1704
1688
  message: "Wallet mismatch: session wallet does not match connected wallet",
1705
1689
  errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
1706
1690
  });
1707
- clearSession();
1691
+ await clearSession();
1708
1692
  return;
1709
1693
  }
1710
- storeSession(data.content);
1694
+ await storeSession(data.content);
1711
1695
  } else {
1712
1696
  setAuthState({
1713
1697
  step: "error",
@@ -1715,7 +1699,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1715
1699
  message: "Failed to load session",
1716
1700
  errorCode: AUTH_ERROR_CODES.AUTH_FAILED
1717
1701
  });
1718
- clearSession();
1702
+ await clearSession();
1719
1703
  }
1720
1704
  }
1721
1705
 
@@ -1844,7 +1828,7 @@ async function loginOAuth(provider, deps) {
1844
1828
  }
1845
1829
 
1846
1830
  // src/client/auth/passkeyFlow.ts
1847
- async function loginSmartWallet(deps) {
1831
+ async function smartWalletFlow(deps, mode) {
1848
1832
  const { api, signal, setAuthState, passkey } = deps;
1849
1833
  if (!passkey) {
1850
1834
  setAuthState({
@@ -1867,7 +1851,7 @@ async function loginSmartWallet(deps) {
1867
1851
  return failPasskey(setAuthState, "Failed to start passkey");
1868
1852
  }
1869
1853
  setAuthState({ step: "creating_passkey" });
1870
- const ceremony = await passkey({ challenge });
1854
+ const ceremony = await passkey({ challenge, mode });
1871
1855
  const response = ceremony.response;
1872
1856
  if (ceremony.kind === "register") {
1873
1857
  setAuthState({ step: "deploying_smart_account" });
@@ -2031,7 +2015,7 @@ var PollarClient = class {
2031
2015
  this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
2032
2016
  this._maxIdleMs = config.maxIdleMs;
2033
2017
  this._openAuthUrl = config.openAuthUrl ?? defaultWebOAuthOpener;
2034
- this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location.origin : "");
2018
+ this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location?.origin ?? "" : "");
2035
2019
  this._api = createApiClient(this.basePath);
2036
2020
  this._wireMiddlewares();
2037
2021
  this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
@@ -2460,9 +2444,10 @@ var PollarClient = class {
2460
2444
  loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2461
2445
  }
2462
2446
  /**
2463
- * "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
2464
- * user, creates a sponsored smart-account C-address. Requires the `passkey`
2465
- * ceremony to be configured (e.g. via `@pollar/react`).
2447
+ * "Smart Wallet" login: runs the passkey (WebAuthn) `get()` ceremony for a
2448
+ * returning user and signs them in. Use {@link createSmartWallet} for a new
2449
+ * user. Requires the `passkey` ceremony to be configured (e.g. via
2450
+ * `@pollar/react`).
2466
2451
  */
2467
2452
  loginSmartWallet() {
2468
2453
  if (!isClientRuntime) {
@@ -2470,7 +2455,21 @@ var PollarClient = class {
2470
2455
  return;
2471
2456
  }
2472
2457
  const controller = this._newController();
2473
- loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2458
+ smartWalletFlow(this._flowDeps(controller.signal), "login").catch((err) => this._handleFlowError(err));
2459
+ }
2460
+ /**
2461
+ * "Smart Wallet" registration: runs the passkey (WebAuthn) `create()` ceremony
2462
+ * for a new user and deploys a sponsored smart-account C-address. Use
2463
+ * {@link loginSmartWallet} for a returning user. Requires the `passkey`
2464
+ * ceremony to be configured (e.g. via `@pollar/react`).
2465
+ */
2466
+ createSmartWallet() {
2467
+ if (!isClientRuntime) {
2468
+ warnServerSide("createSmartWallet");
2469
+ return;
2470
+ }
2471
+ const controller = this._newController();
2472
+ smartWalletFlow(this._flowDeps(controller.signal), "register").catch((err) => this._handleFlowError(err));
2474
2473
  }
2475
2474
  // ─── Cancel ───────────────────────────────────────────────────────────────
2476
2475
  cancelLogin() {
@@ -2710,6 +2709,54 @@ var PollarClient = class {
2710
2709
  this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2711
2710
  }
2712
2711
  }
2712
+ /**
2713
+ * Establishes (omit `limit`) or removes (`limit: '0'`) a trustline for an asset.
2714
+ *
2715
+ * Routing mirrors how the platform pays for the reserve:
2716
+ * - **Sponsored custodial** (`opts.sponsored` true, internal wallet) → the
2717
+ * server orchestrates a sponsored `changeTrust`: the app's wallets cover the
2718
+ * 0.5 XLM reserve and the fee, so the user pays nothing. Pass the asset's
2719
+ * `sponsored` flag (from {@link refreshAssets}) straight through.
2720
+ * - **Self-paid** (external/adapter wallet, sponsorship disabled, or a custom
2721
+ * asset not configured in the app) → a plain `change_trust` transaction the
2722
+ * user's own wallet signs and pays for, via {@link runTx}.
2723
+ *
2724
+ * Does not refresh on its own — callers should `refreshAssets()` afterwards.
2725
+ */
2726
+ async setTrustline(asset, opts) {
2727
+ const limit = opts?.limit;
2728
+ const walletType = this._session?.wallet?.type;
2729
+ if (!this._session?.wallet?.address) {
2730
+ return { status: "error", details: "No wallet connected" };
2731
+ }
2732
+ if (walletType === "smart") {
2733
+ return { status: "error", details: "Trustlines do not apply to smart wallets" };
2734
+ }
2735
+ if (opts?.sponsored && !this._walletAdapter && walletType === "internal") {
2736
+ try {
2737
+ const { data, error } = await this._api.POST("/wallet/assets/trustline", {
2738
+ body: { code: asset.code, issuer: asset.issuer, ...limit !== void 0 && { limit } }
2739
+ });
2740
+ if (!error && data?.success) {
2741
+ if (data.content) this._setEnabledAssetsState({ step: "loaded", data: data.content });
2742
+ return { status: "success" };
2743
+ }
2744
+ const details = error?.details ?? error?.code;
2745
+ return { status: "error", ...details && { details } };
2746
+ } catch (err) {
2747
+ const details = err instanceof Error ? err.message : void 0;
2748
+ return { status: "error", ...details && { details } };
2749
+ }
2750
+ }
2751
+ return this.runTx("change_trust", {
2752
+ asset: {
2753
+ type: asset.code.length <= 4 ? "credit_alphanum4" : "credit_alphanum12",
2754
+ code: asset.code,
2755
+ issuer: asset.issuer
2756
+ },
2757
+ ...limit !== void 0 && { limit }
2758
+ });
2759
+ }
2713
2760
  // ─── Transactions ─────────────────────────────────────────────────────────
2714
2761
  /**
2715
2762
  * Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
@@ -3345,6 +3392,9 @@ var PollarClient = class {
3345
3392
  void this._resume();
3346
3393
  } else {
3347
3394
  this._log.info("[PollarClient] No session in storage");
3395
+ if (this._authState.step !== "idle") {
3396
+ await this._clearSession();
3397
+ }
3348
3398
  }
3349
3399
  }
3350
3400
  /**
@@ -3391,8 +3441,11 @@ var PollarClient = class {
3391
3441
  user: session.user,
3392
3442
  // The wire response still carries the legacy `publicKey` alias (kept for
3393
3443
  // older SDKs); the persisted session standardizes on `address` only.
3444
+ // The wire also still emits the legacy type `'custodial'` (unchanged for
3445
+ // SDKs ≤0.8.x); we remap it to `'internal'` here so the SDK surface and
3446
+ // persisted session speak one vocabulary while the wire stays compatible.
3394
3447
  wallet: {
3395
- type: w.type,
3448
+ type: w.type === "custodial" ? "internal" : w.type,
3396
3449
  address: w.address ?? w.publicKey ?? null,
3397
3450
  ...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
3398
3451
  ...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},