@pollar/core 0.8.1 → 0.9.0-rc.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.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var p256 = require('@noble/curves/p256');
3
+ var nist = require('@noble/curves/nist');
4
+ var sha2 = require('@noble/hashes/sha2');
4
5
 
5
6
  var __create = Object.create;
6
7
  var __defProp = Object.defineProperty;
@@ -186,11 +187,8 @@ function defaultKeyManager(storage, apiKey) {
186
187
  }
187
188
  return _factory(storage, apiKey);
188
189
  }
189
-
190
- // src/lib/sha256.ts
191
190
  async function sha256(data) {
192
- const buf = await crypto.subtle.digest("SHA-256", data);
193
- return new Uint8Array(buf);
191
+ return sha2.sha256(data);
194
192
  }
195
193
 
196
194
  // src/lib/api-key-hash.ts
@@ -269,11 +267,14 @@ async function computeJwkThumbprint(jwk) {
269
267
  const digest = await sha256(new TextEncoder().encode(canonical));
270
268
  return base64urlEncode(digest);
271
269
  }
270
+ function toBase64url(value) {
271
+ return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/[^A-Za-z0-9_-]/g, "");
272
+ }
272
273
  function canonicalEcJwk(jwk) {
273
274
  if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
274
275
  throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
275
276
  }
276
- return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
277
+ return { kty: "EC", crv: "P-256", x: toBase64url(jwk.x), y: toBase64url(jwk.y) };
277
278
  }
278
279
 
279
280
  // src/keys/noble.ts
@@ -325,14 +326,14 @@ var NobleKeyManager = class {
325
326
  priv = null;
326
327
  }
327
328
  if (!priv) {
328
- priv = p256.p256.utils.randomPrivateKey();
329
+ priv = nist.p256.utils.randomPrivateKey();
329
330
  try {
330
331
  await this.storage.set(this.storageKey, base64urlEncode(priv));
331
332
  } catch {
332
333
  }
333
334
  }
334
335
  this.privateKey = priv;
335
- const pub = p256.p256.getPublicKey(priv, false);
336
+ const pub = nist.p256.getPublicKey(priv, false);
336
337
  if (pub.length !== 65 || pub[0] !== 4) {
337
338
  throw new Error("[PollarClient:keys] Unexpected public key format from @noble/curves");
338
339
  }
@@ -374,172 +375,11 @@ var NobleKeyManager = class {
374
375
  throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
375
376
  }
376
377
  const digest = await sha256(payload);
377
- const signature = p256.p256.sign(digest, this.privateKey, { prehash: false });
378
+ const signature = nist.p256.sign(digest, this.privateKey, { prehash: false });
378
379
  return signature.toCompactRawBytes();
379
380
  }
380
381
  };
381
382
 
382
- // src/keys/web-crypto.ts
383
- var DB_NAME = "pollar-keys";
384
- var DB_VERSION = 1;
385
- var STORE_NAME = "keys";
386
- function openDb() {
387
- return new Promise((resolve, reject) => {
388
- if (typeof indexedDB === "undefined") {
389
- reject(new Error("[PollarClient:keys] IndexedDB not available"));
390
- return;
391
- }
392
- const req = indexedDB.open(DB_NAME, DB_VERSION);
393
- req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
394
- req.onsuccess = () => resolve(req.result);
395
- req.onupgradeneeded = () => {
396
- const db = req.result;
397
- if (!db.objectStoreNames.contains(STORE_NAME)) {
398
- db.createObjectStore(STORE_NAME);
399
- }
400
- };
401
- });
402
- }
403
- function awaitTx(req) {
404
- return new Promise((resolve, reject) => {
405
- req.onsuccess = () => resolve(req.result);
406
- req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
407
- });
408
- }
409
- async function dbGet(key) {
410
- const db = await openDb();
411
- try {
412
- const tx = db.transaction(STORE_NAME, "readonly");
413
- const result = await awaitTx(tx.objectStore(STORE_NAME).get(key));
414
- return result;
415
- } finally {
416
- db.close();
417
- }
418
- }
419
- async function dbPut(key, value) {
420
- const db = await openDb();
421
- try {
422
- const tx = db.transaction(STORE_NAME, "readwrite");
423
- await awaitTx(tx.objectStore(STORE_NAME).put(value, key));
424
- } finally {
425
- db.close();
426
- }
427
- }
428
- async function dbDelete(key) {
429
- const db = await openDb();
430
- try {
431
- const tx = db.transaction(STORE_NAME, "readwrite");
432
- await awaitTx(tx.objectStore(STORE_NAME).delete(key));
433
- } finally {
434
- db.close();
435
- }
436
- }
437
- function isCryptoKeyPair(v) {
438
- if (typeof v !== "object" || v === null) return false;
439
- const obj = v;
440
- return obj.privateKey !== void 0 && obj.publicKey !== void 0;
441
- }
442
- var WebCryptoKeyManager = class {
443
- constructor(apiKey) {
444
- this.apiKeyHash = null;
445
- this.keyPair = null;
446
- this.publicJwk = null;
447
- this.thumbprint = null;
448
- /**
449
- * Cached in-flight init. Lets `init()` be called concurrently (or implicitly
450
- * from `getPublicJwk` / `sign`) without doing the work twice. Cleared on
451
- * failure so callers can retry, and cleared on `reset()`.
452
- */
453
- this._initPromise = null;
454
- if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
455
- throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
456
- }
457
- this.apiKey = apiKey;
458
- }
459
- /**
460
- * Idempotent and safe under concurrency. The first call kicks off the real
461
- * init; subsequent (and concurrent) calls return the same in-flight promise.
462
- * Other methods (`getPublicJwk`, `getThumbprint`, `sign`) auto-await this so
463
- * the manager is self-healing if `init()` was never explicitly invoked.
464
- */
465
- async init() {
466
- if (this.keyPair) return;
467
- if (!this._initPromise) {
468
- this._initPromise = this._doInit().catch((err) => {
469
- console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
470
- this._initPromise = null;
471
- throw err;
472
- });
473
- }
474
- return this._initPromise;
475
- }
476
- async _doInit() {
477
- if (!this.apiKeyHash) {
478
- this.apiKeyHash = await hashApiKey(this.apiKey);
479
- }
480
- let pair;
481
- try {
482
- pair = await dbGet(this.apiKeyHash);
483
- if (pair && !isCryptoKeyPair(pair)) pair = void 0;
484
- } catch {
485
- pair = void 0;
486
- }
487
- if (!pair) {
488
- pair = await globalThis.crypto.subtle.generateKey(
489
- { name: "ECDSA", namedCurve: "P-256" },
490
- // false → private key non-extractable; per W3C ECDSA spec the public
491
- // key is always extractable regardless of this flag.
492
- false,
493
- ["sign", "verify"]
494
- );
495
- try {
496
- await dbPut(this.apiKeyHash, pair);
497
- } catch {
498
- }
499
- }
500
- this.keyPair = pair;
501
- const exported = await globalThis.crypto.subtle.exportKey("jwk", pair.publicKey);
502
- this.publicJwk = canonicalEcJwk(exported);
503
- this.thumbprint = await computeJwkThumbprint(this.publicJwk);
504
- }
505
- async reset() {
506
- try {
507
- if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
508
- } catch {
509
- }
510
- this.keyPair = null;
511
- this.publicJwk = null;
512
- this.thumbprint = null;
513
- this._initPromise = null;
514
- }
515
- async getPublicJwk() {
516
- if (!this.publicJwk) await this.init();
517
- if (!this.publicJwk) {
518
- throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
519
- }
520
- return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
521
- }
522
- async getThumbprint() {
523
- if (!this.thumbprint) await this.init();
524
- if (!this.thumbprint) {
525
- throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
526
- }
527
- return this.thumbprint;
528
- }
529
- async sign(payload) {
530
- if (!this.keyPair) await this.init();
531
- if (!this.keyPair) {
532
- throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
533
- }
534
- const sig = await globalThis.crypto.subtle.sign(
535
- { name: "ECDSA", hash: "SHA-256" },
536
- this.keyPair.privateKey,
537
- payload
538
- );
539
- return new Uint8Array(sig);
540
- }
541
- };
542
-
543
383
  // ../../node_modules/openapi-fetch/dist/index.mjs
544
384
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
545
385
  var supportsRequestInitExt = () => {
@@ -1259,6 +1099,9 @@ function defaultStorage(options = {}) {
1259
1099
  return createLocalStorageAdapter(options);
1260
1100
  }
1261
1101
 
1102
+ // src/version.ts
1103
+ var POLLAR_CORE_VERSION = "0.9.0-rc.0" ;
1104
+
1262
1105
  // src/visibility/noop.ts
1263
1106
  function createNoopVisibilityProvider() {
1264
1107
  return {
@@ -1323,6 +1166,7 @@ var AUTH_ERROR_CODES = {
1323
1166
  WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
1324
1167
  WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
1325
1168
  WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
1169
+ PASSKEY_FAILED: "PASSKEY_FAILED",
1326
1170
  UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
1327
1171
  };
1328
1172
  var PollarFlowError = class extends Error {
@@ -1368,7 +1212,7 @@ var FreighterAdapter = class {
1368
1212
  if (!userInfo?.publicKey) {
1369
1213
  throw new Error("Failed to get user information from Freighter");
1370
1214
  }
1371
- return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
1215
+ return { address: userInfo.publicKey };
1372
1216
  }
1373
1217
  async disconnect() {
1374
1218
  }
@@ -1406,6 +1250,19 @@ var FreighterAdapter = class {
1406
1250
  };
1407
1251
 
1408
1252
  // src/wallets/AlbedoAdapter.ts
1253
+ var PUBLIC_PASSPHRASE = "Public Global Stellar Network ; September 2015";
1254
+ var TESTNET_PASSPHRASE = "Test SDF Network ; September 2015";
1255
+ function albedoNetwork(options, fallback) {
1256
+ switch (options?.networkPassphrase) {
1257
+ case PUBLIC_PASSPHRASE:
1258
+ return "public";
1259
+ case TESTNET_PASSPHRASE:
1260
+ return "testnet";
1261
+ }
1262
+ if (options?.network === "public" || options?.network === "mainnet") return "public";
1263
+ if (options?.network === "testnet") return "testnet";
1264
+ return fallback;
1265
+ }
1409
1266
  function openAlbedoPopup(url) {
1410
1267
  const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
1411
1268
  if (!popup) {
@@ -1444,7 +1301,13 @@ function waitForAlbedoResult() {
1444
1301
  });
1445
1302
  }
1446
1303
  var AlbedoAdapter = class {
1447
- constructor() {
1304
+ /**
1305
+ * Network used for `connect` and `signAuthEntry` (which carry no per-call
1306
+ * network) and as the fallback for `signTransaction`. Defaults to `'testnet'`
1307
+ * to preserve the previous behavior when constructed with no argument.
1308
+ */
1309
+ constructor(network = "testnet") {
1310
+ this.network = network;
1448
1311
  this.type = "albedo" /* ALBEDO */;
1449
1312
  }
1450
1313
  async isAvailable() {
@@ -1454,7 +1317,7 @@ var AlbedoAdapter = class {
1454
1317
  const url = new URL("https://albedo.link");
1455
1318
  url.searchParams.set("intent", "public-key");
1456
1319
  url.searchParams.set("app_name", "Pollar");
1457
- url.searchParams.set("network", "testnet");
1320
+ url.searchParams.set("network", this.network);
1458
1321
  url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1459
1322
  url.searchParams.set("origin", window.location.origin);
1460
1323
  openAlbedoPopup(url.toString());
@@ -1462,7 +1325,7 @@ var AlbedoAdapter = class {
1462
1325
  if (!result.pubkey) {
1463
1326
  throw new Error("Albedo connection rejected");
1464
1327
  }
1465
- return { address: result.pubkey, publicKey: result.pubkey };
1328
+ return { address: result.pubkey };
1466
1329
  }
1467
1330
  async disconnect() {
1468
1331
  }
@@ -1472,12 +1335,12 @@ var AlbedoAdapter = class {
1472
1335
  async getNetwork() {
1473
1336
  throw new Error("Albedo does not expose network");
1474
1337
  }
1475
- async signTransaction(xdr, _options) {
1338
+ async signTransaction(xdr, options) {
1476
1339
  const url = new URL("https://albedo.link");
1477
1340
  url.searchParams.set("intent", "tx");
1478
1341
  url.searchParams.set("xdr", xdr);
1479
1342
  url.searchParams.set("app_name", "Pollar");
1480
- url.searchParams.set("network", "testnet");
1343
+ url.searchParams.set("network", albedoNetwork(options, this.network));
1481
1344
  url.searchParams.set("callback", window.location.href);
1482
1345
  url.searchParams.set("origin", window.location.origin);
1483
1346
  window.location.href = url.toString();
@@ -1490,7 +1353,7 @@ var AlbedoAdapter = class {
1490
1353
  url.searchParams.set("intent", "sign-auth-entry");
1491
1354
  url.searchParams.set("xdr", entryXdr);
1492
1355
  url.searchParams.set("app_name", "Pollar");
1493
- url.searchParams.set("network", "testnet");
1356
+ url.searchParams.set("network", this.network);
1494
1357
  url.searchParams.set("callback", window.location.href);
1495
1358
  url.searchParams.set("origin", window.location.origin);
1496
1359
  window.location.href = url.toString();
@@ -1577,8 +1440,12 @@ function isValidSession(value) {
1577
1440
  return false;
1578
1441
  }
1579
1442
  const w = wallet;
1580
- if (w["publicKey"] !== null && !isBoundedString(w["publicKey"], MAX_WALLET_PUBLIC_KEY)) {
1581
- console.warn("[PollarClient:session] Invalid session \u2014 wallet.publicKey must be string|null");
1443
+ if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
1444
+ console.warn("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
1445
+ return false;
1446
+ }
1447
+ if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
1448
+ console.warn("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
1582
1449
  return false;
1583
1450
  }
1584
1451
  if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
@@ -1589,6 +1456,10 @@ function isValidSession(value) {
1589
1456
  console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
1590
1457
  return false;
1591
1458
  }
1459
+ if (w["linkedAt"] !== void 0 && (typeof w["linkedAt"] !== "number" || !Number.isFinite(w["linkedAt"]))) {
1460
+ console.warn("[PollarClient:session] Invalid session \u2014 wallet.linkedAt must be a finite number if present");
1461
+ return false;
1462
+ }
1592
1463
  return true;
1593
1464
  }
1594
1465
  async function readStorage(storage, apiKeyHash) {
@@ -1596,6 +1467,12 @@ async function readStorage(storage, apiKeyHash) {
1596
1467
  if (!raw) return null;
1597
1468
  try {
1598
1469
  const session = JSON.parse(raw);
1470
+ if (typeof session === "object" && session !== null) {
1471
+ const w = session.wallet;
1472
+ if (w && w["address"] == null && typeof w["publicKey"] === "string") {
1473
+ w["address"] = w["publicKey"];
1474
+ }
1475
+ }
1599
1476
  if (!isValidSession(session)) {
1600
1477
  await storage.remove(sessionStorageKey(apiKeyHash));
1601
1478
  console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
@@ -1686,7 +1563,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1686
1563
  }));
1687
1564
  } catch (e) {
1688
1565
  if (e instanceof Error && e.name === "AbortError") throw e;
1689
- console.warn(e);
1566
+ console.warn("[PollarClient:stream] session-status request failed; will retry", e);
1690
1567
  }
1691
1568
  if (error || !data) {
1692
1569
  await sleep(backoff);
@@ -1726,7 +1603,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1726
1603
  } catch (e) {
1727
1604
  if (e instanceof Error && e.name === "AbortError") throw e;
1728
1605
  if (e instanceof SessionStatusError) throw e;
1729
- console.warn(e);
1606
+ console.warn("[PollarClient:stream] session-status stream read failed; will retry", e);
1730
1607
  } finally {
1731
1608
  reader.releaseLock();
1732
1609
  }
@@ -1754,7 +1631,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
1754
1631
  envelope = await response.json().catch(() => null);
1755
1632
  } catch (e) {
1756
1633
  if (e instanceof Error && e.name === "AbortError") throw e;
1757
- console.warn(e);
1634
+ console.warn("[PollarClient:stream] session-status poll failed; will retry", e);
1758
1635
  }
1759
1636
  if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
1760
1637
  throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
@@ -1958,6 +1835,55 @@ async function loginOAuth(provider, deps) {
1958
1835
  await authenticate(clientSessionId, deps);
1959
1836
  }
1960
1837
 
1838
+ // src/client/auth/passkeyFlow.ts
1839
+ async function loginSmartWallet(deps) {
1840
+ const { api, signal, setAuthState, passkey } = deps;
1841
+ if (!passkey) {
1842
+ setAuthState({
1843
+ step: "error",
1844
+ previousStep: "creating_session",
1845
+ message: "Passkey support is not configured",
1846
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1847
+ });
1848
+ return;
1849
+ }
1850
+ const clientSessionId = await createAuthSession(deps);
1851
+ if (!clientSessionId) return;
1852
+ try {
1853
+ const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
1854
+ body: { clientSessionId },
1855
+ signal
1856
+ });
1857
+ const challenge = challengeData?.content?.challenge;
1858
+ if (!challengeData?.success || !challenge) {
1859
+ return failPasskey(setAuthState, "Failed to start passkey");
1860
+ }
1861
+ setAuthState({ step: "creating_passkey" });
1862
+ const ceremony = await passkey({ challenge });
1863
+ const response = ceremony.response;
1864
+ if (ceremony.kind === "register") {
1865
+ setAuthState({ step: "deploying_smart_account" });
1866
+ const { data } = await api.POST("/auth/passkey/register", {
1867
+ body: { clientSessionId, response },
1868
+ signal
1869
+ });
1870
+ if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
1871
+ } else {
1872
+ const { data } = await api.POST("/auth/passkey/login", {
1873
+ body: { clientSessionId, response },
1874
+ signal
1875
+ });
1876
+ if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
1877
+ }
1878
+ } catch {
1879
+ return failPasskey(setAuthState, "Passkey login failed");
1880
+ }
1881
+ await authenticate(clientSessionId, deps);
1882
+ }
1883
+ function failPasskey(setAuthState, message) {
1884
+ setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
1885
+ }
1886
+
1961
1887
  // src/client/auth/walletFlow.ts
1962
1888
  function withSignal(promise, signal) {
1963
1889
  return Promise.race([
@@ -1984,12 +1910,12 @@ async function loginWallet(type, deps) {
1984
1910
  setAuthState({ step: "wallet_not_installed", walletType: type });
1985
1911
  return;
1986
1912
  }
1987
- const { publicKey } = await withSignal(adapter.connect(), signal);
1988
- connectedWallet = publicKey;
1913
+ const { address } = await withSignal(adapter.connect(), signal);
1914
+ connectedWallet = address;
1989
1915
  deps.storeWalletAdapter(adapter, type);
1990
1916
  setAuthState({ step: "authenticating_wallet" });
1991
1917
  const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1992
- body: { clientSessionId, walletAddress: publicKey },
1918
+ body: { clientSessionId, walletAddress: address },
1993
1919
  signal
1994
1920
  });
1995
1921
  if (walletError || !walletData?.success) {
@@ -2054,8 +1980,12 @@ var PollarClient = class {
2054
1980
  this._transactionStateListeners = /* @__PURE__ */ new Set();
2055
1981
  this._txHistoryState = { step: "idle" };
2056
1982
  this._txHistoryStateListeners = /* @__PURE__ */ new Set();
1983
+ this._sessionsState = { step: "idle" };
1984
+ this._sessionsStateListeners = /* @__PURE__ */ new Set();
2057
1985
  this._walletBalanceState = { step: "idle" };
2058
1986
  this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
1987
+ this._enabledAssetsState = { step: "idle" };
1988
+ this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
2059
1989
  this._authState = { step: "idle" };
2060
1990
  this._authStateListeners = /* @__PURE__ */ new Set();
2061
1991
  this._networkState = { step: "idle" };
@@ -2071,6 +2001,8 @@ var PollarClient = class {
2071
2001
  this._storageDegradeListeners = /* @__PURE__ */ new Set();
2072
2002
  this._walletAdapter = null;
2073
2003
  this._loginController = null;
2004
+ /** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
2005
+ this._resumeController = null;
2074
2006
  this.apiKey = config.apiKey;
2075
2007
  this.id = randomUUID();
2076
2008
  this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
@@ -2083,6 +2015,8 @@ var PollarClient = class {
2083
2015
  this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
2084
2016
  this._walletAdapterResolver = config.walletAdapter ?? null;
2085
2017
  this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
2018
+ this._passkey = config.passkey ?? null;
2019
+ this._passkeySign = config.passkeySign ?? null;
2086
2020
  this._deviceLabel = config.deviceLabel;
2087
2021
  this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
2088
2022
  this._maxIdleMs = config.maxIdleMs;
@@ -2096,7 +2030,9 @@ var PollarClient = class {
2096
2030
  this._initialized = Promise.resolve();
2097
2031
  return;
2098
2032
  }
2099
- console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`);
2033
+ console.info(
2034
+ `[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
2035
+ );
2100
2036
  this._initialized = this._initialize();
2101
2037
  }
2102
2038
  /**
@@ -2134,7 +2070,11 @@ var PollarClient = class {
2134
2070
  }
2135
2071
  await this._restoreSession();
2136
2072
  this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
2137
- if (visible) void this._maybeProactiveRefresh();
2073
+ if (!visible) return;
2074
+ void this._maybeProactiveRefresh();
2075
+ if (this._authState.step === "authenticated" && !this._authState.verified) {
2076
+ void this._resume();
2077
+ }
2138
2078
  });
2139
2079
  }
2140
2080
  /** Detach the cross-tab storage listener and abort any in-flight login. */
@@ -2145,6 +2085,8 @@ var PollarClient = class {
2145
2085
  }
2146
2086
  this._loginController?.abort();
2147
2087
  this._loginController = null;
2088
+ this._resumeController?.abort();
2089
+ this._resumeController = null;
2148
2090
  this._clearRefreshTimer();
2149
2091
  if (this._visibilityUnsubscribe) {
2150
2092
  this._visibilityUnsubscribe();
@@ -2159,7 +2101,9 @@ var PollarClient = class {
2159
2101
  request.headers.set("x-pollar-api-key", self.apiKey);
2160
2102
  self._lastRequestAt = Date.now();
2161
2103
  await self._initialized;
2162
- if (request.body !== null) {
2104
+ const cacheMethod = request.method.toUpperCase();
2105
+ const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
2106
+ if (cacheBodyAllowed && request.body != null) {
2163
2107
  try {
2164
2108
  self._requestBodyCache.set(request, await request.clone().arrayBuffer());
2165
2109
  } catch (err) {
@@ -2246,7 +2190,9 @@ var PollarClient = class {
2246
2190
  }
2247
2191
  }
2248
2192
  }
2249
- const cachedBody = this._requestBodyCache.get(originalRequest);
2193
+ const retryMethod = originalRequest.method.toUpperCase();
2194
+ const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
2195
+ const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
2250
2196
  const retried = new Request(originalRequest.url, {
2251
2197
  method: originalRequest.method,
2252
2198
  headers,
@@ -2290,19 +2236,26 @@ var PollarClient = class {
2290
2236
  throw err;
2291
2237
  }
2292
2238
  if (error || !data) {
2293
- console.warn("[PollarClient] /auth/refresh returned error", { error });
2239
+ console.error("[PollarClient] /auth/refresh returned error", { error });
2294
2240
  await this._clearSession();
2295
2241
  throw new Error("Refresh failed");
2296
2242
  }
2297
2243
  const successData = data;
2298
2244
  if (!successData.success || !successData.content?.token) {
2299
- console.warn("[PollarClient] /auth/refresh response malformed", successData);
2245
+ console.error("[PollarClient] /auth/refresh response malformed", {
2246
+ success: successData.success,
2247
+ hasToken: !!successData.content?.token
2248
+ });
2300
2249
  await this._clearSession();
2301
2250
  throw new Error("Refresh response malformed");
2302
2251
  }
2303
2252
  const newToken = successData.content.token;
2304
2253
  if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
2305
- console.warn("[PollarClient] /auth/refresh token shape invalid", newToken);
2254
+ console.error("[PollarClient] /auth/refresh token shape invalid", {
2255
+ accessToken: typeof newToken.accessToken,
2256
+ refreshToken: typeof newToken.refreshToken,
2257
+ expiresAt: typeof newToken.expiresAt
2258
+ });
2306
2259
  await this._clearSession();
2307
2260
  throw new Error("Refresh response token shape invalid");
2308
2261
  }
@@ -2496,6 +2449,19 @@ var PollarClient = class {
2496
2449
  const controller = this._newController();
2497
2450
  loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2498
2451
  }
2452
+ /**
2453
+ * "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
2454
+ * user, creates a sponsored smart-account C-address. Requires the `passkey`
2455
+ * ceremony to be configured (e.g. via `@pollar/react`).
2456
+ */
2457
+ loginSmartWallet() {
2458
+ if (!isClientRuntime) {
2459
+ warnServerSide("loginSmartWallet");
2460
+ return;
2461
+ }
2462
+ const controller = this._newController();
2463
+ loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2464
+ }
2499
2465
  // ─── Cancel ───────────────────────────────────────────────────────────────
2500
2466
  cancelLogin() {
2501
2467
  this._loginController?.abort();
@@ -2558,6 +2524,29 @@ var PollarClient = class {
2558
2524
  }
2559
2525
  return data.content.sessions;
2560
2526
  }
2527
+ getSessionsState() {
2528
+ return this._sessionsState;
2529
+ }
2530
+ onSessionsStateChange(cb) {
2531
+ this._sessionsStateListeners.add(cb);
2532
+ cb(this._sessionsState);
2533
+ return () => this._sessionsStateListeners.delete(cb);
2534
+ }
2535
+ /**
2536
+ * Fire-and-forget variant of {@link listSessions} that drives the observable
2537
+ * `SessionsState` store instead of returning the array. UI layers subscribe
2538
+ * via `onSessionsStateChange` and stay pure readers — mirrors `fetchTxHistory`.
2539
+ */
2540
+ async fetchSessions() {
2541
+ this._setSessionsState({ step: "loading" });
2542
+ try {
2543
+ const sessions = await this.listSessions();
2544
+ this._setSessionsState({ step: "loaded", sessions });
2545
+ } catch (err) {
2546
+ const message = err instanceof Error ? err.message : "Failed to load sessions";
2547
+ this._setSessionsState({ step: "error", message });
2548
+ }
2549
+ }
2561
2550
  /**
2562
2551
  * Revoke a specific refresh-token family (a single device session). Use
2563
2552
  * `listSessions` to enumerate the familyIds. Revoking the current session
@@ -2634,16 +2623,19 @@ var PollarClient = class {
2634
2623
  cb(this._walletBalanceState);
2635
2624
  return () => this._walletBalanceStateListeners.delete(cb);
2636
2625
  }
2637
- async refreshBalance(publicKey) {
2638
- const pk = publicKey ?? this._session?.wallet?.publicKey;
2639
- if (!pk) {
2626
+ /**
2627
+ * Refreshes the balances of the authenticated user's OWN wallet. The wallet
2628
+ * and network are resolved server-side from the session — no arguments. Drives
2629
+ * `walletBalanceState`. For an arbitrary wallet, use {@link getWalletBalance}.
2630
+ */
2631
+ async refreshBalance() {
2632
+ if (!this._session?.wallet?.address) {
2640
2633
  this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
2641
2634
  return;
2642
2635
  }
2643
2636
  this._setWalletBalanceState({ step: "loading" });
2644
2637
  try {
2645
- const network = this.getNetwork();
2646
- const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
2638
+ const { data, error } = await this._api.GET("/wallet/balance");
2647
2639
  if (!error && data?.success && data.content) {
2648
2640
  this._setWalletBalanceState({ step: "loaded", data: data.content });
2649
2641
  } else {
@@ -2653,6 +2645,53 @@ var PollarClient = class {
2653
2645
  this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
2654
2646
  }
2655
2647
  }
2648
+ /**
2649
+ * General-purpose balance lookup for ANY wallet on ANY network — not scoped
2650
+ * to this application. Enumerates the account's real on-chain holdings via
2651
+ * Horizon (server-side) and returns the data directly (no reactive state).
2652
+ * `network` defaults to the client's current network.
2653
+ */
2654
+ async getWalletBalance(publicKey, network) {
2655
+ const { data, error } = await this._api.GET("/wallet/{publicKey}/balance", {
2656
+ params: { path: { publicKey }, query: { network: network ?? this.getNetwork() } }
2657
+ });
2658
+ if (error || !data?.success || !data.content) {
2659
+ throw new Error("[PollarClient] Failed to load wallet balance");
2660
+ }
2661
+ return data.content;
2662
+ }
2663
+ // ─── Enabled assets ───────────────────────────────────────────────────────
2664
+ getEnabledAssetsState() {
2665
+ return this._enabledAssetsState;
2666
+ }
2667
+ onEnabledAssetsStateChange(cb) {
2668
+ this._enabledAssetsStateListeners.add(cb);
2669
+ cb(this._enabledAssetsState);
2670
+ return () => this._enabledAssetsStateListeners.delete(cb);
2671
+ }
2672
+ /**
2673
+ * Loads the application's enabled assets paired with the authenticated
2674
+ * wallet's on-chain trustline state — so the SDK knows which trustlines still
2675
+ * need to be added. Wallet and network are resolved server-side from the
2676
+ * session. Drives `enabledAssetsState`; mirrors {@link refreshBalance}.
2677
+ */
2678
+ async refreshAssets() {
2679
+ if (!this._session?.wallet?.address) {
2680
+ this._setEnabledAssetsState({ step: "error", message: "No wallet connected" });
2681
+ return;
2682
+ }
2683
+ this._setEnabledAssetsState({ step: "loading" });
2684
+ try {
2685
+ const { data, error } = await this._api.GET("/wallet/assets");
2686
+ if (!error && data?.success && data.content) {
2687
+ this._setEnabledAssetsState({ step: "loaded", data: data.content });
2688
+ } else {
2689
+ this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2690
+ }
2691
+ } catch {
2692
+ this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2693
+ }
2694
+ }
2656
2695
  // ─── Transactions ─────────────────────────────────────────────────────────
2657
2696
  /**
2658
2697
  * Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
@@ -2660,14 +2699,14 @@ var PollarClient = class {
2660
2699
  * inspect the result without subscribing to state changes.
2661
2700
  */
2662
2701
  async buildTx(operation, params, options) {
2663
- if (!this._session?.wallet?.publicKey) {
2702
+ if (!this._session?.wallet?.address) {
2664
2703
  const details = "No wallet connected";
2665
2704
  this._setTransactionState({ step: "error", phase: "building", details });
2666
2705
  return { status: "error", details };
2667
2706
  }
2668
2707
  const body = {
2669
2708
  network: this.getNetwork(),
2670
- publicKey: this._session.wallet.publicKey,
2709
+ address: this._session.wallet.address,
2671
2710
  operation,
2672
2711
  params,
2673
2712
  options: options ?? {}
@@ -2708,7 +2747,7 @@ var PollarClient = class {
2708
2747
  const buildData = this._currentBuildData();
2709
2748
  this._setTransactionState({ step: "signing", ...buildData && { buildData } });
2710
2749
  if (this._walletAdapter) {
2711
- const accountToSign = this._session?.wallet?.publicKey;
2750
+ const accountToSign = this._session?.wallet?.address;
2712
2751
  const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2713
2752
  try {
2714
2753
  const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
@@ -2729,10 +2768,10 @@ var PollarClient = class {
2729
2768
  return { status: "error", ...details && { details } };
2730
2769
  }
2731
2770
  }
2732
- const publicKey = this._session?.wallet?.publicKey ?? "";
2771
+ const address = this._session?.wallet?.address ?? "";
2733
2772
  try {
2734
2773
  const { data, error } = await this._api.POST("/tx/sign", {
2735
- body: { network: this.getNetwork(), publicKey, unsignedXdr }
2774
+ body: { network: this.getNetwork(), address, unsignedXdr }
2736
2775
  });
2737
2776
  if (!error && data?.success && data.content?.signedXdr) {
2738
2777
  const { signedXdr, idempotencyKey } = data.content;
@@ -2785,12 +2824,12 @@ var PollarClient = class {
2785
2824
  const buildData = this._currentBuildData();
2786
2825
  const outcomeExtra = buildData ? { buildData } : {};
2787
2826
  this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
2788
- const publicKey = this._session?.wallet?.publicKey ?? "";
2827
+ const address = this._session?.wallet?.address ?? "";
2789
2828
  try {
2790
2829
  const { data, error } = await this._api.POST("/tx/submit", {
2791
2830
  body: {
2792
2831
  network: this.getNetwork(),
2793
- publicKey,
2832
+ address,
2794
2833
  signedXdr,
2795
2834
  ...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
2796
2835
  }
@@ -2850,6 +2889,19 @@ var PollarClient = class {
2850
2889
  * `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
2851
2890
  */
2852
2891
  async signAndSubmitTx(unsignedXdr) {
2892
+ if (this._session?.wallet?.type === "smart") {
2893
+ const buildData2 = this._currentBuildData();
2894
+ if (!buildData2?.smart) {
2895
+ const details = "no prepared smart transaction; call buildTx first";
2896
+ this._setTransactionState({ step: "error", phase: "signing", details });
2897
+ return { status: "error", details };
2898
+ }
2899
+ return this._signSubmitSmart(buildData2);
2900
+ }
2901
+ if (!unsignedXdr) {
2902
+ this._setTransactionState({ step: "error", phase: "signing", details: "missing unsigned transaction" });
2903
+ return { status: "error", details: "missing unsigned transaction" };
2904
+ }
2853
2905
  if (this._walletAdapter) {
2854
2906
  const signed = await this.signTx(unsignedXdr);
2855
2907
  if (signed.status === "error") {
@@ -2867,7 +2919,7 @@ var PollarClient = class {
2867
2919
  this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
2868
2920
  const body = {
2869
2921
  network: this.getNetwork(),
2870
- publicKey: this._session?.wallet?.publicKey ?? "",
2922
+ address: this._session?.wallet?.address ?? "",
2871
2923
  unsignedXdr
2872
2924
  };
2873
2925
  try {
@@ -2936,14 +2988,20 @@ var PollarClient = class {
2936
2988
  * `signTx`, and `submitTx` separately instead.
2937
2989
  */
2938
2990
  async buildAndSignAndSubmitTx(operation, params, options) {
2991
+ if (this._session?.wallet?.type === "smart") {
2992
+ return this._runSmartTx(operation, params, options);
2993
+ }
2939
2994
  if (this._walletAdapter) {
2940
2995
  const built = await this.buildTx(operation, params, options);
2941
2996
  if (built.status === "error") {
2942
2997
  return { status: "error", ...built.details && { details: built.details } };
2943
2998
  }
2999
+ if (!built.buildData.unsignedXdr) {
3000
+ return { status: "error", details: "build returned no unsigned transaction" };
3001
+ }
2944
3002
  return this.signAndSubmitTx(built.buildData.unsignedXdr);
2945
3003
  }
2946
- if (!this._session?.wallet?.publicKey) {
3004
+ if (!this._session?.wallet?.address) {
2947
3005
  this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
2948
3006
  return { status: "error", details: "No wallet connected" };
2949
3007
  }
@@ -2952,7 +3010,7 @@ var PollarClient = class {
2952
3010
  const { data, error } = await this._api.POST("/tx/build-sign-submit", {
2953
3011
  body: {
2954
3012
  network: this.getNetwork(),
2955
- publicKey: this._session.wallet.publicKey,
3013
+ address: this._session.wallet.address,
2956
3014
  operation,
2957
3015
  params,
2958
3016
  options: options ?? {}
@@ -2996,6 +3054,113 @@ var PollarClient = class {
2996
3054
  async runTx(operation, params, options) {
2997
3055
  return this.buildAndSignAndSubmitTx(operation, params, options);
2998
3056
  }
3057
+ /**
3058
+ * Smart-wallet (passkey / C-address) transaction: build (server prepares the
3059
+ * SAC transfer + returns the auth digest) → sign the digest with the passkey
3060
+ * → submit (server assembles the signed auth entry and broadcasts; the
3061
+ * sponsor pays the fee). State machine: building → built → signing →
3062
+ * submitting → success.
3063
+ */
3064
+ async _runSmartTx(operation, params, options) {
3065
+ const address = this._session?.wallet?.address;
3066
+ if (!address) {
3067
+ this._setTransactionState({ step: "error", phase: "building", details: "No wallet connected" });
3068
+ return { status: "error", details: "No wallet connected" };
3069
+ }
3070
+ if (!this._passkeySign) {
3071
+ const details = "Passkey signer not configured";
3072
+ this._setTransactionState({ step: "error", phase: "signing", details });
3073
+ return { status: "error", details };
3074
+ }
3075
+ this._setTransactionState({ step: "building" });
3076
+ let buildData;
3077
+ try {
3078
+ const body = {
3079
+ network: this.getNetwork(),
3080
+ address,
3081
+ operation,
3082
+ params,
3083
+ options: options ?? {}
3084
+ };
3085
+ const { data, error } = await this._api.POST("/tx/build", { body });
3086
+ if (error || !data?.success || !data.content?.smart) {
3087
+ const details = error?.details ?? "Failed to build transaction";
3088
+ this._setTransactionState({ step: "error", phase: "building", details });
3089
+ return { status: "error", details };
3090
+ }
3091
+ buildData = data.content;
3092
+ } catch (err) {
3093
+ const details = err instanceof Error ? err.message : void 0;
3094
+ this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
3095
+ return { status: "error", ...details && { details } };
3096
+ }
3097
+ this._setTransactionState({ step: "built", buildData });
3098
+ return this._signSubmitSmart(buildData);
3099
+ }
3100
+ /**
3101
+ * Steps 2–3 of the smart-wallet flow: sign the prepared auth digest with the
3102
+ * passkey, then submit. Shared by `_runSmartTx` (atomic) and `signAndSubmitTx`
3103
+ * (split flow, when a smart build is already on the state machine).
3104
+ */
3105
+ async _signSubmitSmart(buildData) {
3106
+ const address = this._session?.wallet?.address;
3107
+ const smart = buildData.smart;
3108
+ if (!address || !smart) {
3109
+ const details = "no prepared smart transaction";
3110
+ this._setTransactionState({ step: "error", phase: "signing", buildData, details });
3111
+ return { status: "error", buildData, details };
3112
+ }
3113
+ if (!this._passkeySign) {
3114
+ const details = "Passkey signer not configured";
3115
+ this._setTransactionState({ step: "error", phase: "signing", buildData, details });
3116
+ return { status: "error", buildData, details };
3117
+ }
3118
+ this._setTransactionState({ step: "signing", buildData });
3119
+ let assertion;
3120
+ try {
3121
+ assertion = await this._passkeySign({ credentialId: smart.credentialId, challenge: smart.digest });
3122
+ } catch (err) {
3123
+ const details = err instanceof Error ? err.message : void 0;
3124
+ this._setTransactionState({ step: "error", phase: "signing", buildData, ...details && { details } });
3125
+ return { status: "error", buildData, ...details && { details } };
3126
+ }
3127
+ this._setTransactionState({ step: "submitting", buildData });
3128
+ const outcomeExtra = { buildData };
3129
+ try {
3130
+ const { data, error } = await this._api.POST("/tx/submit", {
3131
+ body: {
3132
+ network: this.getNetwork(),
3133
+ address,
3134
+ smart: { entryXdr: smart.entryXdr, funcXdr: smart.funcXdr, assertion }
3135
+ }
3136
+ });
3137
+ if (!error && data?.success && data.content) {
3138
+ const { hash, status: backendStatus, resultCode } = data.content;
3139
+ if (backendStatus === "SUCCESS") {
3140
+ this._setTransactionState({ step: "success", hash, buildData });
3141
+ return { status: "success", hash, ...outcomeExtra };
3142
+ }
3143
+ if (backendStatus === "PENDING") {
3144
+ this._setTransactionState({ step: "submitted", hash, buildData });
3145
+ return { status: "pending", hash, ...outcomeExtra };
3146
+ }
3147
+ this._setTransactionState({
3148
+ step: "error",
3149
+ phase: "submitting",
3150
+ buildData,
3151
+ ...resultCode && { details: resultCode }
3152
+ });
3153
+ return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
3154
+ }
3155
+ const details = error?.details;
3156
+ this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
3157
+ return { status: "error", ...outcomeExtra, ...details && { details } };
3158
+ } catch (err) {
3159
+ const details = err instanceof Error ? err.message : void 0;
3160
+ this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
3161
+ return { status: "error", ...outcomeExtra, ...details && { details } };
3162
+ }
3163
+ }
2999
3164
  // ─── App config ───────────────────────────────────────────────────────────
3000
3165
  async getAppConfig() {
3001
3166
  try {
@@ -3049,10 +3214,18 @@ var PollarClient = class {
3049
3214
  this._txHistoryState = next;
3050
3215
  for (const cb of this._txHistoryStateListeners) cb(next);
3051
3216
  }
3217
+ _setSessionsState(next) {
3218
+ this._sessionsState = next;
3219
+ for (const cb of this._sessionsStateListeners) cb(next);
3220
+ }
3052
3221
  _setWalletBalanceState(next) {
3053
3222
  this._walletBalanceState = next;
3054
3223
  for (const cb of this._walletBalanceStateListeners) cb(next);
3055
3224
  }
3225
+ _setEnabledAssetsState(next) {
3226
+ this._enabledAssetsState = next;
3227
+ for (const cb of this._enabledAssetsStateListeners) cb(next);
3228
+ }
3056
3229
  // ─── Private ──────────────────────────────────────────────────────────────
3057
3230
  _newController() {
3058
3231
  this._loginController?.abort();
@@ -3077,6 +3250,7 @@ var PollarClient = class {
3077
3250
  this._walletAdapter = adapter;
3078
3251
  await writeWalletType(this._storage, this.apiKeyHash, id);
3079
3252
  },
3253
+ ...this._passkey ? { passkey: this._passkey } : {},
3080
3254
  ...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
3081
3255
  };
3082
3256
  }
@@ -3106,7 +3280,7 @@ var PollarClient = class {
3106
3280
  }
3107
3281
  }
3108
3282
  if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
3109
- if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
3283
+ if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
3110
3284
  throw new Error(
3111
3285
  `[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
3112
3286
  );
@@ -3147,21 +3321,66 @@ var PollarClient = class {
3147
3321
  }
3148
3322
  }
3149
3323
  console.info("[PollarClient] Session restored from storage");
3150
- this._setAuthState({ step: "authenticated", session: this._session });
3324
+ this._setAuthState({ step: "authenticated", session: this._session, verified: false });
3151
3325
  this._scheduleNextRefresh();
3326
+ void this._resume();
3152
3327
  } else {
3153
3328
  console.info("[PollarClient] No session in storage");
3154
3329
  }
3155
3330
  }
3331
+ /**
3332
+ * Validate the restored session against the server and repopulate the
3333
+ * in-memory profile (PII is never persisted, so it's null after a cold
3334
+ * reload). Goes through the normal authed client, so it coalesces with any
3335
+ * in-flight refresh (onRequest awaits `_refreshPromise`) and, being a GET,
3336
+ * is auto-retried after a 401-triggered refresh.
3337
+ *
3338
+ * - 200 → store profile, mark the session `verified`.
3339
+ * - 401 → the refresh-on-401 path already ran; if the family was
3340
+ * revoked, refresh failed and `_clearSession()` took us to
3341
+ * idle. Nothing to do here — don't double-handle.
3342
+ * - network error → stay optimistic (do NOT log out); revalidated later on
3343
+ * `visibilitychange` or first use.
3344
+ */
3345
+ async _resume() {
3346
+ if (!this._session) return;
3347
+ this._resumeController?.abort();
3348
+ const controller = new AbortController();
3349
+ this._resumeController = controller;
3350
+ try {
3351
+ const { data, error } = await this._api.GET("/auth/session/resume", { signal: controller.signal });
3352
+ if (error || !data) return;
3353
+ const content = data.content;
3354
+ if (!content || !this._session) return;
3355
+ this._profile = { ...content };
3356
+ this._setAuthState({ step: "authenticated", session: this._session, verified: true });
3357
+ } catch (err) {
3358
+ if (err?.name === "AbortError") return;
3359
+ console.warn("[PollarClient] resume failed (network); will retry", err);
3360
+ } finally {
3361
+ if (this._resumeController === controller) this._resumeController = null;
3362
+ }
3363
+ }
3156
3364
  async _storeSession(session) {
3157
3365
  console.info("[PollarClient] Session stored");
3366
+ const w = session.wallet;
3158
3367
  const persisted = {
3159
3368
  clientSessionId: session.clientSessionId,
3160
3369
  userId: session.userId ?? null,
3161
3370
  status: session.status,
3162
3371
  token: session.token,
3163
3372
  user: session.user,
3164
- wallet: session.wallet
3373
+ // The wire response still carries the legacy `publicKey` alias (kept for
3374
+ // older SDKs); the persisted session standardizes on `address` only.
3375
+ wallet: {
3376
+ type: w.type,
3377
+ address: w.address ?? w.publicKey ?? null,
3378
+ ...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
3379
+ ...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
3380
+ ...w.linkedAt !== void 0 ? { linkedAt: w.linkedAt } : {},
3381
+ ...w.network !== void 0 ? { network: w.network } : {},
3382
+ ...w.deployTxHash !== void 0 ? { deployTxHash: w.deployTxHash } : {}
3383
+ }
3165
3384
  };
3166
3385
  this._session = persisted;
3167
3386
  if (session.data) {
@@ -3174,7 +3393,7 @@ var PollarClient = class {
3174
3393
  };
3175
3394
  }
3176
3395
  await writeStorage(this._storage, this.apiKeyHash, persisted);
3177
- this._setAuthState({ step: "authenticated", session: persisted });
3396
+ this._setAuthState({ step: "authenticated", session: persisted, verified: true });
3178
3397
  this._scheduleNextRefresh();
3179
3398
  }
3180
3399
  async _clearSession() {
@@ -3226,6 +3445,196 @@ var PollarClient = class {
3226
3445
  }
3227
3446
  };
3228
3447
 
3448
+ // src/keys/web-crypto.ts
3449
+ var DB_NAME = "pollar-keys";
3450
+ var DB_VERSION = 1;
3451
+ var STORE_NAME = "keys";
3452
+ function openDb() {
3453
+ return new Promise((resolve, reject) => {
3454
+ if (typeof indexedDB === "undefined") {
3455
+ reject(new Error("[PollarClient:keys] IndexedDB not available"));
3456
+ return;
3457
+ }
3458
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
3459
+ req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB open failed"));
3460
+ req.onsuccess = () => resolve(req.result);
3461
+ req.onupgradeneeded = () => {
3462
+ const db = req.result;
3463
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
3464
+ db.createObjectStore(STORE_NAME);
3465
+ }
3466
+ };
3467
+ });
3468
+ }
3469
+ function awaitTx(req) {
3470
+ return new Promise((resolve, reject) => {
3471
+ req.onsuccess = () => resolve(req.result);
3472
+ req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB request failed"));
3473
+ });
3474
+ }
3475
+ async function dbGet(key) {
3476
+ const db = await openDb();
3477
+ try {
3478
+ const tx = db.transaction(STORE_NAME, "readonly");
3479
+ const result = await awaitTx(tx.objectStore(STORE_NAME).get(key));
3480
+ return result;
3481
+ } finally {
3482
+ db.close();
3483
+ }
3484
+ }
3485
+ async function dbPut(key, value) {
3486
+ const db = await openDb();
3487
+ try {
3488
+ const tx = db.transaction(STORE_NAME, "readwrite");
3489
+ await awaitTx(tx.objectStore(STORE_NAME).put(value, key));
3490
+ } finally {
3491
+ db.close();
3492
+ }
3493
+ }
3494
+ async function dbDelete(key) {
3495
+ const db = await openDb();
3496
+ try {
3497
+ const tx = db.transaction(STORE_NAME, "readwrite");
3498
+ await awaitTx(tx.objectStore(STORE_NAME).delete(key));
3499
+ } finally {
3500
+ db.close();
3501
+ }
3502
+ }
3503
+ function isCryptoKeyPair(v) {
3504
+ if (typeof v !== "object" || v === null) return false;
3505
+ const obj = v;
3506
+ return obj.privateKey !== void 0 && obj.publicKey !== void 0;
3507
+ }
3508
+ var WebCryptoKeyManager = class {
3509
+ constructor(apiKey) {
3510
+ this.apiKeyHash = null;
3511
+ this.keyPair = null;
3512
+ this.publicJwk = null;
3513
+ this.thumbprint = null;
3514
+ /**
3515
+ * Cached in-flight init. Lets `init()` be called concurrently (or implicitly
3516
+ * from `getPublicJwk` / `sign`) without doing the work twice. Cleared on
3517
+ * failure so callers can retry, and cleared on `reset()`.
3518
+ */
3519
+ this._initPromise = null;
3520
+ if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
3521
+ throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
3522
+ }
3523
+ this.apiKey = apiKey;
3524
+ }
3525
+ /**
3526
+ * Idempotent and safe under concurrency. The first call kicks off the real
3527
+ * init; subsequent (and concurrent) calls return the same in-flight promise.
3528
+ * Other methods (`getPublicJwk`, `getThumbprint`, `sign`) auto-await this so
3529
+ * the manager is self-healing if `init()` was never explicitly invoked.
3530
+ */
3531
+ async init() {
3532
+ if (this.keyPair) return;
3533
+ if (!this._initPromise) {
3534
+ this._initPromise = this._doInit().catch((err) => {
3535
+ console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
3536
+ this._initPromise = null;
3537
+ throw err;
3538
+ });
3539
+ }
3540
+ return this._initPromise;
3541
+ }
3542
+ async _doInit() {
3543
+ if (!this.apiKeyHash) {
3544
+ this.apiKeyHash = await hashApiKey(this.apiKey);
3545
+ }
3546
+ let pair;
3547
+ try {
3548
+ pair = await dbGet(this.apiKeyHash);
3549
+ if (pair && !isCryptoKeyPair(pair)) pair = void 0;
3550
+ } catch {
3551
+ pair = void 0;
3552
+ }
3553
+ if (!pair) {
3554
+ pair = await globalThis.crypto.subtle.generateKey(
3555
+ { name: "ECDSA", namedCurve: "P-256" },
3556
+ // false → private key non-extractable; per W3C ECDSA spec the public
3557
+ // key is always extractable regardless of this flag.
3558
+ false,
3559
+ ["sign", "verify"]
3560
+ );
3561
+ try {
3562
+ await dbPut(this.apiKeyHash, pair);
3563
+ } catch {
3564
+ }
3565
+ }
3566
+ this.keyPair = pair;
3567
+ this.publicJwk = await this._exportPublicJwk(pair.publicKey);
3568
+ this.thumbprint = await computeJwkThumbprint(this.publicJwk);
3569
+ }
3570
+ /**
3571
+ * Derive the public JWK from a `CryptoKey`. Prefers the `'raw'` export (the
3572
+ * 65-byte uncompressed point `0x04 || X(32) || Y(32)`) and base64url-encodes
3573
+ * the coordinates ourselves — that sidesteps polyfills whose `exportKey('jwk')`
3574
+ * emits non-base64url `x`/`y` (standard base64, `=` padding, or — as seen with
3575
+ * `react-native-quick-crypto` — a stray `.`). Real browsers and most polyfills
3576
+ * support `'raw'` for public EC keys.
3577
+ *
3578
+ * Falls back to the `'jwk'` export (normalized via `canonicalEcJwk`) if `'raw'`
3579
+ * is unsupported or returns an unexpected shape, so this can't regress on a
3580
+ * runtime that only implements the JWK path. Both routes yield identical
3581
+ * coordinate bytes, so the `cnf.jkt` thumbprint is unchanged either way.
3582
+ */
3583
+ async _exportPublicJwk(publicKey) {
3584
+ try {
3585
+ const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey("raw", publicKey));
3586
+ if (raw.length !== 65 || raw[0] !== 4) {
3587
+ throw new Error(`[PollarClient:keys] Unexpected raw EC point (len=${raw.length}, tag=${raw[0]})`);
3588
+ }
3589
+ return {
3590
+ kty: "EC",
3591
+ crv: "P-256",
3592
+ x: base64urlEncode(raw.slice(1, 33)),
3593
+ y: base64urlEncode(raw.slice(33, 65))
3594
+ };
3595
+ } catch {
3596
+ const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
3597
+ return canonicalEcJwk(jwk);
3598
+ }
3599
+ }
3600
+ async reset() {
3601
+ try {
3602
+ if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
3603
+ } catch {
3604
+ }
3605
+ this.keyPair = null;
3606
+ this.publicJwk = null;
3607
+ this.thumbprint = null;
3608
+ this._initPromise = null;
3609
+ }
3610
+ async getPublicJwk() {
3611
+ if (!this.publicJwk) await this.init();
3612
+ if (!this.publicJwk) {
3613
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
3614
+ }
3615
+ return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
3616
+ }
3617
+ async getThumbprint() {
3618
+ if (!this.thumbprint) await this.init();
3619
+ if (!this.thumbprint) {
3620
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
3621
+ }
3622
+ return this.thumbprint;
3623
+ }
3624
+ async sign(payload) {
3625
+ if (!this.keyPair) await this.init();
3626
+ if (!this.keyPair) {
3627
+ throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
3628
+ }
3629
+ const sig = await globalThis.crypto.subtle.sign(
3630
+ { name: "ECDSA", hash: "SHA-256" },
3631
+ this.keyPair.privateKey,
3632
+ payload
3633
+ );
3634
+ return new Uint8Array(sig);
3635
+ }
3636
+ };
3637
+
3229
3638
  // src/stellar/StellarClient.ts
3230
3639
  var HORIZON_URLS = {
3231
3640
  mainnet: "https://horizon.stellar.org",
@@ -3256,10 +3665,6 @@ var StellarClient = class {
3256
3665
 
3257
3666
  // src/index.rn.ts
3258
3667
  _setDefaultKeyManagerFactory((storage, apiKey) => {
3259
- const subtle = globalThis.crypto?.subtle;
3260
- if (subtle && typeof subtle.generateKey === "function" && typeof subtle.sign === "function") {
3261
- return new WebCryptoKeyManager(apiKey);
3262
- }
3263
3668
  return new NobleKeyManager(storage, apiKey);
3264
3669
  });
3265
3670
 
@@ -3267,6 +3672,7 @@ exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
3267
3672
  exports.AlbedoAdapter = AlbedoAdapter;
3268
3673
  exports.FreighterAdapter = FreighterAdapter;
3269
3674
  exports.NobleKeyManager = NobleKeyManager;
3675
+ exports.POLLAR_CORE_VERSION = POLLAR_CORE_VERSION;
3270
3676
  exports.PollarClient = PollarClient;
3271
3677
  exports.StellarClient = StellarClient;
3272
3678
  exports.WalletType = WalletType;